Documentation
¶
Index ¶
- Constants
- Variables
- func GetRetryCount(delivery amqp.Delivery) int
- func Permanent(err error) error
- func WillRetry(delivery amqp.Delivery, maxRetries int) bool
- type Consumer
- type ConsumerOption
- type MessageHandler
- type MetricsRecorder
- type Publisher
- type PublisherOption
- type RabbitMQConsumer
- type RabbitMQPublisher
- func (p *RabbitMQPublisher) Close() error
- func (p *RabbitMQPublisher) Connection() *amqp.Connection
- func (p *RabbitMQPublisher) DLQName() string
- func (p *RabbitMQPublisher) DLXName() string
- func (p *RabbitMQPublisher) MaxRetries() int
- func (p *RabbitMQPublisher) Publish(ctx context.Context, message interface{}) error
- func (p *RabbitMQPublisher) PublishToDLQ(ctx context.Context, body []byte, correlationId string) error
- func (p *RabbitMQPublisher) PublishToRetry(ctx context.Context, retryIndex int, body []byte, correlationId string, ...) error
- func (p *RabbitMQPublisher) RetryQueues() []string
Constants ¶
const ( OutcomeSuccess = "success" // handler succeeded, message ACKed OutcomeRetry = "retry" // handler failed, message sent to a retry queue OutcomeDLQ = "dlq" // message sent to the DLQ (retries exhausted or permanent error) OutcomeRequeued = "requeued" // message requeued without consuming a retry attempt (shutdown) )
Consume outcomes reported to MetricsRecorder.RecordConsume.
const RetryCountHeader = "x-retry-count"
RetryCountHeader is the AMQP header key for tracking retry attempts
Variables ¶
var ( ErrConsumerClosed = errors.New("consumer is closed") ErrConsumeSetupFailed = errors.New("failed to setup consumer") ErrNilPublisher = errors.New("publisher is required") ErrAlreadyConsuming = errors.New("consumer is already consuming") ErrDeliveryChannelClosed = errors.New("delivery channel closed") ErrReconnectFailed = errors.New("reconnect attempts exhausted") )
Consumer errors
var ( ErrConnectionFailed = errors.New("failed to connect to RabbitMQ") ErrChannelFailed = errors.New("failed to open channel") ErrQueueSetupFailed = errors.New("failed to setup queue infrastructure") ErrMarshalFailed = errors.New("failed to marshal message") ErrPublishFailed = errors.New("failed to publish message") ErrPublishNotConfirmed = errors.New("publish not confirmed by broker") ErrPublisherClosed = errors.New("publisher is closed") ErrRetryOutOfBounds = errors.New("retry index out of bounds") ErrCloseFailed = errors.New("failed to close connection") )
Common errors returned by the queue package.
var ErrPermanent = errors.New("permanent failure")
ErrPermanent marks a handler error as permanent: the message can never be processed successfully (e.g. malformed payload, missing referenced entity). The consumer sends such messages straight to the DLQ instead of burning retry attempts.
Handlers can either wrap an error with Permanent():
return queue.Permanent(fmt.Errorf("unmarshal event: %w", err))
or wrap the sentinel directly:
return fmt.Errorf("unknown message type %q: %w", t, queue.ErrPermanent)
Both forms satisfy errors.Is(err, queue.ErrPermanent).
Functions ¶
func GetRetryCount ¶ added in v0.34.0
GetRetryCount extracts the retry count from message headers. Unknown types and negative values are treated as 0.
func Permanent ¶ added in v0.51.0
Permanent wraps err so the consumer routes the message straight to the DLQ. Returns nil if err is nil. The wrapped error is available via errors.Unwrap.
func WillRetry ¶ added in v0.52.0
WillRetry reports whether a delivery that fails with a transient error will be routed to a retry queue (true) or dead-lettered (false). It is the exact predicate the consumer applies after the handler returns, so handlers can record the upcoming routing decision without re-deriving it. maxRetries is the number of configured retry queues (publisher.MaxRetries()). Errors matching ErrPermanent go straight to the DLQ regardless.
Types ¶
type Consumer ¶ added in v0.34.0
type Consumer interface {
// Consume starts consuming messages and blocks until context is cancelled
Consume(ctx context.Context, handler MessageHandler) error
// Close stops consuming and closes connections
Close() error
}
Consumer defines the interface for message queue consuming
type ConsumerOption ¶ added in v0.51.0
type ConsumerOption func(*RabbitMQConsumer)
ConsumerOption configures a RabbitMQConsumer.
func WithConsumerMetrics ¶ added in v0.51.0
func WithConsumerMetrics(m MetricsRecorder) ConsumerOption
WithConsumerMetrics sets the metrics recorder for consume and reconnect events. Defaults to a no-op recorder.
type MessageHandler ¶ added in v0.34.0
MessageHandler processes a single message delivery. Return nil to ACK the message, return error to trigger retry logic. Wrap errors with Permanent() to skip retries and go straight to the DLQ. The context carries the message correlation ID (logger.GetCorrelationID).
A panic inside the handler does not crash the process: it is recovered, logged with the stack, and treated as a transient handler error that rides the retry ladder (reaching the DLQ once retries are exhausted).
type MetricsRecorder ¶ added in v0.51.0
type MetricsRecorder interface {
// RecordPublish is called after every publish attempt. queue is the
// routing target (main queue, retry queue, or DLQ name).
RecordPublish(queue string, success bool, duration time.Duration)
// RecordConsume is called after a delivery is processed. outcome is one
// of the Outcome* constants; duration is the handler execution time.
RecordConsume(queue string, outcome string, duration time.Duration)
// RecordReconnect is called when a dropped connection is detected and a
// reconnect cycle starts. component is "publisher" or "consumer".
RecordReconnect(component string)
}
MetricsRecorder receives queue events for instrumentation. Implementations must be safe for concurrent use and must not panic - recorder calls run on the publish and consume paths and, unlike message handlers, are not recovered. The metrics package provides a Prometheus implementation (metrics.QueueMetrics); pass it via WithPublisherMetrics / WithConsumerMetrics. When no recorder is configured, events are discarded.
type Publisher ¶
type Publisher interface {
Publish(ctx context.Context, message interface{}) error
PublishToRetry(ctx context.Context, retryIndex int, body []byte, correlationId string, headers amqp.Table) error
PublishToDLQ(ctx context.Context, body []byte, correlationId string) error
MaxRetries() int
Close() error
}
Publisher defines the interface for message queue publishing with retry support
type PublisherOption ¶ added in v0.51.0
type PublisherOption func(*RabbitMQPublisher)
PublisherOption configures a RabbitMQPublisher.
func WithPublisherLogger ¶ added in v0.51.0
func WithPublisherLogger(logger *slog.Logger) PublisherOption
WithPublisherLogger sets the logger used for reconnection events. Defaults to slog.Default().
func WithPublisherMetrics ¶ added in v0.51.0
func WithPublisherMetrics(m MetricsRecorder) PublisherOption
WithPublisherMetrics sets the metrics recorder for publish and reconnect events. Defaults to a no-op recorder.
type RabbitMQConsumer ¶ added in v0.34.0
type RabbitMQConsumer struct {
// contains filtered or unexported fields
}
RabbitMQConsumer implements Consumer for RabbitMQ
func NewRabbitMQConsumer ¶ added in v0.34.0
func NewRabbitMQConsumer( cfg config.RabbitMQConfig, publisher *RabbitMQPublisher, logger *slog.Logger, opts ...ConsumerOption, ) (*RabbitMQConsumer, error)
NewRabbitMQConsumer creates a new consumer that shares queue infrastructure with the publisher. The publisher must be created first as it declares all queues. If logger is nil, slog.Default() is used.
func (*RabbitMQConsumer) Close ¶ added in v0.34.0
func (c *RabbitMQConsumer) Close() error
Close stops consuming and closes the connection. If a Consume call is active, Close blocks until in-flight handlers finish and Consume returns. Idempotent: subsequent calls return nil.
Close must not be called from inside a MessageHandler: it waits for that very handler to finish and would deadlock. To stop consuming from within a handler, cancel the context passed to Consume instead.
func (*RabbitMQConsumer) Connection ¶ added in v0.34.0
func (c *RabbitMQConsumer) Connection() *amqp.Connection
Connection returns the current AMQP connection for health checks. The returned pointer changes after a reconnect; health checks should use health.NewRabbitMQCheckerWithProvider(consumer.Connection) so they always observe the current connection.
func (*RabbitMQConsumer) Consume ¶ added in v0.34.0
func (c *RabbitMQConsumer) Consume(ctx context.Context, handler MessageHandler) error
Consume starts consuming messages from the queue and blocks until the context is cancelled or Close is called.
Unlike the publisher (whose supervisor goroutine reconnects in the background), reconnection runs inline in this loop because in-flight handlers must be drained before the channel they ack on is replaced.
Unless cfg.DisableReconnect is set, a dropped connection or channel is re-established with exponential backoff and consumption resumes; Consume then only returns ctx.Err() on cancellation, ErrConsumerClosed after Close, or ErrReconnectFailed when cfg.ReconnectMaxAttempts is exceeded. With reconnection disabled, it returns ErrDeliveryChannelClosed when the broker connection drops (the previous behavior).
Messages are processed with cfg.ConsumerConcurrency parallel handlers (default 1, sequential). Consume waits for in-flight handlers to finish before returning. Only one Consume may be active per consumer; concurrent calls return ErrAlreadyConsuming.
The handler is called for each message; return nil to ACK, an error to trigger the retry ladder, or a Permanent()-wrapped error to send the message straight to the DLQ. If the context is cancelled while a message is being handled, the message is requeued without consuming a retry attempt.
type RabbitMQPublisher ¶
type RabbitMQPublisher struct {
// contains filtered or unexported fields
}
RabbitMQPublisher implements Publisher for RabbitMQ. All publish methods are safe for concurrent use.
func NewRabbitMQPublisher ¶
func NewRabbitMQPublisher(cfg config.RabbitMQConfig, opts ...PublisherOption) (*RabbitMQPublisher, error)
NewRabbitMQPublisher creates a new RabbitMQ publisher with exchange, retry queues, and DLQ.
The publisher is safe for concurrent use from multiple goroutines.
Unless cfg.DisableReconnect is set, the publisher automatically reconnects with exponential backoff when the connection or channel drops, re-declaring the topology on success. Publishes issued while disconnected fail fast with ErrPublishFailed; callers decide whether to retry. The initial connection is still fail-fast: if RabbitMQ is unreachable at startup an error is returned.
With cfg.PublisherConfirms enabled, every publish blocks until the broker confirms the message (bounded by ctx) and returns ErrPublishNotConfirmed on a broker NACK.
Retry flow: Consumers must explicitly call PublishToRetry() to route failed messages through the retry chain. The main queue's dead-letter config routes directly to DLQ for unhandled failures (e.g., message rejected without calling PublishToRetry).
If cfg.RetryDelays is empty, rejected messages route directly to the DLQ with no retry attempts.
func (*RabbitMQPublisher) Close ¶
func (p *RabbitMQPublisher) Close() error
Close closes the channel and connection and stops the reconnect supervisor. Safe to call concurrently with Publish methods - will wait for in-flight publishes to complete. Idempotent: subsequent calls return nil.
func (*RabbitMQPublisher) Connection ¶ added in v0.33.0
func (p *RabbitMQPublisher) Connection() *amqp.Connection
Connection returns the current AMQP connection for health checks. The returned pointer changes after a reconnect; health checks should use health.NewRabbitMQCheckerWithProvider(publisher.Connection) so they always observe the current connection.
func (*RabbitMQPublisher) DLQName ¶
func (p *RabbitMQPublisher) DLQName() string
DLQName returns the dead letter queue name
func (*RabbitMQPublisher) DLXName ¶
func (p *RabbitMQPublisher) DLXName() string
DLXName returns the dead letter exchange name
func (*RabbitMQPublisher) MaxRetries ¶
func (p *RabbitMQPublisher) MaxRetries() int
MaxRetries returns the number of retry queues (attempts before DLQ)
func (*RabbitMQPublisher) Publish ¶
func (p *RabbitMQPublisher) Publish(ctx context.Context, message interface{}) error
Publish sends a message to the main queue. The message CorrelationId is taken from the context (logger.GetCorrelationID) when present so messages can be traced back to the originating request; otherwise a new one is generated. It is preserved through retries.
func (*RabbitMQPublisher) PublishToDLQ ¶
func (p *RabbitMQPublisher) PublishToDLQ(ctx context.Context, body []byte, correlationId string) error
PublishToDLQ sends a message to the dead letter queue (permanent failure). The correlationId should be preserved from the original message for tracing.
func (*RabbitMQPublisher) PublishToRetry ¶
func (p *RabbitMQPublisher) PublishToRetry(ctx context.Context, retryIndex int, body []byte, correlationId string, headers amqp.Table) error
PublishToRetry sends a message to a specific retry queue by index. Returns error if retryIndex is out of bounds (should send to DLQ instead). The correlationId should be preserved from the original message for tracing. Headers is optional (can be nil) - used to track retry count.
When cfg.RetryJitter > 0, a per-message TTL shortened by up to that fraction is set so messages that failed together do not all return to the main queue at the same instant. The queue-level TTL remains the upper bound; per-message expiry only shortens the delay (RabbitMQ uses the lower of the two). Because RabbitMQ expires only the queue head, a message may still wait for messages ahead of it.
func (*RabbitMQPublisher) RetryQueues ¶
func (p *RabbitMQPublisher) RetryQueues() []string
RetryQueues returns a copy of the retry queue names for use by consumers