Skip to content

FAQ & Debugging

Gerfey edited this page Jul 24, 2025 · 4 revisions

Listeners & Handlers


Q: My handler is not called when sending a message. What am I doing wrong?

A: Make sure of the following points:

  • You have registered the handler via Builder.RegisterHandler. If you do not do this, Messenger will not know about your handler, and you will receive a warning “no handlers registered for message type” in the logs or an error during Dispatch.
  • Check that the type of message you are dispatching exactly matches the type expected by the handler. For example, if a handler is defined for *MyMessage, and you send the value MyMessage (not a pointer), then Messenger will see a different type. It is usually better to send a pointer to a structure if the handler is declared with a pointer (as in the examples above).
  • if the message is sent via an external transport (for example, RabbitMQ), make sure that you have called messenger.Run(ctx) – without starting, consumers will not be activated and messages in the queue will not be processed. If Messenger is running in multi-bus mode, check that your handler is either on default_bus or implements GetBusName() correctly, and that the message is being routed to the correct bus.
  • check the logs (logger): the built-in logger slog at the Debug level can give hints what is happening with the message (for example, it was sent to an external transport and therefore was not processed locally, or handlers were not found).

Q: How do I correctly specify the message type in the routing configuration?

A: In the YAML configuration, in the routing section, you need to use the name of the message type. Messenger is trying to match this string with the registered handler types. If your type is not in the main package and several packages may have structures of the same name, you must specify the full name (for example, pkg.MessageType). Mapping errors result in the message “failed to resolve message type 'X' in routing configuration” when calling `Build()'. If you receive such an error, you can:

  • check the spelling/namespace of the type in the config,
  • make sure that the appropriate handler is registered before calling Build(),
  • as a last resort, explicitly register the message via RegisterMessage(&MyMessage{}) (this will fill the TypeResolver with type information, even without a handler). The latter method is useful for services that only send messages, but do not process them, so that routing does not crash on an unknown type.

Q: When running messenger.Run(), the application immediately exits with a context error.

A: The Run(ctx) method will return an error when ctx is canceled (for example, if you passed context.Background() without timeout and immediately canceled it, or if ctx was tied to the end of the program). Usually, Run is run in a separate goroutine and context' is used.Background() or another long-lived context. Make sure that the 'ctx` is not canceled ahead of time. For example, don't do this:

ctx, cancel := context.WithCancel(context.Background())
cancel()
messenger.Run(ctx)  // will return immediately.Err()

In this case, Run will exit almost instantly. The correct way is to call Run and then cancel the context when the application is terminated (for example, when an OS signal is received).


Q: Messages are being sent to RabbitMQ, but my handler in the other service is not receiving them.

A: If different services (processes) need to exchange messages through a broker, make sure that:

  • they are configured on the same broker (DSN, exchange, queues, binding keys). Check that exchange.name and the binding keys match.
  • the auto_setup option is enabled in the amqp transport configuration (or you created the exchange/queue yourself in advance). If `auto_setup: false', you need to manually declare the exchange and queues in RabbitMQ, otherwise nothing will bind.
  • check that the exchange type is correct (for example, topic) and the binding key corresponds to the routing key of the messages being sent.
  • make sure that the consumer service has actually started Run and is listening to the queue (when starting Messenger with auto_setup and consumer_pool_size > 0, you should see that consumers are connected; you can check in RabbitMQ Management that there are active consumers).

Q: How is the retry mechanism implemented and what to do if the message could not be processed?

A: The retry mechanism in Messenger works at the transport level. The built-in amqp transport implements the RetryableTransport interface (the Retry method). When sending a message to RabbitMQ fails (for example, the broker is unavailable), Messenger generates the SendFailedMessageEvent event. A listener is registered in the builder, which intercepts this event and decides whether to retry sending. This standard listener, the SendFailedMessageForRetryListener, uses the MultiplierRetryStrategy: it counts down the attempts and the delays between them. It calls transport.Retry(ctx, env) to resend. Repeats continue until max_retries is exceeded or until the message is successfully sent. If sending still fails after the maximum number of attempts, the listener will check if FailureTransport is specified. If yes, the message will be sent to this backup transport (usually a configured "dead-letter" queue). If failure_transport is not set, the message will simply remain unsent; you will see a sending error in the logs on the last attempt. To handle such situations: you can set up a failure_transport in the config (and a separate consumer for this "drop-off" queue for manual processing or alerts), or add your listener to the SendFailedMessageEvent, which, for example, will log or notify developers about the problem.


Q: Is there any way to debug which middleware/listeners are running?

A: Yes, it is convenient to increase the logging level for this. Messenger uses log/slog. By default, slog.Default() is the info level. You can configure your slog.Logger with Level=Debug and pass it to newBuilder. Messenger already contains some debug logs: for example, EventDispatcher logs adding a listener and dispatching an event (to debug), SendMessageMiddleware logs when it does not find transports or sends a message, HandleMessageMiddleware logs the absence of handlers and successful message processing. These messages at the Debug level will help you understand whether the message is passing through the chain, which transports are being selected, and whether listeners are being called.

In addition, you can always add your own middleware or debugging listeners. For example, your LoggerMiddleware (from the example above) will output each stage to the console. Or the listener on SendMessageToTransportsEvent will show that the message is going to be sent and where. Such tools allow you to enlighten the "insides" of Messenger during execution.


Q: When registering the handler/listener, I get an error about an incorrect signature.

A: Messenger imposes strict requirements on the signatures of the Handle methods. For Handlers: the method must accept the context and message, and return either error or (something, error). If, for example, you accidentally wrote a method without a context.Context or pointers did not match, RegisterHandler will return an error like “Handle method must accept exactly 2 parameters” or “last return value must be error”. Correct the signature according to the requirements (see Quick Start). For Listeners: make sure that your function/method accepts 1 or 2 arguments (Context is optional, event is required). For example, if you wrote the Handle() method without parameters or with the wrong parameter type, Dispatcher will not call it. The logs may contain the message “listener does not have Handle method”. Check the listener definition more carefully.


Q: How to gracefully shut down Messenger?

A: To stop correctly, you need to cancel the context passed to messenger.Run(ctx). When ctx.Done() closes, the Run method initiates a stop: it stops all transports (Manager stops their receive cycles) and exits with ctx.Err() == context.Cancelled or another reason for closing. In your code, if you run Run in goroutine, you can, for example, intercept the signal OS (SIGINT/SIGTERM), call cancel()at the context, then wait for Run to finish (for example, via WaitGroup or simple pause). Messenger does not store unsent messages at the exit (except for those that are already in transport). If additional actions are needed upon completion (for example, to wait for all current message processing to complete), you may need to monitor the status yourself (Messenger immediately stops reading new messages, but current processing may still be performed). As a rule, it is enough to call cancel and give the application a few seconds to terminate.


Listeners & Handlers

Clone this wiki locally