GithubHelp home page GithubHelp logo

jandelgado / rabtap Goto Github PK

View Code? Open in Web Editor NEW
254.0 10.0 16.0 1.34 MB

RabbitMQ wire tap and swiss army knife

License: GNU General Public License v3.0

Makefile 0.46% Go 98.85% Shell 0.60% Dockerfile 0.09%
rabbitmq tap wiretap golang publish subscribe amqp testing

rabtap's Introduction

rabtap - RabbitMQ wire tap

Build Status Coverage Status Go Report Card

Swiss army knife for RabbitMQ. Tap/Pub/Sub messages, create/delete/bind queues and exchanges, inspect broker.

Contents

Features

  • tap to messages being sent to exchanges using RabbitMQ exchange-to-exchange bindings without affecting actual message delivery (aka tapping)
  • display broker related information using the RabbitMQ REST management API
  • save messages and meta data for later analysis and replay
  • publish messages to exchanges
  • consume messages from queues and streams (subscribe)
  • supports TLS
  • no runtime dependencies (statically linked golang single file binary)
  • simple to use command line tool
  • runs on Linux, Windows, Mac and wherever you can compile go

Screenshots

Show broker topology

Output of rabtap info command:

info mode

Output of rabtap info --stats command, showing additional statistics:

info mode

Visualize broker topology with graphviz

Using the --format=dot option, the info command can generate output in the dot format, which can be visualized using graphviz, e.g. rabtap info --show-default --format dot | dot -T svg > mybroker.svg. The resulting SVG file can be visualized with a web browser.

info mode

Tap messages

Output of rabtap in tap mode, showing message meta data and the message body:

info mode

Installation

Docker image

A docker image is provided so rabtap can be used as a docker container, e.g.

$ docker run --rm -ti ghcr.io/jandelgado/rabtap:latest 

Pre-compiled binaries

Pre-compiled binaries can be downloaded for multiple platforms from the releases page.

Arch Linux

Rabtap can be installed from the Arch Linux User Repository (AUR):

$ yay -S rabtap

Installation from source

See the build from source section if you prefer to compile from source.

Usage


rabtap - RabbitMQ wire tap.                    github.com/jandelgado/rabtap

Usage:
  rabtap -h|--help
  rabtap info [--api=APIURI] [--consumers] [--stats] [--filter=EXPR] [--omit-empty]
              [--show-default] [--mode=MODE] [--format=FORMAT] [-kncv]
              [(--tls-cert-file=CERTFILE --tls-key-file=KEYFILE)] [--tls-ca-file=CAFILE]
  rabtap tap EXCHANGES [--uri=URI] [--saveto=DIR]
              [--format=FORMAT]  [--limit=NUM] [--idle-timeout=DURATION] [-jkncsv]
              [(--tls-cert-file=CERTFILE --tls-key-file=KEYFILE)] [--tls-ca-file=CAFILE]
  rabtap (tap --uri=URI EXCHANGES)... [--saveto=DIR]
              [--format=FORMAT]  [--limit=NUM] [--idle-timeout=DURATION] [-jkncsv]
              [(--tls-cert-file=CERTFILE --tls-key-file=KEYFILE)] [--tls-ca-file=CAFILE]
  rabtap sub QUEUE [--uri URI] [--saveto=DIR] [--format=FORMAT] [--limit=NUM]
              [--offset=OFFSET] [--args=KV]... [(--reject [--requeue])] [-jkcsvn]
			  [--idle-timeout=DURATION]
              [(--tls-cert-file=CERTFILE --tls-key-file=KEYFILE)] [--tls-ca-file=CAFILE]
  rabtap pub  [--uri=URI] [SOURCE] [--exchange=EXCHANGE] [--format=FORMAT]
              [--routingkey=KEY | (--header=KV)...]
              [--confirms] [--mandatory] [--delay=DELAY | --speed=FACTOR] [-jkv]
              [(--tls-cert-file=CERTFILE --tls-key-file=KEYFILE)] [--tls-ca-file=CAFILE]
  rabtap exchange create EXCHANGE [--uri=URI] [--type=TYPE] [--args=KV]... [-kv]
              [--autodelete] [--durable]
              [(--tls-cert-file=CERTFILE --tls-key-file=KEYFILE)] [--tls-ca-file=CAFILE]
  rabtap exchange bind EXCHANGE to DESTEXCHANGE [--uri=URI] [-kv]
              (--bindingkey=KEY | (--header=KV)... (--all|--any))
              [(--tls-cert-file=CERTFILE --tls-key-file=KEYFILE)] [--tls-ca-file=CAFILE]
  rabtap exchange rm EXCHANGE [--uri=URI] [-kv]
              [(--tls-cert-file=CERTFILE --tls-key-file=KEYFILE)] [--tls-ca-file=CAFILE]
  rabtap queue create QUEUE [--uri=URI] [--queue-type=TYPE] [--args=KV]... [-kv]
              [--autodelete] [--durable] [--lazy]
              [(--tls-cert-file=CERTFILE --tls-key-file=KEYFILE)] [--tls-ca-file=CAFILE]
  rabtap queue bind QUEUE to EXCHANGE [--uri=URI] [-kv]
              (--bindingkey=KEY | (--header=KV)... (--all|--any))
              [(--tls-cert-file=CERTFILE --tls-key-file=KEYFILE)] [--tls-ca-file=CAFILE]
  rabtap queue unbind QUEUE from EXCHANGE [--uri=URI] [-kv]
              (--bindingkey=KEY | (--header=KV)... (--all|--any))
              [(--tls-cert-file=CERTFILE --tls-key-file=KEYFILE)] [--tls-ca-file=CAFILE]
  rabtap queue rm QUEUE [--uri=URI] [-kv]
              [(--tls-cert-file=CERTFILE --tls-key-file=KEYFILE)] [--tls-ca-file=CAFILE]
  rabtap queue purge QUEUE [--uri=URI] [-kv]
              [(--tls-cert-file=CERTFILE --tls-key-file=KEYFILE)] [--tls-ca-file=CAFILE]
  rabtap conn close CONNECTION [--api=APIURI] [--reason=REASON] [-kv]
              [(--tls-cert-file=CERTFILE --tls-key-file=KEYFILE)] [--tls-ca-file=CAFILE]
  rabtap --version

Arguments and options:
 EXCHANGES            comma-separated list of exchanges and optional binding keys,
                      e.g. amq.topic:# or exchange1:key1,exchange2:key2.
 EXCHANGE             name of an exchange, e.g. amq.direct.
 DESTEXCHANGE         name of a a destination exchange in an exchange-to-exchange binding.
 SOURCE               file or directory to publish in pub mode. If omitted, stdin will be read.
 QUEUE                name of a queue.
 CONNECTION           name of a connection.
 DIR                  directory to read messages from.
 -a, --autodelete     create auto delete exchange/queue.
 --all                set x-match=all option in header based routing.
 --any                set x-match=any option in header based routing.
 --api=APIURI         connect to given API server. If APIURL is omitted,
                      the environment variable RABTAP_APIURI will be used.
 --args=KV            A key value pair in the form of "key=value" passed as
                      additional arguments. e.g. '--args=x-queue-type=quorum'
 -b, --bindingkey=KEY binding key to use in bind queue command.
 --by-connection      output of info command starts with connections.
 -c, --color          force colored output
 --confirms           enable publisher confirms and wait for confirmations.
 --consumers          include consumers and connections in output of info command.
 --delay=DELAY        Time to wait between sending messages during publish.
                      If not set then messages will be delayed as recorded.
					  The value must be suffixed with a time unit, e.g. ms, s etc.
 -d, --durable        create durable exchange/queue.
 --exchange=EXCHANGE  Optional exchange to publish to. If omitted, exchange will
                      be taken from message being published (see JSON message format).
 --filter=EXPR        Predicate for info command to filter queues [default: true]
 --format=FORMAT      * for tap, pub, sub command: format to write/read messages to console
                        and optionally to file (when --saveto DIR is given).
                        Valid options are: "raw", "json", "json-nopp". Default: raw
                      * for info command: controls generated output format. Valid
                        options are: "text", "dot". Default: text
 -h, --help           print this help.
 --header=KV          A key value pair in the form of "key=value" used as a
                      routing- or binding-key. Can occur multiple times.
 --idle-timeout=DURATION end reading messages when no new message was received
                      for the given duration.  The value must be suffixed with 
					  a time unit, e.g. ms, s etc.
 -j, --json           deprecated. Use "--format json" instead.
 -k, --insecure       allow insecure TLS connections (no certificate check).
 --lazy               create a lazy queue.
 --limit=NUM          Stop afer NUM messages were received. When set to 0, will
                      run until terminated [default: 0].
 --mandatory          enable mandatory publishing (messages must be delivered to queue).
 --mode=MODE          mode for info command. One of "byConnection", "byExchange".
                      [default: byExchange].
 -n, --no-color       don't colorize output (see also environment variable NO_COLOR).
 --omit-empty         don't show echanges without bindings in info command.
 --offset=OFFSET      Offset when reading from a stream. Can be 'first', 'last',
                      'next', a duration like '10m', a RFC3339-Timestamp or
					  an integer index value. Basically it is an alias for
					  '--args=x-stream-offset=OFFSET'.
 --queue-type=TYPE    type of queue [default: classic].
 --reason=REASON      reason why the connection was closed [default: closed by rabtap].
 --reject             Reject messages. Default behaviour is to acknowledge messages.
 --requeue            Instruct broker to requeue rejected message
 -r, --routingkey=KEY routing key to use in publish mode. If omitted, routing key
                      will be taken from message being published (see JSON
					  message format).
 --saveto=DIR         also save messages and metadata to DIR.
 --show-default       include default exchange in output info command.
 -s, --silent         suppress message output to stdout.
 --speed=FACTOR       Speed factor to use during publish [default: 1.0].
 --stats              include statistics in output of info command.
 -t, --type=TYPE		  type of exchange [default: fanout].
 --tls-cert-file=CERTFILE A Cert file to use for client authentication.
 --tls-key-file=KEYFILE   A Key file to use for client authentication.
 --tls-ca-file=CAFILE     A CA Cert file to use with TLS.
 --uri=URI            connect to given AQMP broker. If omitted, the
                      environment variable RABTAP_AMQPURI will be used.
 -v, --verbose        enable verbose mode.
 --version            show version information and exit.

Examples:
  rabtap tap --uri amqp://guest:guest@localhost/ amq.fanout:
  rabtap tap --uri amqp://guest:guest@localhost/ amq.topic:#,amq.fanout:
  rabtap pub --uri amqp://guest:guest@localhost/ --exchange amq.topic message.json --format=json
  rabtap info --api http://guest:guest@localhost:15672/api

  # use RABTAP_AMQPURI environment variable to specify broker instead of --uri
  export RABTAP_AMQPURI=amqp://guest:guest@localhost:5672/
  rabtap queue create JDQ
  rabtap queue bind JDQ to amq.topic --bindingkey=key
  echo "Hello" | rabtap pub --exchange amq.topic --routingkey "key"
  rabtap sub JDQ
  rabtap queue rm JDQ

  # use RABTAP_APIURI environment variable to specify mgmt api uri instead of --api
  export RABTAP_APIURI=http://guest:guest@localhost:15672/api
  rabtap info
  rabtap info --filter "binding.Source == 'amq.topic'" --omit-empty
  rabtap conn close "172.17.0.1:40874 -> 172.17.0.2:5672"

  # use RABTAP_TLS_CERTFILE | RABTAP_TLS_KEYFILE | RABTAP_TLS_CAFILE environments variables
  # instead of specifying --tls-cert-file=CERTFILE --tls-key-file=KEYFILE --tls-ca-file=CAFILE

Basic commands

Rabtap understands the following commands:

  • tap - taps to an exchange and receives messages sent to the exchange, without affecting actual message delivery (using an exchange-to-exchange binding).
  • sub - subscribes to a queue and consumes messages sent to the queue (acts like a RabbitMQ consumer)
  • pub - publish messages to an exchange, optionally with the timing as recorded.
  • info - show broker related info (exchanges, queues, bindings, stats).
  • queue - create/bind/unbind/remove/purge queue
  • exchange - create/remove exchange
  • conn - close connections

See the examples section for further information.

Broker URI specification

The specification of the RabbitMQ broker URI follows the AMQP URI specification as implemented by the go RabbitMQ client library.

Examples:

Note that according to RFC3986 it might be necessary to escape certain characters like e.g. ? (%3F) or # (%23) as otherwise parsing of the URI may fail with an error.

Authentication

Authentication is either by the username and password provided in the broker URI as desribed above (RabbitMQ PLAIN method), or by mTLS providing a client certificate and key using the --tls-key, --tls-cert options (RabbitMQ EXTERNAL method). If both mTLS and a username and password is provided, then rabtap will use mTLS and PLAIN authentication with the given username and password.

Format specification for tap and sub command

The --format=FORMAT option controls the format of the tap and sub commands when writing messages to the console and optionally to the filesystem (i.e. when --saveto is set).

The FORMAT parameter has the following effect on the output:

FORMAT Format on console Format of saved messages (--saveto DIR)
raw (default) Pretty-printed metadata + raw Message body Metadata as JSON-File + Body as-is
json Pretty-printed JSON wiht base64 encoded body Pretty-printed JSON with base64 encoded body
json-nopp Single line JSON wiht base64 encoded body Pretty-printed JSON with base64 encoded body

Notes:

  • the --json option is now deprecated. Use --format=json instead
  • nopp stands for no pretty-print

Environment variables

Use environment variables to specify standard values for broker and api endpoint.

Default RabbitMQ broker

In cases where the URI argument is optional, e.g. rabtap tap [-uri URI] exchange ..., the URI of the RabbitMQ broker can be set with the environment variable RABTAP_AMQPURI. Example:

$ export RABTAP_AMQPURI=amqp://guest:guest@localhost:5672/
$ rabtap tap amq.fanout:
...

Default RabbitMQ management API endpoint

The default RabbitMQ management API URI can be set using the RABTAP_APIURI environment variable. Example:

$ export RABTAP_APIURI=http://guest:guest@localhost:15672/api
$ rabtap info
...

Default RabbitMQ TLS config

The default TLS certificates path can be set using the RABTAP_TLS_CERTFILE and RABTAP_TLS_KEYFILE and RABTAP_TLS_CAFILE environments variables. All certificate and key files are expected in PEM format. Example:

$ export RABTAP_TLS_CERTFILE=/path/to/certs/user.crt
$ export RABTAP_TLS_KEYFILE=/path/to/certs/user.key
$ export RABTAP_TLS_CAFILE =/path/to/certs/ca.crt
$ echo "Hello" | rabtap pub --exchange amq.topic --routingkey "key"
...

Colored output

Output is colored, when writing to a terminal. This behaviour can be changed:

  • set environment variable NO_COLOR to disable color output (or set --no-color option)
  • set --color option to force colored output

Examples

The following examples assume a RabbitMQ broker running on localhost:5672 and the management API available on port 15672. Easiest way to start such an instance is by running docker run -ti --rm -p 5672:5672 -p 15672:15672 rabbitmq:3-management or similar command to start a RabbitMQ container.

Broker info

The info command uses the REST API of RabbitMQ to gather and display topolgy related information from the broker.

The --mode MODE option controls how the output is structured. Valid options for MODE are byExchange (default) or byConnection.

The --format=FORMAT option controls the format of generated output. Valid options are text for console text format (default) or dot to output the tree structure in dot format for visualization with graphviz.

The features of an exchange are displayed in square brackets with D (durable), AD (auto delete) and I (internal). The features of a queue are displayed in square brackets with D (durable), AD (auto delete) and EX (exclusive).

If the --statistics option is enabled, basic statistics are included in the output.

The --filter option allows to filter output. See filtering section for details. Use the --by-connection to sort output by connection (implies --consumers)

Examples (assume that RABTAP_APIURI environment variable is set):

  • rabtap info --consumers - shows virtual hosts exchanges, queues and consumers of given broker in a tree view (see screenshot).
  • rabtap info --mode=byConnection - shows virtual hosts, connections, consumers and queues of given broker in an tree view.
  • rabtap info --format=dot | dot -T svg > broker.svg - renders broker info into dot format and uses graphviz to render a SVG file for final visualization.

Wire-tapping messages

The tap command allows to tap exchanges and transparently receives messages sent to the exchanges. Rabtap automatically reconnects on connections failures. The syntax of the tap command is rabtap tap [--uri URI] EXCHANGES where the EXCHANGES argument specifies the exchanges and binding keys to use. The EXCHANGES argument is of the form EXCHANGE:[KEY][,EXCHANGE:[KEY]]*. If the exchange name contains a colon, use \\: to escape it, e.g. myexchange\\:with\\:colons:KEY.

The acutal format of the binding key depends on the exchange type (e.g. direct, topic, headers) and is described in the RabbitMQ documentation.

Examples for binding keys used in tap command:

  • # on an exchange of type topic will make the tap receive all messages on the exchange.
  • a valid queue name for an exchange of type direct binds exactly to messages destined for this queue
  • an empty binding key for exchanges of type fanout or type headers will receive all messages published to these exchanges

Note: on exchanges of type headers the binding key is currently ignored and all messages are received by the tap.

The following examples assume that the RABTAP_AMQPURI environment variable is set, otherwise you have to pass the additional --uri URI parameter to the commands below.

  • $ rabtap tap my-topic-exchange:#
  • $ rabtap tap my-fanout-exchange:
  • $ rabtap tap my-headers-exchange:
  • $ rabtap tap my-direct-exchange:binding-key

The following example connects to multiple exchanges:

  • $ rabtap tap my-fanout-exchange:,my-topic-exchange:#,my-other-exchange:binding-key
Tap all messages published or delivered (RabbitMQ FireHose)

The RabbitMQ Firehose Tracer allows to "see" every message that is published or delivered. To use it, the FireHose tracer has to be enabled first:

$ rabbitmqctl trace_on 

Afterwards, every message published or delivered will be CC'd to the topic exhange amq.rabbitmq.trace. The messages can now be tapped with rabtap:

$ rabtap --uri amqp://guest:guest@localhost:5672/ tap amq.rabbitmq.trace:published.#

RabbitMQ sends all messages published or delivered to the FireHose exchange. Published messages are sent with the routing key publish.{exchangename}, while delivered messages are sent with the routing key deliver.{queuename}. Depending on what you want to record, specify your binding accordingly.

Replaying messages from the FireHose exchange

When messages are tapped or subscribed from the FireHose tracer exchange, these messages have the original meta data stored in the headers section of the message. When published later, rabtap detects that these message was recorded from the FireHose (by examining the exchange attribute, which will be set to amq.rabbitmq.trace by RabbitMQ in that case) and automatically transform the message so that the originally published messages are replayed again.

Connect to multiple brokers

Rabtap allows you also to connect simultaneously to multiple brokers and exchanges:

  • $ rabtap tap --uri amqp://broker1 amq.topic:# tap --uri amqp://broker2 amq.fanout:

The example connects to broker1 and taps to the amq.topic exchange and to the amq.fanout exchange on broker2.

Message recorder

All tapped messages can be also be saved for later analysis or replay. Rabtap supports saving of messages in two formats: raw body and metadata in separate files or JSON message format with embedded metadata and message the body base64 encode. Examples:

  • $ rabtap tap amq.topic:# --saveto /tmp - saves messages as pair of files consisting of raw message body and JSON meta data file to /tmp directory.
  • $ rabtap tap amq.topic:# --saveto /tmp --format json - saves messages as JSON files to /tmp directory.

Files are created with file name rabtap-+<Unix-Nano-Timestamp>+ . + <extension>.

Consume Messages (subscribe)

The sub command reads messages from a queue or a stream. The general form of the sub command is:

  rabtap sub QUEUE [--uri URI] [--saveto=DIR] [--format=FORMAT] [--limit=NUM]
              [--offset=OFFSET] [--args=KV]... [(--reject [--requeue])] [-jksvn]
			  [--idle-timeout=DURATION]

Use the --limit=NUM option to limit the number of received messages. If specified, rabtap will terminate, after NUM messages were successfully read.

Use the --reject option to 'nack' messages, which in turn will be discarded by the broker or routed to a configured dead letter exchange (DLX). if --requeue is also set, the message will be returned to the queue.

The --offset=OFFSET option is used when subscribing to streams. Streams are append-only data structures with non-destructive semantics and were introduced with RabbitMQ 3.9. The OFFSET parameter specifies where to start reading from the stream and must be any of: first, last, next, a numerical offset, a RFC3339-Timestamp or a duration specification like 10m. Consult the RabbitMQ documentation for more information on streams.

When --idle-timeout=DURATION is set, the subscribe command will terminate when no new messages were received in the given time period. Look for the description of the --delay option for the format of the DURATION parameter.

Examples:

  • $ rabtap sub somequeue --format=json - will consume messages from queue somequeue and print out messages in JSON format. The Example assumes that RABTAP_AMQPURI environment variable is set, as the --uri=AMQPURI parameter is omitted.
  • rabtap sub somequeue --limit=1 --reject --requeue - consume one message from the queue somequeue and let the broker requeue the message.
  • rabtap sub mystream --offset=first - read all messages from stream mystream.
  • rabtap sub mystream --offset=50 - read messages from stream mystream starting with the 50th message.
  • rabtap sub mystream --offset=10m - read messages from stream mystream which are aged 10 minutes or less.
  • rabtap sub somequeue --idle-timeout=5s - read messages from queue somequeue and exit when there is no new message received for 5 seconds.

Publish messages

The pub command is used to publish messages to an exchange. The messages to be published are either read from a file, or from a directory which contains previously recorded messages (e.g. using the --saveto option of the tap command). The general form of the pub command is:

rabtap pub  [--uri=URI] [SOURCE] [--exchange=EXCHANGE] [--format=FORMAT] 
            [--routingkey=KEY | (--header=HEADERKV)...]
            [--confirms] [--mandatory] [--delay=DELAY | --speed=FACTOR] [-jkv]

Message routing is either specified with a routing key and the --routingkey option or, when header based routing should be used, by specifying the headers with the --header option. Each header is specified in the form KEY=VALUE. Multiple headers can be specified by specifying multiple --header options.

Messages can be published either in raw format, in which they are sent as-is, or in JSON-format, as described here, which includes message metadata and the body in a single JSON document. When multiple messages are published with metadata, rabtap will calculate the time elapsed of consecutive recorded messages using the metadata, and delay publishing accordingly. To set the publishing delay to a fix value, use the --delay option. To publish without delays, use --delay=0s. To modify publishing speed use the --speed option, which allows to set a factor to apply to the delays. A delay is a sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" 0or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".

When the --confirms option is set, rabtap waits for publisher confirmations from the server and logs an error if a confirmation is negative or not received (slows down throughput),

When the --mandatory option is set, rabtap publishes message in mandatory mode. If set and a message can not be delivered to a queue, the server returns the message and rabtap will log an error.

Examples:

  • $ echo hello | rabtap pub --exchange amq.fanout - publish "hello" to exchange amqp.fanout
  • echo "hello" | rabtap pub --exchange amq.header --header KEY=VAL --header X=Y - publish hello to exchange amq.header use set message headers.
  • $ rabtap pub messages.json --format=json - messages are read from file messages.json in rabtap JSON format. Target exchange and routing keys are read from the messages meta data. The messages.json file can contain multiple JSON documents as it is treated as a JSON stream. Rabtap will honor the XRabtapReceived timestamps of the messages and by default will delay the messages as they were recorded. This behaviour can be overridden by the --delay and --speed options.
  • $ rabtap pub --exchange amq.direct -r myKey --format=json messages.json --delay=0s - as before, but publish messages always to exchange amq.direct with routing key myKey and without any delays.
  • $ rabtap pub --exchange amq.direct -r myKey --format=raw somedir --delay=0s - as before, but assuming that somedir is a directory, the messages are read from message files previously recorded to this directory and replayed in the order they were recorded.

Poor mans shovel

Rabtap instances can be connected through a pipe and messages will be read on one side and published to the other. Note that for publish to work in streaming mode, the JSON mode (--format json) must be used on both sides, so that messages are encapsulated in JSON messages.

The example taps messages on broker1 and publishes the messages to the amq.direct exchange on broker2

$ rabtap tap --uri amqp://broker1 my-topic-exchange:# --format json | \
  rabtap pub --uri amqp://broker2 --exchange amq.direct -r routingKey --format json

Close connection

The conn command allows to close a connection. The name of the connection to be closed is expected as parameter. Use the info command with the --consumers option to find the connection associated with a queue. Example:

$ rabtap info --consumers
http://localhost:15672/api (broker ver='3.6.9', mgmt ver='3.6.9', cluster='rabbit@ae1ad1477419')
└── Vhost /
    ├── amq.direct (exchange, type 'direct', [D])
    :
    └── test-topic (exchange, type 'topic', [AD])
        ├── test-q-test-topic-0 (queue, key='test-q-test-topic-0', running, [])
        │   └── __rabtap-consumer-4823a3c0 (consumer user='guest', chan='172.17.0.1:59228 -> 172.17.0.2:5672 (1)')
        │       └── '172.17.0.1:59228 -> 172.17.0.2:5672' (connection client='https://github.com/streadway/amqp', host='172.17.0.2:5672', peer='172.17.0.1:59228')
        ├── test-q-test-topic-1 (queue, key='test-q-test-topic-1', running, [])
        :
$ rabtap conn close '172.17.0.1:59228 -> 172.17.0.2:5672'

Exchange commands

The exchange command is used to create, remove and bind exchanges:

$ rabtap exchange create myexchange --type topic
$ rabtap exchange rm myexchange

The create commands allows to specify additional arguments to be passed to RabbitMQ using the --args=key=value syntax:

$ rabtap exchange create myexchange --type topic --args=alternate-exchange=myae

The bind command creates an exchange-to-exchange binding (similar to a queue-to-exchange binding):

$ rabtap exchange bind myechange to destexchange --bindingkey=KEY

Queue commands

The queue command is used to create, remove, bind or unbind queues:

$ rabtap queue create myqueue
$ rabtap info --show-default
http://localhost:15672/api (broker ver='3.7.8', mgmt ver='3.7.8', cluster='rabbit@b2fe3b3b6826')
└── Vhost /
    ├── (default) (exchange, type 'direct', [D])
    │   └── myqueue (queue, key='myqueue', idle since 2018-12-07 20:46:15, [])
    :
    └── amq.topic (exchange, type 'topic', [D])
$ rabtap queue bind myqueue to amq.topic --bindingkey hello
$ rabtap info --show-default
http://localhost:15672/api (broker ver='3.7.8', mgmt ver='3.7.8', cluster='rabbit@b2fe3b3b6826')
└── Vhost /
    ├── (default) (exchange, type 'direct', [D])
    │   └── myqueue (queue, key='myqueue', idle since 2018-12-07 20:46:15, [])
    :
    └── amq.topic (exchange, type 'topic', [D])
        └── myqueue (queue, key='hello', idle since 2018-12-07 20:46:15, [])
$ rabtap queue unbind myqueue from amq.topic --bindingkey hello
$ rabtap info --show-default
http://localhost:15672/api (broker ver='3.7.8', mgmt ver='3.7.8', cluster='rabbit@b2fe3b3b6826')
└── Vhost /
    ├── (default) (exchange, type 'direct', [D])
    │   └── myqueue (queue, key='myqueue', idle since 2018-12-07 20:46:15, [])
    :
    └── amq.topic (exchange, type 'topic', [D])
$ rabtap queue purge myqueue
$ rabtap queue rm myqueue
$ rabtap info
http://localhost:15672/api (broker ver='3.7.8', mgmt ver='3.7.8', cluster='rabbit@b2fe3b3b6826')
└── Vhost /
    :
    └── amq.topic (exchange, type 'topic', [D])

The create commands allows to specify additional arguments to be passed to RabbitMQ using the --args=key=value syntax. This allows for example to specify the queue type or mode:

  • rabtap queue create quorum_queue --args=x-queue-type=quorum --durable - create a quorum queue named quorum_queue. The same can be achieved by using the --queue-type option, which is an alias for setting the arg x-queue-type: rabtap queue create quorum --queue-type=quorum --durable
  • rabtap queue create mystream --queue-type=stream --durable - create a stream
  • rabtap queue create lazy_queue --lazy - create a classic queue in lazy mode that is named lazy_queue. --lazy is an alias for setting the arg x-queue-mode.

JSON message format

When using the --format json option, messages are print/read as a stream of JSON messages in the following format:

...
{
  "ContentType": "text/plain",
  "ContentEncoding": "",
  "DeliveryMode": 0,
  "Priority": 0,
  "CorrelationID": "",
  "ReplyTo": "",
  "Expiration": "",
  "MessageID": "",
  "Timestamp": "2017-11-10T00:13:38+01:00",
  "Type": "",
  "UserID": "",
  "AppID": "rabtap.testgen",
  "DeliveryTag": 27,
  "Redelivered": false,
  "Exchange": "amq.topic",
  "RoutingKey": "test-q-amq.topic-0",
  "XRabtapReceivedTimestamp": "2019-06-13T19:33:51.920711583+02:00",
  "Body": "dGhpcyB0ZXN0IG1lc3NhZ2U .... IGFuZCBoZWFkZXJzIGFtcXAuVGFibGV7fQ=="
}
...

Note that in JSON mode, the Body is base64 encoded.

Filtering output of info command

When your brokers topology is complex, the output of the info command can become very bloated. The --filter helps you to narrow output to the desired information.

Filtering expressions

A filtering expression is a function that evaluates to true or false (i.e. a predicate). Rabtap allows the specification of predicates to be applied when printing queues using the info command. The output will only proceed if the predicate evaluates to true.

Rabtap uses the govalute to evaluate the predicate. This allows or complex expressions.

See official govaluate documentation for further information.

Note: currently the filter is ignored when used in conjunction with --by-connection.

Evaluation context

During evaluation the context (i.e. the current exchange, queue and binding) is available in the expression as variables:

  • the current exchange is bound to the variable exchange
  • the current queue is bound to the variable queue
  • the curren binding is bound to the variable binding

Examples

The examples assume that RABTAP_APIURI environment variable points to the broker to be used, e.g. http://guest:guest@localhost:15672/api).

  • rabtap info --filter "exchange.Name == 'amq.direct'" --omit-empty - print only queues bound to exchange amq.direct and skip all empty exchanges.
  • rabtap info --filter "queue.Name =~ '.*test.*'" --omit-empty - print all queues with test in their name.
  • rabtap info --filter "queue.Name =~ '.*test.*' && exchange.Type == 'topic'" --omit-empty - like before, but consider only exchanges of type topic.
  • rabtap info --filter "queue.Consumers > 0" --omit --stats --consumers - print all queues with at least one consumer

Type reference

The types reflect more or less the JSON API objects of the REST API of RabbitMQ transformed to golang types.

Exchange type

Definition of the Exchange type
type Exchange struct {
    Name       string
    Vhost      string
    Type       string
    Durable    bool
    AutoDelete bool
    Internal   bool
    MessageStats struct {
        PublishOut
        PublishOutDetails struct {
            Rate float64
        }
        PublishIn        int
        PublishInDetails struct {
            Rate float64
        }
    }
}

Queue type

Definition of the Queue type
type Queue struct {
    MessagesDetails struct {
        Rate float64
    }
    Messages
    MessagesUnacknowledgedDetails struct {
        Rate float64
    }
    MessagesUnacknowledged int
    MessagesReadyDetails   struct {
        Rate float64
    }
    MessagesReady     int
    ReductionsDetails struct {
        Rate float64
    }
    Reductions int
    Node       string
    Exclusive            bool
    AutoDelete           bool
    Durable              bool
    Vhost                string
    Name                 string
    MessageBytesPagedOut int
    MessagesPagedOut     int
    BackingQueueStatus   struct {
        Mode string
        Q1   int
        Q2   int
        Q3  int
        Q4  int
        Len int
        NextSeqID         int
        AvgIngressRate    float64
        AvgEgressRate     float64
        AvgAckIngressRate float64
        AvgAckEgressRate  float64
    }
    MessageBytesPersistent     int
    MessageBytesRAM            int
    MessageBytesUnacknowledged int
    MessageBytesReady          int
    MessageBytes               int
    MessagesPersistent         int
    MessagesUnacknowledgedRAM  int
    MessagesReadyRAM           int
    MessagesRAM                int
    GarbageCollection          struct {
        MinorGcs        int
        FullsweepAfter  int
        MinHeapSize     int
        MinBinVheapSize int
        MaxHeapSize     int
    }
    State string
    Consumers int
    IdleSince string
    Memory    int
}

Binding type

Definition of the Binding type
type Binding struct {
    Source          string
    Vhost           string
    Destination     string
    DestinationType string
    RoutingKey      string
    PropertiesKey string
}

Build from source

Download and build using go install

$ go install github.com/jandelgado/rabtap/cmd/rabtap@latest

Build using Makefile and tests

To build rabtap from source, you need go (version >= 1.18) and golangci-lint installed.

$ git clone https://github.com/jandelgado/rabtap && cd rabtap
$ make test  -or- make short-test
$ make

In order to run all tests (make test) an instance of RabbitMQ is expected to run on localhost. Easiest way to start one is running make run-broker, which will start a RabbitMQ docker container (i.e. docker run -ti --rm -p 5672:5672 -p 15672:15672 rabbitmq:3-management).

Experimental WASM/wasip1 port

Rabtap can be compiled for Web Assembly (WASM) and the new wasip1 GOOS and run on the console using, e.g. wasirun. Example:

$ go version
go version go1.21.3 linux/amd64
$ make wasm-build
CGO_ENABLED=1 GOOS=wasip1 GOARCH=wasm go build -o ./bin/rabtap-wasm ./cmd/rabtap
$ wasirun --version
wasirun v0.6.5
$ wasirun  bin/rabtap-wasm -- --api "http://guest:password@localhost:15672/api" info --no-color
http://localhost:15672/api (broker ver='3.12.6', mgmt ver='3.12.6', cluster='rabbit@3ea5bf2bac2f')
└─ Vhost /
   ├─ amq.direct (exchange(direct), [D])
   ├─ amq.fanout (exchange(fanout), [D])
   ├─ amq.headers (exchange(headers), [D])
   ├─ amq.match (exchange(headers), [D])
   ├─ amq.rabbitmq.trace (exchange(topic), [D|I])
   └─ amq.topic (exchange(topic), [D])

Another example using wasirun and wasmedge to publish and subscribe to a queue:

$ URI="amqp://guest:password@localhost/"
$ rabtap queue create test
$ rabtap queue bind test to amq.topic --bindingkey=key
$ echo "hello" | wasmedge  bin/rabtap-wasm  --uri "$URI" pub --exchange amq.topic --routingkey=key
$ wasirun  bin/rabtap-wasm -- --uri "$URI" sub test --limit=1
------ message received on 2023-10-29T11:48:56Z ------
exchange.......: amq.topic
routingkey.....: key
hello

See my blog for details.

Limitations:

  • environment variables like RABTAP_AMQPURI not supported, must specify all options on the command line
  • depending on the runtime, colors must explicitly set or disabled using --color and --no-color, since terminal detection not working.

Test data generator

A simple test data generator tool for manual tests is included in the cmd/testgen directory.

Contributing

  • fork this repository
  • create your feature branch
  • add code
  • add tests and make sure test coverage does not fall (make test)
  • add documentation
  • commit changes
  • submit a PR

Author

Jan Delgado (jdelgado at gmx dot net)

Copyright and license

Copyright (c) 2017-2023 Jan Delgado. rabtap is licensed under the GPLv3 license.

rabtap's People

Contributors

dependabot[bot] avatar fgouteroux avatar jandelgado avatar nvllsvm avatar szaydel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rabtap's Issues

Allow ':' in exchange name

When tapping exchanges, ':' is used as a separator. Unfortunately, I'm using a framework (MassTransit) that creates exchanges with ':' in them (it uses type URNs for bindings), and it doesn't look like there's any way to escape it, since the code just uses a split.

Is it possible to provide some way to tap exchanges that include a ':'? Perhaps allow some kind of escaping? Ideally, a different character should be used for separating exchange and key, the AQMP spec (https://www.rabbitmq.com/amqp-0-9-1-reference.html) says:

The exchange name consists of a non-empty sequence of these characters: letters, digits, hyphen, underscore, period, or colon.

So there are plenty of other options. Of course, this would break existing scripts which is far from ideal.

Thanks!

Enhancement: To allow run 'rabtap tap' as a background process (with a switch parameter). So that other commands in the same script can continue without going in the taps 'listening' mode.

Often in our system tests, there are multiple steps involved including publishing, tapping multiple exchanges, sorting, comparing etc all.

To be able to do all these steps in say a PowerShell script, without going in the 'tap mode', I have to tap the exchanges using background jobs (start-job). Later I do a (receive-job) to get the data from these jobs to say an output file.

Is it possible to give background-processing as an option in the tap command? So that when this flag is selected, we can continue with the next steps in the current script without having to create these jobs (for tap commands). This would add more readability to the code and is a day to day use case in a system test scenario with more commands in same script (doing publish, multiple exchange taps and other operations for say sort/compare etc).

P.S: I know there are workaround like (start-job and start-process) to achieve this but having this as a part of the command parameter, would be awesome!

Dont know if that is possible though?

PowerShell: First publish lost and for subsequent publishes missing end brackets '}' when using PowerShell to receive output

Summary:
I am using PowerShell to create jobs to set up the taps.
Then I publish messages and receive job output.

So, If I have three files to iterate from a folder to publish messages; What I see is that:

  1. First files messages are "Lost"
  2. Second files messages are "Received" but with a missing "}" for the last JSON msg.
  3. Third (and subsequent) file, starts with "}{" and the last line misses "}"

Note that this is not an issue when I run another PowerShell instance to Set up a tap and receive the output (i.e. only happens when I use the job mechanism). Due to this, I am not sure if this is entirely a PowerShell issue or something that you can fix in rabtap? In any case, I bring this to your notice so that you can try it at your end and atleast are aware of this issue. If something can be done to fix this, that would ofcourse be awesome!

Steps:
The code that I am using is as below:

Step01: Set up tap using PowerShell jobs

$pingblockForRawMessagesTap = {
rabtap tap "Name.A.B.MyGateway:GatewayMessage:" --json
}

    Start-Job $pingblockForRawMessagesTap -Name tapGatewayMsg

Step02: Publish messages by reading JSON files.

Get-Content $inputMessages | rabtap pub Name.A.B.MyGateway:GatewayMessage --json

Step03: Receive output

Receive-Job -Name tapGatewayMsg | Out-File $outputRawMessages

#Output of the first file will be lost. Second have a missing '}' in end. and the next file starts as '}{' with each file missing '{' in the last line.

Using wrong exchange name doesn't result in any error/warning message when publishing message

Using rabtap v1.27 to publish a message with the command like below.
The raptap output doesn't indicate whether publishing was actually successful, i.e. it states that message got published
while RabbitMQ states an error because the used exchange is unknown.

$ RABTAP_AMQPURI=amqp://guest:guest@localhost:5672/
$ RABTAP_APIURI=http://guest:guest@localhost:15672/api
$ rabtap --verbose pub --exchange non_existing_exchange --routingkey foo --format json rabtap-1628256743950000000.json
INFO[0000] waiting for new session ...
INFO[0000] sleeping for 0s
DEBUG[0000] publishing message to exchange 'non_existing_exchange' with routing key 'foo'
INFO[0000] got new amqp session ...
INFO[0000] publishing channel closed.
INFO[0000] session: shutting down factory (cancel)

While RabbitMQ server indicates an error on its console output regarding an unknown exchange:

rabbitmq              | 2021-08-06 13:39:44.940 [info] <0.2930.0> accepting AMQP connection <0.2930.0> (172.21.0.1:62628 -> 172.21.0.3:5672)
rabbitmq              | 2021-08-06 13:39:44.947 [info] <0.2930.0> connection <0.2930.0> (172.21.0.1:62628 -> 172.21.0.3:5672): user 'guest' authenticated and granted access to vhost '/'
rabbitmq              | 2021-08-06 13:39:44.951 [error] <0.2939.0> Channel error on connection <0.2930.0> (172.21.0.1:62628 -> 172.21.0.3:5672, vhost: '/', user: 'guest'), channel 1:
rabbitmq              | operation basic.publish caused a channel exception not_found: no exchange 'non_existing_exchange' in vhost '/'
rabbitmq              | 2021-08-06 13:39:44.953 [warning] <0.2930.0> closing AMQP connection <0.2930.0> (172.21.0.1:62628 -> 172.21.0.3:5672, vhost: '/', user: 'guest'):
rabbitmq              | client unexpectedly closed TCP connection

Any interest in extending rabtap to be able to use S3?

This is an awesome tool. Many thanks @jandelgado !

I've been roped into performance testing a new product which will require a ton of data in order to address all of our test scenarios. S3 would be the best solution for me for storage and playback.

I would be happy to fork and generate a PR that performs the same as the directory option works today for S3.

Go is not a core capability at my company and I can't saddle our teams with a codebase outside of their skillset, so I would only do this if there was interest in accepting the PR (assuming good quality etc). Otherwise, I'll put rabtap in a pipeline controlled by a node or python app.

Publish a Docker image?

It would be awesome to have rabtap available as a Docker image. I don't know if it's better to run a Go build inside the Dockerfile or just to download/extract the prebuilt binary.

rabtap info: runtime: goroutine stack exceeds 1000000000-byte limit

Hi, it looks like on bigger cluster rabtap info crash (both variant - text in dot format)
rabtap v1.22

in our cluster is 475 exchanges and 551 queues

I tried filter command (like --filter "queue.Consumers > 30") but it doesn't help.

stacktrace example:

rabtap info --show-default -k --format dot
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

runtime stack:
runtime.throw(0x85df9a, 0xe)
	/home/travis/.gimme/versions/go1.12.16.linux.amd64/src/runtime/panic.go:617 +0x72
runtime.newstack()
	/home/travis/.gimme/versions/go1.12.16.linux.amd64/src/runtime/stack.go:1041 +0x6f0
runtime.morestack()
	/home/travis/.gimme/versions/go1.12.16.linux.amd64/src/runtime/asm_amd64.s:429 +0x8f

goroutine 1 [running]:
runtime.heapBitsSetType(0xc01a506450, 0x10, 0x10, 0x804c60)
	/home/travis/.gimme/versions/go1.12.16.linux.amd64/src/runtime/mbitmap.go:938 +0xa56 fp=0xc028000348 sp=0xc028000340 pc=0x415cd6
runtime.mallocgc(0x10, 0x804c60, 0x1, 0x0)
	/home/travis/.gimme/versions/go1.12.16.linux.amd64/src/runtime/malloc.go:969 +0x51c fp=0xc0280003e8 sp=0xc028000348 pc=0x40ba1c
runtime.newobject(0x804c60, 0x0)
	/home/travis/.gimme/versions/go1.12.16.linux.amd64/src/runtime/malloc.go:1068 +0x38 fp=0xc028000418 sp=0xc0280003e8 pc=0x40c078
github.com/Knetic/govaluate.EvaluableExpression.Eval(0x8665b3, 0x22, 0x1, 0xc000176d80, 0x1, 0x1, 0xc0000a9810, 0xc00002618b, 0x4, 0x8e8fc0, ...)
	/home/travis/gopath/pkg/mod/github.com/!knetic/[email protected]/EvaluableExpression.go:158 +0x10d fp=0xc0280004a8 sp=0xc028000418 pc=0x75972d
github.com/Knetic/govaluate.EvaluableExpression.Evaluate(0x8665b3, 0x22, 0x1, 0xc000176d80, 0x1, 0x1, 0xc0000a9810, 0xc00002618b, 0x4, 0xc0185bcc90, ...)
	/home/travis/gopath/pkg/mod/github.com/!knetic/[email protected]/EvaluableExpression.go:137 +0x8b fp=0xc028000530 sp=0xc0280004a8 pc=0x75953b
main.PredicateExpression.Eval(0xc0000a97c0, 0xc0185bcc90, 0xc028000690, 0xc0185b5ce0, 0xc018609e60)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/predicate.go:44 +0xb8 fp=0xc0280005f8 sp=0xc028000530 pc=0x77fa38
main.(*PredicateExpression).Eval(0xc0001a60b8, 0xc0185bcc90, 0x85c070, 0x8, 0xc018609f08)
	<autogenerated>:1 +0x46 fp=0xc028000630 sp=0xc0280005f8 pc=0x784016
main.defaultBrokerInfoTreeBuilder.shouldDisplayQueue(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:144 +0x270 fp=0xc0280008f0 sp=0xc028000630 pc=0x76faf0
main.defaultBrokerInfoTreeBuilder.createQueueNodeFromBinding(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc000248990, 0xf, 0xc000248986, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:199 +0x1e0 fp=0xc028001038 sp=0xc0280008f0 pc=0x7705b0
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:237 +0x3d1 fp=0xc0280014f8 sp=0xc028001038 pc=0x770d41
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280019b8 sp=0xc0280014f8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028001e78 sp=0xc0280019b8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028002338 sp=0xc028001e78 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280027f8 sp=0xc028002338 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028002cb8 sp=0xc0280027f8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028003178 sp=0xc028002cb8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028003638 sp=0xc028003178 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028003af8 sp=0xc028003638 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028003fb8 sp=0xc028003af8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028004478 sp=0xc028003fb8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028004938 sp=0xc028004478 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028004df8 sp=0xc028004938 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280052b8 sp=0xc028004df8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028005778 sp=0xc0280052b8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028005c38 sp=0xc028005778 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280060f8 sp=0xc028005c38 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280065b8 sp=0xc0280060f8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028006a78 sp=0xc0280065b8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028006f38 sp=0xc028006a78 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280073f8 sp=0xc028006f38 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280078b8 sp=0xc0280073f8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028007d78 sp=0xc0280078b8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028008238 sp=0xc028007d78 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280086f8 sp=0xc028008238 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028008bb8 sp=0xc0280086f8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028009078 sp=0xc028008bb8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028009538 sp=0xc028009078 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280099f8 sp=0xc028009538 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028009eb8 sp=0xc0280099f8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800a378 sp=0xc028009eb8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800a838 sp=0xc02800a378 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800acf8 sp=0xc02800a838 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800b1b8 sp=0xc02800acf8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800b678 sp=0xc02800b1b8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800bb38 sp=0xc02800b678 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800bff8 sp=0xc02800bb38 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800c4b8 sp=0xc02800bff8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800c978 sp=0xc02800c4b8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800ce38 sp=0xc02800c978 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800d2f8 sp=0xc02800ce38 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800d7b8 sp=0xc02800d2f8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800dc78 sp=0xc02800d7b8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800e138 sp=0xc02800dc78 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800e5f8 sp=0xc02800e138 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800eab8 sp=0xc02800e5f8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800ef78 sp=0xc02800eab8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800f438 sp=0xc02800ef78 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800f8f8 sp=0xc02800f438 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02800fdb8 sp=0xc02800f8f8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028010278 sp=0xc02800fdb8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028010738 sp=0xc028010278 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028010bf8 sp=0xc028010738 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280110b8 sp=0xc028010bf8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028011578 sp=0xc0280110b8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028011a38 sp=0xc028011578 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028011ef8 sp=0xc028011a38 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280123b8 sp=0xc028011ef8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028012878 sp=0xc0280123b8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028012d38 sp=0xc028012878 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280131f8 sp=0xc028012d38 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280136b8 sp=0xc0280131f8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028013b78 sp=0xc0280136b8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028014038 sp=0xc028013b78 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280144f8 sp=0xc028014038 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280149b8 sp=0xc0280144f8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028014e78 sp=0xc0280149b8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028015338 sp=0xc028014e78 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280157f8 sp=0xc028015338 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028015cb8 sp=0xc0280157f8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028016178 sp=0xc028015cb8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028016638 sp=0xc028016178 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028016af8 sp=0xc028016638 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028016fb8 sp=0xc028016af8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028017478 sp=0xc028016fb8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028017938 sp=0xc028017478 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028017df8 sp=0xc028017938 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280182b8 sp=0xc028017df8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028018778 sp=0xc0280182b8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028018c38 sp=0xc028018778 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280190f8 sp=0xc028018c38 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc0280195b8 sp=0xc0280190f8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028019a78 sp=0xc0280195b8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc028019f38 sp=0xc028019a78 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02801a3f8 sp=0xc028019f38 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02801a8b8 sp=0xc02801a3f8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02801ad78 sp=0xc02801a8b8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02801b238 sp=0xc02801ad78 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02801b6f8 sp=0xc02801b238 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02801bbb8 sp=0xc02801b6f8 pc=0x770eaa
main.defaultBrokerInfoTreeBuilder.createExchangeNode(0xc000024371, 0xa, 0x1, 0x8e8720, 0xc0001a60b8, 0x0, 0xc0001a0360, 0xf, 0xc0001a0350, 0x7, ...)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/broker_info_tree_builder.go:231 +0x53a fp=0xc02801c078 sp=0xc02801bbb8 pc=0x770eaa
...additional frames elided...

goroutine 5 [syscall]:
os/signal.signal_recv(0x0)
	/home/travis/.gimme/versions/go1.12.16.linux.amd64/src/runtime/sigqueue.go:139 +0x9c
os/signal.loop()
	/home/travis/.gimme/versions/go1.12.16.linux.amd64/src/os/signal/signal_unix.go:23 +0x22
created by os/signal.init.0
	/home/travis/.gimme/versions/go1.12.16.linux.amd64/src/os/signal/signal_unix.go:29 +0x41

goroutine 7 [select]:
main.main.func2(0xc00001f4a0, 0xc0001a2700, 0x8ef960, 0xc0001a4300)
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/main.go:219 +0xb7
created by main.main
	/home/travis/gopath/src/github.com/jandelgado/rabtap/cmd/rabtap/main.go:218 +0x3a9

Feature Request: Limit number of messages consumed

Thanks for this really useful tool!
I'd like to be able to consume n messages per invocation.
Additionally I'd like to be able to use the --delay option when consuming to limit the rate at which messages are consumed.

eg rabtap sub somequeue --limit 10 --delay 30s would consume 1 message every 30s until 10 messages had been consumed.

Is this something you might consider implementing?
Thanks!

Feature request: Allow replay from `--saveto` directory

I'm working on a project that exchanges ProtoBuf serialized messages. By default rabtap dumps the binary to stdout and messes up my terminal. I see redirection rabtap ... > /dev/null is a workaround for this. I would be glad if the stdout can be turned off and keeps --saveto option.

EDIT:
Change the title for more clarification.

Non-beautified JSON output

For automation & further processing of messages it's somewhat annoying that the JSON-formatted output is beautified. Therefore you can't parse rabtap's output line by line. You need to assemble the whole output line by line until the JSON message is completed.

It would be awesome if there would be a flag to disable beautification of the JSON-output (or to opt-in to it).

Feature Request: Terminate after all messages are consumed from queue

I like to use rabtap to consume all messages from a DeadLetter/ParkinLot queue to inspect them and later maybe re-publish them.

Currently rabtap seem to continue to consume message from given queue without ever terminating ( unless I hit Ctrl-C ).

Would be nice if rabtap would terminate ( after a given timeout ) when no more messages where consumed from given queue.

How to specify a virtual host?

Hi, thanks for developing this tool.

I have a project where I'm working with multiple RabbitMQ virtual hosts and I would like to tap to a specified exchange in a virtual host other than the default '/'. Does this tool support it?

Currently trying to tap gives following errors:

ERRO[0000] Exception (404) Reason: "NOT_FOUND - no exchange 'messages' in vhost '/'"

Which is because the exchange messages is located in vhost /interface.

Timestamp is never set

Am I missing something or when I use tap mode the Timestamp is never set:

{
"ContentType": "application/json",
"ContentEncoding": "UTF-8",
"DeliveryMode": 0,
"Priority": 0,
"CorrelationID": "",
"ReplyTo": "",
"Expiration": "1296000000",
"MessageID": "",
"Timestamp": "0001-01-01T00:00:00Z",
"Type": "",
"UserID": "",
"AppID": "",
"DeliveryTag": 6,
"Redelivered": false,
"Exchange": "some_exchange",
"RoutingKey": "sdn.all"
}

Is it possible to tap queue on default exchange?

When attempting to run rabtap tap -v "":<my queue name>. I receive

INFO[0000] waiting for new session ...
INFO[0000] got new session ...
INFO[0000] tap: bind to exchange  failed with Exception (403) Reason: "ACCESS_REFUSED - operation not permitted on the default exchange"
INFO[0000] delete exchange: <nil>
DEBUG[0000] subscribe: cancel
INFO[0000] session: shutting down factory (cancel)
ERROR[0000] tap failed with Exception (403) Reason: "ACCESS_REFUSED - operation not permitted on the default exchange

I believe the issue may be we are trying to bind the queue to the default exchange. Am I completely misunderstanding? I am new to rabbit so any help would be appreciated.

Also let me know if I can assist in anyway.

rabtap info fails

> rabtap info --verbose --api <URL>/api

ERROR[0001] failed retrieving info from rabbitmq REST api: json: cannot unmarshal string into Go struct field RabbitConsumer.channel_details of type int
> rabtap --version

v1.24

go 1.16+ is required to build from sources

I tried to build rabtap in Astra Linux which has golang 1.15.0 in repos and failed with error:

github.com/rabbitmq/amqp091-go
/home/username/go/pkg/mod/github.com/rabbitmq/[email protected]/connection.go:919:16: undefined: os.ReadFile
note: module requires Go 1.16

So I'm suggesting to update ReadMe.md as go 1.12 requirement is listed there.

How to specify authentication mechanism

The server is configured with tls enabled, then try to publish:

echo hello | ./rabtap pub --uri=amqps://admin:[email protected]:30047/ --exchange=amq.direct --routingkey=test \
                      --tls-ca-file=... \
                      --tls-cert-file=... \
                      --tls-key-file=...

error:

ERROR[0000] session: cannot (re-)dial: Exception (403) Reason: "SASL could not negotiate a shared mechanism": "amqps://admin:[email protected]:30047/" 
ERROR[0000] publish: session factory closed 

Toggle console output

It would be nice if it were possible to not show any output to console windows.
If you are tapping a high frequency exchange, the console slows down the saving of the messages as json files by quite a bit.

AUR package

Hey,

FYI - I've created an AUR package for rabtap https://aur.archlinux.org/packages/rabtap-bin/
It can be installed on Arch-like systems using one of the AUR clients (eg. yay).

If you want to you can add this to the installation guides. If not, feel free to close this issue :)

Replay a directory

A feature I would find useful would be to "replay" all the messages recorded in a directory at a speed comparable to their original. I could build this on top of rabtap by using a shell script or a Python script to examine the messages and the .json metadata files, but a built-in facility would be convenient.

It could look something like the following:

rabtap replay <directoryName> [--speed=1x]

where directoryName would be a directory containing previously recorded messages and their metadata. The speed parameter would specify how fast to replay the messages and would require that the timestamp had been added when the messages were originally sent. (Or the recording could be modified to automatically add a current timestamp as an option.) If no timestamps are present the replay would always be "as fast as possible".

The default speed of 1x would replay at real time as follows. The first message would be sent immediately and its timestamp would then be matched with the current timestamp. For all subsequent messages we would compute the duration between the timestamp and the first timestamp, add that duration to our starting timestamp, and pause until that time is reached. 2x, 3x, and so on (including 0.5x and other <1 numbers) would modify the duration appropriately. A value of "max" or perhaps "99x" or something similar would be used to indicate "as fast as possible", i.e. publish as soon as read.

The exchange and routing key for each message would by default be the same as when it was recorded, but could be overridden via the command line.

The idea of this feature is to allow a significant recording over time to be replayed in a manner similar to the original. This is useful for:

  1. assisting developers to make changes for their components without having to have full end-to-end installations in place
  2. allowing recordings to be used for automated testing
  3. allowing recordings to be used for performance testing (i.e. can our new version keep up with the rates of the old version).

If this is deemed useful, our software team may be willing to perform the work.

How to escape special characters in password

Hi,

Rabtab 1.38.2 on Windows 11. I have a password with a few special characters which I am trying to pass to rabtap's --uri. Any idea how to escape it? I have tried % escape to no avail.

rabtap.exe --uri="amqps://myuser:AB(cdfeJp7z^E0..,@mydomain:5671" queue create test_queue -kv
time="2023-08-28T09:59:31+02:00" level=fatal msg="failed to parse AMQP URL: parse "amqps://myuser:AB(cdfeJp7z^E0..,@my domain:5671": net/url: invalid userinfo"

Cheers
Sébastien

Feature Request: Terminate when all messages from file(s) where published

I like to use rabtap to publish all messages from given JSON files via selected exchange/routing-key.

Currently rabtap seem to stay connect to AMQP server after all selected messages from the selected files have been published ( unless I hit Ctrl-C ).

Would be nice if rabtap would terminate after all messages from selected files have been published.

go get: installing executables with 'go get' in module mode is deprecated.

Maybe install command should be:

go install github.com/jandelgado/rabtap/cmd/rabtap@latest

Not sure about GO111MODULE though

$ GO111MODULE=on go get github.com/jandelgado/rabtap/cmd/rabtap
go get: installing executables with 'go get' in module mode is deprecated.
	Use 'go install pkg@version' instead.
	For more information, see https://golang.org/doc/go-get-install-deprecation
	or run 'go help get' or 'go help install'.

Feature: push messages directly to a queue

I'd really love it if there were some type of rabtap send functionality, where we could push messages directly from a queue instead of to an exchange. Basically the reverse of the very nice rabtap sub. Apologies if this is already there, I've checked the docs but couldn't find anything.

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.