7.2 KiB
Backend Handlers
All backend handlers implement slog.Handler and follow the Option{}.NewXxxHandler() constructor pattern.
Common Option Fields
Every handler's Option struct includes:
| Field | Purpose |
|---|---|
Level |
Minimum log level (default: slog.LevelDebug) |
AddSource |
Include source file/line in log output |
ReplaceAttr |
Callback to modify attributes before emission |
Converter |
Custom payload builder for the target format |
AttrFromContext |
Slice of functions extracting attributes from context.Context |
Cloud Backends
Datadog — slog-datadog
import slogdatadog "github.com/samber/slog-datadog/v2"
handler := slogdatadog.Option{
Level: slog.LevelInfo,
// Service, Source, Hostname, Tags configured via Datadog client
}.NewDatadogHandler()
defer handler.(interface{ Stop(context.Context) error }).Stop(context.Background()) // REQUIRED: flush buffered logs
Batch mode is the default — logs are buffered and sent periodically (default 5s). Call Stop(ctx) on shutdown or buffered logs are lost. The handler also exposes Flush(ctx) for mid-lifecycle flushes. For synchronous delivery, check the Option configuration.
Sentry — slog-sentry
import slogsentry "github.com/samber/slog-sentry/v2"
handler := slogsentry.Option{
Level: slog.LevelWarn,
Hub: sentry.CurrentHub(),
AddSource: true,
}.NewSentryHandler()
// Flush on shutdown
defer sentry.Flush(2 * time.Second)
Recognized attributes: error (any error type), request (*http.Request), dist, environment, release, server_name, transaction. Use slog.Group("tags", ...) for Sentry tags and slog.Group("user", ...) for user context.
Error keys: Global ErrorKeys = []string{"error", "err"} — attributes with these keys are treated as error objects.
Loki — slog-loki
import slogloki "github.com/samber/slog-loki/v3"
lokiClient, _ := loki.New(lokiCfg)
defer lokiClient.Stop() // REQUIRED: flush buffered logs
handler := slogloki.Option{
Level: slog.LevelDebug,
Client: lokiClient,
}.NewLokiHandler()
Labels vs metadata: By default, attributes are sent as Loki labels. For high-cardinality data (request IDs, trace IDs), enable HandleRecordsWithMetadata: true to send as structured metadata instead — this avoids label explosion that degrades Loki performance.
Graylog — slog-graylog
import sloggraylog "github.com/samber/slog-graylog/v2"
gelfWriter, _ := gelf.NewWriter("localhost:12201")
handler := sloggraylog.Option{
Level: slog.LevelDebug,
Writer: gelfWriter,
}.NewGraylogHandler()
Uses GELF (Graylog Extended Log Format) over UDP.
Messaging Backends
Kafka — slog-kafka
import slogkafka "github.com/samber/slog-kafka/v2"
writer := &kafka.Writer{
Addr: kafka.TCP("localhost:9092"),
Topic: "logs",
Async: true, // non-blocking writes
}
handler := slogkafka.Option{
Level: slog.LevelDebug,
KafkaWriter: writer,
Timeout: 60 * time.Second,
}.NewKafkaHandler()
defer writer.Close() // REQUIRED: flush pending messages
Fluentd — slog-fluentd
import slogfluentd "github.com/samber/slog-fluentd/v2"
client, _ := fluent.New(fluent.Config{
FluentHost: "localhost", FluentPort: 24224,
})
handler := slogfluentd.Option{
Level: slog.LevelDebug,
Client: client,
Tag: "api",
}.NewFluentdHandler()
defer client.Close()
Logstash — slog-logstash
import sloglogstash "github.com/samber/slog-logstash/v2"
conn, _ := net.Dial("tcp", "localhost:9999")
handler := sloglogstash.Option{
Level: slog.LevelDebug,
Conn: conn,
}.NewLogstashHandler()
defer conn.Close()
Output format: JSON with @timestamp, level, message, error, extra fields.
Notification Backends
Slack — slog-slack
import slogslack "github.com/samber/slog-slack/v2"
// Via webhook
handler := slogslack.Option{
Level: slog.LevelError,
WebhookURL: "https://hooks.slack.com/services/...",
Channel: "alerts",
}.NewSlackHandler()
// Via bot token
handler := slogslack.Option{
Level: slog.LevelError,
BotToken: "xoxb-...",
Channel: "alerts",
}.NewSlackHandler()
Telegram — slog-telegram
import slogtelegram "github.com/samber/slog-telegram/v2"
handler := slogtelegram.Option{
Level: slog.LevelError,
Token: "your-bot-token",
Username: "@your-channel",
}.NewTelegramHandler()
Webhook — slog-webhook
import slogwebhook "github.com/samber/slog-webhook/v2"
handler := slogwebhook.Option{
Level: slog.LevelError,
Endpoint: "https://webhook.site/your-id",
Timeout: 10 * time.Second,
}.NewWebhookHandler()
Storage Backends
Parquet — slog-parquet
import slogparquet "github.com/samber/slog-parquet/v2"
buffer := slogparquet.NewParquetBuffer(bucket, "logs/", 10000, 5*time.Minute)
defer buffer.Flush(true) // REQUIRED: flush remaining records synchronously
handler := slogparquet.Option{
Level: slog.LevelDebug,
Buffer: buffer,
}.NewParquetHandler()
Uses Thanos objstore.Bucket for cloud storage (S3, GCS, Azure). Records are buffered and written as Parquet files when either maxRecords or maxInterval is reached.
Logging Bridges
Bridge the slog.Handler interface to legacy logging frameworks. Use during incremental migration from Zap/Zerolog/Logrus to slog.
slog-zap
import slogzap "github.com/samber/slog-zap/v2"
zapLogger, _ := zap.NewProduction()
handler := slogzap.Option{
Level: slog.LevelDebug,
Logger: zapLogger,
}.NewZapHandler()
slog.SetDefault(slog.New(handler))
// Now all slog.Info() calls route through Zap
slog-zerolog
import slogzerolog "github.com/samber/slog-zerolog/v2"
zerologLogger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr})
handler := slogzerolog.Option{
Level: slog.LevelDebug,
Logger: &zerologLogger,
}.NewZerologHandler()
slog-logrus
import sloglogrus "github.com/samber/slog-logrus/v2"
handler := sloglogrus.Option{
Level: slog.LevelDebug,
Logger: logrus.StandardLogger(),
}.NewLogrusHandler()
Graceful Shutdown Checklist
Handlers that buffer records internally and MUST be closed on shutdown:
| Handler | Shutdown method | What happens without it |
|---|---|---|
slog-datadog |
handler.Stop(ctx) |
Buffered logs lost (default 5s batch) |
slog-loki |
lokiClient.Stop() |
Pending push requests dropped |
slog-kafka |
writer.Close() |
Pending messages never sent |
slog-parquet |
buffer.Flush(true) |
Partial Parquet file not flushed to storage |
For non-batched handlers (Sentry, Slack, Telegram, Webhook), logs are sent synchronously — no close required, but sentry.Flush(timeout) is recommended.
// Production shutdown pattern
func main() {
lokiClient, _ := loki.New(lokiCfg)
defer lokiClient.Stop() // flush buffered logs
lokiHandler := slogloki.Option{
Level: slog.LevelDebug, Client: lokiClient,
}.NewLokiHandler()
// Use signal handling for graceful shutdown
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
// ... start server ...
<-ctx.Done()
// deferred Stop() runs here, flushing buffered logs
}