Skip to content

FAQ и советы по отладке

Gerfey edited this page Jul 24, 2025 · 2 revisions

Слушатели и обработчики


Q: Мой обработчик не вызывается при отправке сообщения. Что я делаю не так?

A: Убедитесь в следующих моментах:

  • вы зарегистрировали обработчик через Builder.RegisterHandler. Если этого не сделать, Messenger не узнает о вашем обработчике, и вы получите предупреждение “no handlers registered for message type” в логах или ошибку при Dispatch.
  • проверьте, что тип сообщения, которое вы Dispatch-ите, точно соответствует типу, ожидаемому обработчиком. Например, если обработчик определён для *MyMessage, а вы отправляете значение MyMessage (не указатель), то Messenger увидит другой тип. Обычно лучше отправлять указатель на структуру, если обработчик объявлен с указателем (как в примерах выше).
  • если сообщение отправляется через внешний транспорт (например, RabbitMQ), убедитесь, что вы вызвали messenger.Run(ctx) – без запуска, потребители не активируются и сообщения в очередь не будут обработаны. Если Messenger работает в режиме нескольких bus, проверьте, что ваш обработчик либо на default_bus, либо реализует GetBusName() правильно, и что сообщение направляется в нужный bus.
  • проверьте журналы (логгер): встроенный логгер slog на уровне Debug может дать подсказки, что происходит с сообщением (например, оно отправлено во внешний транспорт и потому локально не обработано, или не найдены обработчики).

Q: Как правильно указать тип сообщения в конфигурации routing?

A: В YAML-конфигурации в секции routing нужно использовать имя типа сообщения. Messenger пытается сопоставить эту строку с зарегистрированными типами обработчиков. Если ваш тип находится не в пакете main и несколько пакетов могут иметь одноимённые структуры, необходимо указать полное имя (например, pkg.MessageType). Ошибки сопоставления приводят к сообщению “failed to resolve message type 'X' in routing configuration” при вызове Build(). Если вы получили такую ошибку, вы можете:

  • проверить орфографию/пространство имён типа в конфиге,
  • убедиться, что соответствующий обработчик зарегистрирован до вызова Build(),
  • в крайнем случае – явно зарегистрировать сообщение через RegisterMessage(&MyMessage{}) (это заполнит TypeResolver информацией о типе, даже без обработчика). Последний способ полезен для сервисов, которые только отправляют сообщения, но не обрабатывают их – чтобы routing не падал на неизвестном типе.

Q: При запуске messenger.Run() приложение сразу завершается с ошибкой контекста.

A: Метод Run(ctx) будет возвращать ошибку, когда ctx отменён (например, если вы передали context.Background() без таймаута и сразу его отменили, или если ctx был привязан к завершению программы). Обычно Run запускают в отдельной горутине и используют context.Background() или другой долгоживущий контекст. Убедитесь, что ctx не отменяется раньше времени. Например, не делайте так:

ctx, cancel := context.WithCancel(context.Background())
cancel()
messenger.Run(ctx)  // сразу же вернётся ctx.Err()

В этом случае Run выйдет почти мгновенно. Правильно – вызывать Run и затем отменять контекст при завершении приложения (например, при получении сигнала OS).


Q: Сообщения отправляются в RabbitMQ, но мой обработчик в другом сервисе их не получает.

A: Если разные сервисы (процессы) должны обмениваться сообщениями через брокер, убедитесь, что:

  • они настроены на один и тот же брокер (DSN, exchange, очереди, binding ключи). Проверьте, что exchange.name и ключи binding совпадают. * * в конфигурации транспорта amqp опция auto_setup включена (или вы самостоятельно заранее создали exchange/queue). Если auto_setup: false, вам нужно вручную объявить exchange и очереди в RabbitMQ, иначе ничего не привяжется.
  • проверьте, что тип exchange правильный (например, topic) и binding ключ соответствует routing key отправляемых сообщений.
  • убедитесь, что сервис-потребитель действительно запустил Run и слушает очередь (при запуске Messenger с auto_setup и consumer_pool_size > 0, вы должны увидеть, что подключились потребители; можно проверить в RabbitMQ Management, что имеются активные consumers).

Q: Как реализован механизм повторных попыток и что делать, если сообщение так и не удалось обработать?

A: Механизм ретраев (retry) в Messenger работает на уровне транспорта. Встроенный amqp транспорт реализует интерфейс RetryableTransport (метод Retry). Когда отправка сообщения в RabbitMQ завершается ошибкой (например, брокер недоступен), Messenger генерирует событие SendFailedMessageEvent. В билдере регистрируется слушатель, который перехватывает это событие и решает, делать ли повторную попытку отправки. Этот стандартный слушатель – SendFailedMessageForRetryListener – использует MultiplierRetryStrategy: он отсчитывает попытки и задержки между ними. Он вызывает transport.Retry(ctx, env) для повторной отправки. Повторы продолжаются, пока не превысится max_retries или пока сообщение не будет успешно отправлено. Если после максимального числа попыток отправка так и не удалась, слушатель проверит, указан ли FailureTransport. Если да – сообщение будет отправлено в этот резервный транспорт (как правило, это настроенная "dead-letter" очередь). Если failure_transport не задан, сообщение просто останется неотправленным; в логах вы увидите ошибку отправки на последней попытке. Для обработки таких ситуаций: вы можете настроить failure_transport в конфиге (и отдельный consumer на эту "отбойную" очередь для ручной обработки или алертов), либо добавить своего слушателя на SendFailedMessageEvent, который, например, будет логировать или уведомлять разработчиков о проблеме.


Q: Можно ли как-то отладить, какие middleware/слушатели выполняются?

A: Да, для этого удобно повысить уровень логирования. Messenger использует log/slog. По умолчанию slog.Default() – инфо-уровень. Вы можете настроить свой slog.Logger с Level=Debug и передать его в NewBuilder. Messenger уже содержит некоторые debug-логи: например, EventDispatcher логирует добавление слушателя и диспетчинг события (на debug), SendMessageMiddleware логирует, когда не находит транспорты или отправляет сообщение, HandleMessageMiddleware логирует отсутствие обработчиков и успешную обработку сообщения. Эти сообщения на уровне Debug помогут понять, проходит ли сообщение по цепочке, какие транспорты выбираются, вызываются ли слушатели.

Кроме того, вы всегда можете добавить собственные middleware или слушатели для отладки. Например, ваш LoggerMiddleware (из примера выше) выведет в консоль каждый этап. Или слушатель на SendMessageToTransportsEvent покажет, что сообщение собирается отправляться и куда. Такие инструменты позволяют просветить "внутренности" Messenger во время исполнения.


Q: При регистрации обработчика/слушателя получаю ошибку о неправильной сигнатуре.

A: Messenger предъявляет строгие требования к сигнатурам методов Handle. Для Handlers: метод должен принимать контекст и сообщение, и возвращать либо error, либо (что-то, error). Если, например, вы случайно написали метод без context.Context или с pointers не совпали, RegisterHandler выдаст ошибку вроде “Handle method must accept exactly 2 parameters” или “last return value must be error”. Исправьте сигнатуру в соответствии с требованиями (см. Быстрый старт). Для Listeners: убедитесь, что ваша функция/метод принимает 1 или 2 аргумента (Context опционален, событие – обязателен). Например, если вы написали метод Handle() без параметров или с неправильным типом параметра, Dispatcher его не вызовет. В логах может быть сообщение “listener does not have Handle method”. Проверьте внимательнее определение слушателя.


Q: Как gracefully завершить работу Messenger?

A: Для корректной остановки нужно отменить контекст, переданный в messenger.Run(ctx). Когда ctx.Done() закроется, метод Run инициирует остановку: он останавливает все транспорты (Manager стопорит их receive-циклы) и выйдет с ctx.Err() == context.Canceled или другой причиной закрытия. В вашем коде, если вы запустили Run в горутине, вы можете, например, перехватить сигнал OS (SIGINT/SIGTERM), вызвать cancel() у контекста, затем дождаться, когда Run завершится (например, через WaitGroup или простую паузу). Messenger не хранит неотправленные сообщения на выходе (кроме тех, что уже в транспорте). Если нужны дополнительные действия при завершении (скажем, дождаться завершения всех текущих обработок сообщений), вам, возможно, придется следить за состоянием самостоятельно (Messenger сразу прекращает чтение новых сообщений, но текущие обработки могут ещё выполняться). Как правило, достаточно вызвать cancel и дать приложению несколько секунд завершиться.


Слушатели и обработчики

Clone this wiki locally