slogloki

package module
v3.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 1, 2025 License: MIT Imports: 9 Imported by: 8

README

slog: Loki handler

tag Go Version GoDoc Build Status Go report Coverage Contributors License

A Loki Handler for slog Go library.

See also:

HTTP middlewares:

Loggers:

Log sinks:

🚀 Install

go get github.com/samber/slog-loki/v3

Compatibility: go >= 1.21

No breaking changes will be made to exported APIs before v4.0.0.

💡 Usage

GoDoc: https://pkg.go.dev/github.com/samber/slog-loki/v3

Handler options
type Option struct {
	// log level (default: debug)
	Level slog.Leveler

	// loki
	Client *loki.Client

	// optional: customize webhook event builder
	Converter Converter
	// optional: fetch attributes from context
	AttrFromContext []func(ctx context.Context) []slog.Attr

	// optional: see slog.HandlerOptions
	AddSource   bool
	ReplaceAttr func(groups []string, a slog.Attr) slog.Attr
	
	// By default, LokiHandler.Handle sends record attributes as labels to Loki.
	// When set to true, this handler sends record attributes as structured metadata.
	// 
	// Combine with RemoveAttrsConverter to avoid sending attributes as labels.
	HandleRecordsWithMetadata bool
}

Attributes will be injected in log payload.

Other global parameters:

slogloki.SourceKey = "source"
slogloki.ErrorKeys = []string{"error", "err"}
slogloki.SubAttributeSeparator = "__"
slogloki.AttributeKeyInvalidCharReplacement = "_"
Example

Default behaviour (all attributes are treated as labels):

import (
	"github.com/grafana/loki-client-go/loki"
	slogloki "github.com/samber/slog-loki/v3"
	"log/slog"
)

func main() {
	// setup loki client
	config, _ := loki.NewDefaultConfig("http://localhost:3100/loki/api/v1/push")
	config.TenantID = "xyz"
	client, _ := loki.New(config)

	logger := slog.New(slogloki.Option{Level: slog.LevelDebug, Client: client}.NewLokiHandler())
	logger = logger.
		With("environment", "dev").
		With("release", "v1.0.0")

	// log error
	logger.Error("caramba!")

	// log user signup
	logger.Info("user registration")

	// stop loki client and purge buffers
	client.Stop()
}

To send record attributes as structured metadata (instead of labels), use the HandleRecordsWithMetadata option along with the RemoveAttrsConverter converter:

import (
	"github.com/grafana/loki-client-go/loki"
	slogloki "github.com/samber/slog-loki/v3"
	"log/slog"
)

func main() {
	// setup loki client
	config, _ := loki.NewDefaultConfig("http://localhost:3100/loki/api/v1/push")
	config.TenantID = "xyz"
	client, _ := loki.New(config)

	// With slogloki.RemoveAttrsConverter and HandleRecordsWithMetadata enabled, attributes are not sent as labels, thus
	// allowing to log high-cardinality metadata without impacting performance.
	o := slogloki.Option{
		HandleRecordsWithMetadata: true,
		Converter:                 slogloki.RemoveAttrsConverter,
		Level:                     slog.LevelDebug,
		Client:                    client,
	}
	logger := slog.New(o.NewLokiHandler())

	// Attributes added via WithAttrs are always sent as labels to Loki.
	logger = logger.With("release", "v1.0.0")
	// This will send the "span_id", a high cardinality value, as structured metadata, not as a label.
	//
	// More about structured metadata in Loki:
	// https://grafana.com/docs/loki/latest/get-started/labels/structured-metadata/
	logger.Error("A message with structured metadata", slog.String("span_id", "1234567"))

	client.Stop()
}

Note: Attributes added via WithAttrs are always sent as labels to Loki.

Tracing

Import the samber/slog-otel library.

import (
	slogloki "github.com/samber/slog-loki"
	slogotel "github.com/samber/slog-otel"
	"go.opentelemetry.io/otel/sdk/trace"
)

func main() {
	tp := trace.NewTracerProvider(
		trace.WithSampler(trace.AlwaysSample()),
	)
	tracer := tp.Tracer("hello/world")

	ctx, span := tracer.Start(context.Background(), "foo")
	defer span.End()

	span.AddEvent("bar")

	logger := slog.New(
		slogloki.Option{
			// ...
			AttrFromContext: []func(ctx context.Context) []slog.Attr{
				slogotel.ExtractOtelAttrFromContext([]string{"tracing"}, "trace_id", "span_id"),
			},
		}.NewLokiHandler(),
	)

	logger.ErrorContext(ctx, "a message")
}

🤝 Contributing

Don't hesitate ;)

# Install some dev dependencies
make tools

# Run tests
make test
# or
make watch-test

👤 Contributors

Contributors

💫 Show your support

Give a ⭐️ if this project helped you!

GitHub Sponsors

📝 License

Copyright © 2023 Samuel Berthe.

This project is MIT licensed.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var AttributeKeyInvalidCharReplacement = "_"
View Source
var ErrorKeys = []string{"error", "err"}
View Source
var SourceKey = "source"
View Source
var SubAttributeSeparator = "__"

See:

Functions

func DefaultConverter

func DefaultConverter(addSource bool, replaceAttr func(groups []string, a slog.Attr) slog.Attr, loggerAttr []slog.Attr, groups []string, record *slog.Record) model.LabelSet

func RemoveAttrsConverter added in v3.6.0

func RemoveAttrsConverter(addSource bool, replaceAttr func(groups []string, a slog.Attr) slog.Attr, loggerAttr []slog.Attr, groups []string, record *slog.Record) model.LabelSet

This converter removes all record attributes, sending only the fixed logger attributes as labels.

Use this converter along with Option.HandleRecordsWithMetadata set to true to send record attributes as structured metadata.

See https://grafana.com/docs/loki/latest/get-started/labels/structured-metadata/ to know more about structured metadata in Loki.

Types

type Converter

type Converter func(addSource bool, replaceAttr func(groups []string, a slog.Attr) slog.Attr, loggerAttr []slog.Attr, groups []string, record *slog.Record) model.LabelSet

type LokiHandler

type LokiHandler struct {
	// contains filtered or unexported fields
}

func (*LokiHandler) Enabled

func (h *LokiHandler) Enabled(_ context.Context, level slog.Level) bool

func (*LokiHandler) Handle

func (h *LokiHandler) Handle(ctx context.Context, record slog.Record) error

func (*LokiHandler) WithAttrs

func (h *LokiHandler) WithAttrs(attrs []slog.Attr) slog.Handler

func (*LokiHandler) WithGroup

func (h *LokiHandler) WithGroup(name string) slog.Handler

type Option

type Option struct {
	// log level (default: debug)
	Level slog.Leveler

	// loki
	Client *loki.Client

	// optional: customize webhook event builder
	Converter Converter
	// optional: fetch attributes from context
	AttrFromContext []func(ctx context.Context) []slog.Attr

	// optional: see slog.HandlerOptions
	AddSource   bool
	ReplaceAttr func(groups []string, a slog.Attr) slog.Attr

	// By default, LokiHandler.Handle sends record attributes as labels to Loki.
	// When set to true, this handler sends record attributes as structured metadata.
	//
	// Combine with RemoveAttrsConverter to avoid sending attributes as labels.
	//
	// Learn more about structured metadata in Loki:
	// https://grafana.com/docs/loki/latest/get-started/labels/structured-metadata/
	HandleRecordsWithMetadata bool
}

func (Option) NewLokiHandler

func (o Option) NewLokiHandler() slog.Handler

Creating a Loki client at each `NewLokiHandler` call may lead to connection leak when chaining many operations: `logger.With(...).With(...).With(...).With(...)`

Directories

Path Synopsis
example
default command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL