From 0b42c705916e1edca1a7a0d822b96a79fd371671 Mon Sep 17 00:00:00 2001 From: Cassio Roos Date: Mon, 22 Dec 2025 09:19:12 -0300 Subject: [PATCH 1/2] feat: add slog bridge --- pkg/events/rabbitmq/options.go | 49 +++++++++++++++++++++++ pkg/log/slog_bridge.go | 72 ++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 pkg/log/slog_bridge.go diff --git a/pkg/events/rabbitmq/options.go b/pkg/events/rabbitmq/options.go index 6e2eb62..c972543 100644 --- a/pkg/events/rabbitmq/options.go +++ b/pkg/events/rabbitmq/options.go @@ -7,6 +7,55 @@ import ( "github.com/gothunder/thunder/internal/events/rabbitmq" ) +func WithURL(url string) rabbitmq.RabbitmqConfigOption { + return func(c *rabbitmq.Config) { + c.URL = url + } +} + +func WithExchangeName(name string) rabbitmq.RabbitmqConfigOption { + return func(c *rabbitmq.Config) { + c.ExchangeName = name + } +} + +func WithQueueName(name string) rabbitmq.RabbitmqConfigOption { + return func(c *rabbitmq.Config) { + c.QueueName = name + } +} + +func WithConsumerName(name string) rabbitmq.RabbitmqConfigOption { + return func(c *rabbitmq.Config) { + c.ConsumerName = name + } +} + +// WithConsumerConcurrency sets the number of concurrent message handlers +func WithConsumerConcurrency(concurrency int) rabbitmq.RabbitmqConfigOption { + return func(c *rabbitmq.Config) { + c.ConsumerConcurrency = concurrency + } +} + +func WithPrefetchCount(prefetch int) rabbitmq.RabbitmqConfigOption { + return func(c *rabbitmq.Config) { + c.PrefetchCount = prefetch + } +} + +func WithMaxRetries(maxRetries int) rabbitmq.RabbitmqConfigOption { + return func(c *rabbitmq.Config) { + c.MaxRetries = maxRetries + } +} + +func WithDeleteDLX(deleteDLX bool) rabbitmq.RabbitmqConfigOption { + return func(c *rabbitmq.Config) { + c.DeleteDLX = deleteDLX + } +} + func WithQueueNamePosfix(posfix string) rabbitmq.RabbitmqConfigOption { return func(c *rabbitmq.Config) { c.QueueName = fmt.Sprintf("%s_%s", c.QueueName, posfix) diff --git a/pkg/log/slog_bridge.go b/pkg/log/slog_bridge.go new file mode 100644 index 0000000..d2afcc0 --- /dev/null +++ b/pkg/log/slog_bridge.go @@ -0,0 +1,72 @@ +package log + +import ( + "context" + "encoding/json" + "log/slog" + + "github.com/rs/zerolog" +) + +// slogWriter is an io.Writer that forwards zerolog JSON output to slog. +type slogWriter struct { + logger *slog.Logger +} + +// Write implements io.Writer. It parses zerolog's JSON output and logs to slog. +func (w *slogWriter) Write(p []byte) (n int, err error) { + var entry map[string]interface{} + if err := json.Unmarshal(p, &entry); err != nil { + // If parsing fails, log raw message + w.logger.Info(string(p)) + return len(p), nil + } + + // Extract and map level + level := slog.LevelInfo + if lvl, ok := entry["level"].(string); ok { + switch lvl { + case "debug", "trace": + level = slog.LevelDebug + case "info": + level = slog.LevelInfo + case "warn", "warning": + level = slog.LevelWarn + case "error", "fatal", "panic": + level = slog.LevelError + } + delete(entry, "level") + } + + // Extract message + msg := "" + if m, ok := entry["message"].(string); ok { + msg = m + delete(entry, "message") + } + + // Remove zerolog timestamp (slog adds its own) + delete(entry, "time") + + // Convert remaining fields to slog args + args := make([]any, 0, len(entry)*2) + for k, v := range entry { + args = append(args, k, v) + } + + w.logger.Log(context.Background(), level, msg, args...) + return len(p), nil +} + +// NewLoggerFromSlog creates a *zerolog.Logger that outputs to the given slog.Logger. +// Use this when your service uses slog but needs to pass a logger to Thunder. +// +// Example: +// +// logger := log.NewLoggerFromSlog(slog.Default()) +// consumer, _ := rabbitmq.NewRabbitMQConsumer(logger, ...) +func NewLoggerFromSlog(slogger *slog.Logger) *zerolog.Logger { + writer := &slogWriter{logger: slogger} + logger := zerolog.New(writer).With().Timestamp().Logger() + return &logger +} From 3739ace5113b2b2c61a4804d84ee62fd5d72a4c1 Mon Sep 17 00:00:00 2001 From: Cassio Roos Date: Mon, 22 Dec 2025 09:24:51 -0300 Subject: [PATCH 2/2] fix: remove comments --- pkg/events/rabbitmq/options.go | 1 - pkg/log/slog_bridge.go | 7 ------- 2 files changed, 8 deletions(-) diff --git a/pkg/events/rabbitmq/options.go b/pkg/events/rabbitmq/options.go index c972543..43243c8 100644 --- a/pkg/events/rabbitmq/options.go +++ b/pkg/events/rabbitmq/options.go @@ -31,7 +31,6 @@ func WithConsumerName(name string) rabbitmq.RabbitmqConfigOption { } } -// WithConsumerConcurrency sets the number of concurrent message handlers func WithConsumerConcurrency(concurrency int) rabbitmq.RabbitmqConfigOption { return func(c *rabbitmq.Config) { c.ConsumerConcurrency = concurrency diff --git a/pkg/log/slog_bridge.go b/pkg/log/slog_bridge.go index d2afcc0..e0768c9 100644 --- a/pkg/log/slog_bridge.go +++ b/pkg/log/slog_bridge.go @@ -8,21 +8,17 @@ import ( "github.com/rs/zerolog" ) -// slogWriter is an io.Writer that forwards zerolog JSON output to slog. type slogWriter struct { logger *slog.Logger } -// Write implements io.Writer. It parses zerolog's JSON output and logs to slog. func (w *slogWriter) Write(p []byte) (n int, err error) { var entry map[string]interface{} if err := json.Unmarshal(p, &entry); err != nil { - // If parsing fails, log raw message w.logger.Info(string(p)) return len(p), nil } - // Extract and map level level := slog.LevelInfo if lvl, ok := entry["level"].(string); ok { switch lvl { @@ -38,17 +34,14 @@ func (w *slogWriter) Write(p []byte) (n int, err error) { delete(entry, "level") } - // Extract message msg := "" if m, ok := entry["message"].(string); ok { msg = m delete(entry, "message") } - // Remove zerolog timestamp (slog adds its own) delete(entry, "time") - // Convert remaining fields to slog args args := make([]any, 0, len(entry)*2) for k, v := range entry { args = append(args, k, v)