slogseq

package module
v0.8.1 Latest Latest
Warning

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

Go to latest
Published: Jun 17, 2026 License: MIT Imports: 16 Imported by: 0

README

[!NOTE] This is a fork of sokkalf/slog-seq with my changes and patches.
Feel free to use it, or the upstream, whichever suits your use case best.
It is maintained for my personal needs and not to compete with upstream.

Release Go Version Go Reference Go Report License
Codecov Lint Tests

slog-seq

slog-seq is a library for sending logs to a Seq server, as a handler for Go's structured logging slog.

Installation

go get github.com/desertwitch/slog-seq

For OpenTelemetry trace correlation and span forwarding:

go get github.com/desertwitch/slog-seq/seqotel

Quick Start

Handlers can be constructed through NewSeqHandler.

Once all work is done, handler.Close() must be called on the returned handler.

import (
	slogseq "github.com/desertwitch/slog-seq"
)

handler := slogseq.NewSeqHandler("http://your-seq-server/ingest/clef",
	slogseq.WithAPIKey("your-api-key"),
	slogseq.WithBatchSize(50),
	slogseq.WithFlushInterval(2*time.Second),
)
defer handler.Close()

slog.SetDefault(slog.New(handler))
slog.Info("Hello, world!")

You can also define your own slog.HandlerOptions struct:

opts := &slog.HandlerOptions{
	Level:     slog.LevelInfo,  // Minimum log level
	AddSource: true,            // Show source file, line and function in log
	ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
		if a.Key == "password" {
			// Mask passwords
			a.Value = slog.StringValue("*****")
		}
		if a.Key == slog.SourceKey {
			// Replace the full path with just the file name
			s := a.Value.Any().(*slog.Source)
			s.File = filepath.Base(s.File)
		}
		return a
	},
}

and pass it to the NewSeqHandler function with slogseq.WithHandlerOptions(opts).

See the package documentation for all available options.

HTTP Client

If you need to disable TLS certificate verification, you can do so by using the option slogseq.WithInsecure().

Alternatively, you can provide your own HTTP client by using the option slogseq.WithHTTPClient(client).

Multiple Workers

You can set the number of workers that send logs to the Seq server by using the option slogseq.WithWorkers(n).

This can be useful if you have a high enough volume of logs to cause dropped messages.

OpenTelemetry

The seqotel sub-package provides OpenTelemetry trace correlation and span forwarding. It has its own Go module, so the dependency is only pulled into your project if you use it.

seqotel.NewSeqOTelHandler works like slogseq.NewSeqHandler but automatically enriches log events with trace and span IDs from the active span context, if one is provided.

seqotel.NewLoggingSpanProcessor constructs a seqotel.LoggingSpanProcessor implementing trace.SpanProcessor and trace.SpanExporter to forward completed spans to Seq as CLEF events.

import (
	slogseq "github.com/desertwitch/slog-seq"
	"github.com/desertwitch/slog-seq/seqotel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/sdk/trace"
	tr "go.opentelemetry.io/otel/trace"
)

handler := seqotel.NewSeqOTelHandler("http://your-seq-server/ingest/clef",
	slogseq.WithAPIKey("your-api-key"),
	slogseq.WithBatchSize(50),
	slogseq.WithFlushInterval(2*time.Second),
)
defer handler.Close()

slog.SetDefault(slog.New(handler))

processor := seqotel.NewLoggingSpanProcessor(handler)
tp := trace.NewTracerProvider(
	trace.WithSpanProcessor(processor),
	trace.WithSampler(trace.AlwaysSample()),
)

tracer := tp.Tracer("example-tracer")

ctx, span := tracer.Start(context.Background(), "operation")
span.AddEvent("Starting work")
time.Sleep(500 * time.Millisecond)

slog.InfoContext(ctx, "This is a span log message", "key", "value")

_, subSpan := tracer.Start(ctx, "sub operation")
subSpan.AddEvent("Sub operation started")
time.Sleep(500 * time.Millisecond)
subSpan.AddEvent("Sub operation completed",
	tr.WithAttributes(attribute.String("key", "value")),
)
subSpan.End()

span.AddEvent("Work done")
slog.InfoContext(ctx, "All done!")
span.End()

Seq with traces

See the package documentation for more information.

Custom Integrations

SeqHandler.HandleCLEFEvent accepts pre-built CLEF events directly, bypassing slog entirely. This lets you use the handler's batching, retry, and worker machinery from any event source - syslog forwarders, custom protocols, queue consumers, or anything that can produce a CLEFEvent.

import (
	slogseq "github.com/desertwitch/slog-seq"
)

handler := slogseq.NewSeqHandler("http://your-seq-server/ingest/clef",
    slogseq.WithAPIKey("your-api-key"),
    slogseq.WithBatchSize(50),
)
defer handler.Close()

handler.HandleCLEFEvent(slogseq.CLEFEvent{
    Timestamp: time.Now(),
    Message:   "forwarded from syslog",
    Level:     slogseq.CLEFLevelInformation,
    Properties: map[string]any{
        "facility": "kern",
        "hostname": "router-01",
    },
})

Benchmarks

Benchmarks measure the hot path (log call through channel send).
HTTP delivery is asynchronous and batched, so it does not block the caller.

Measured using: Go 1.26, Intel Core i5-12600K, 8-core VM.

Benchmark ns/op B/op allocs/op
Handle 90 162 2
Handle (parallel) 189 447 6
Handle + WithAttrs 386 775 12
Handle + WithGroups 447 1021 10
Handle + AddSource 426 996 9
Handle + ReplaceAttr 348 720 10
HandleCLEFEvent1 30 35 0

1: Raw event dispatch after Handle preprocessing or directly by OTel.

Run make benchmark for full results.

License

MIT

Documentation

Overview

Package slogseq provides an slog.Handler that sends structured log events to a Seq server using the CLEF (Compact Log Event Format) protocol.

Events are batched and delivered asynchronously over HTTP, so logging calls do not block on network I/O. Multiple workers can be configured for high-throughput workloads.

For OpenTelemetry trace correlation and span forwarding, see the seqotel sub-package.

Use NewSeqHandler to create a handler:

handler := slogseq.NewSeqHandler("http://seq:5341/ingest/clef",
	slogseq.WithAPIKey("your-api-key"),
	slogseq.WithBatchSize(50),
)
defer handler.Close()
slog.SetDefault(slog.New(handler))

Index

Constants

View Source
const (
	CLEFLevelDebug       string = "Debug"
	CLEFLevelVerbose     string = "Verbose"
	CLEFLevelInformation string = "Information"
	CLEFLevelWarning     string = "Warning"
	CLEFLevelError       string = "Error"
	CLEFLevelFatal       string = "Fatal"
)

CLEF does not enforce a strict set of recognized log levels, but these are the commonly used log levels which seem to offer the best Seq UI experience.

Variables

This section is empty.

Functions

This section is empty.

Types

type CLEFEvent

type CLEFEvent struct {
	// Timestamp is the time the event occurred.
	Timestamp time.Time // required

	// Message is the log message.
	//
	// For multi-line slog messages, this contains only the first line; the
	// remainder goes into [CLEFEvent.Exception].
	Message string // optional

	// Exception holds supplementary detail such as a stack trace or the
	// continuation of a multi-line [CLEFEvent.Message].
	Exception string // optional

	// Level describes the severity of the event (use CLEFLevel* constants).
	Level string // optional

	// Properties holds structured key-value data attached to the event. These
	// are serialized as top-level CLEF properties. Nesting is represented by
	// sub-maps; a key must not be both a leaf value and a parent of other keys.
	//
	// As opposed to [CLEFEvent.ResourceAttributes], dotted keys ("a.b.c": val)
	// are not converted to nested maps but treated as literal top-level keys.
	Properties map[string]any // optional

	// TraceID is a telemetry trace identifier.
	TraceID string // optional

	// SpanID is a telemetry span identifier.
	SpanID string // optional

	// SpanStart is the start time of the telemetry span.
	SpanStart time.Time // optional

	// SpanKind is the telemetry span kind.
	SpanKind string // optional

	// ResourceAttributes holds telemetry resource attributes. Values can be a
	// flat map with dotted keys ("a.b.c": val) or an already-nested map ("a":
	// {"b": {"c": val}}). Dotted keys, as common with telemetry frameworks, are
	// converted to nested maps internally.
	//
	// A key must not be both a leaf value and a parent of other keys (e.g.
	// "a.b" cannot hold a value if "a.b.c" also exists in dotted form, or "b"
	// cannot be a scalar if it also contains a sub-map in pre-nested form).
	ResourceAttributes map[string]any // optional

	// ParentSpanID is the identifier of the parent telemetry span.
	ParentSpanID string // optional
}

CLEFEvent represents a log event in CLEF (Compact Log Event Format), the JSON-based format used by Seq, with Seq-specific extensions. See https://clef-json.org and https://datalust.co/docs/posting-raw-events.

CLEFEvent implements json.Marshaler to produce valid CLEF JSON, including escaping user properties that start with @.

func (CLEFEvent) MarshalJSON added in v0.8.1

func (e CLEFEvent) MarshalJSON() ([]byte, error)

MarshalJSON produces a valid CLEF JSON object, spreading [CLEFEvent.Properties] as top-level keys and escaping any user properties that start with @.

type SeqHandler

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

SeqHandler is an slog.Handler that sends structured log events to a Seq server using the CLEF (Compact Log Event Format) protocol. It supports batching, multiple workers, and asynchronous HTTP delivery.

Create one using NewSeqHandler. Do not construct directly.

func NewLogger

func NewLogger(seqURL string, opts ...SeqOption) (*slog.Logger, *SeqHandler)

NewLogger is a convenience function that creates a SeqHandler and wraps it in an slog.Logger. Refer to NewSeqHandler for teardown information.

func NewSeqHandler

func NewSeqHandler(seqURL string, opts ...SeqOption) *SeqHandler

NewSeqHandler creates and starts a new SeqHandler. seqURL is the URL of the Seq server's CLEF ingestion endpoint. Derived handlers (via WithAttrs and WithGroup) share the same workers and connection. Close must be called on the original handler when no longer needed, rendering all (sub-)handlers unusable.

See package documentation for all possible SeqOption.

func (*SeqHandler) Close

func (h *SeqHandler) Close() error

Close shuts down the handler, draining all pending events and waiting for workers to finish. It is safe to call multiple times. Logging after Close will silently drop events. Note that this method will immediately render all derived handlers (WithGroup/WithAttrs) unusable as well, so should only be called once - usually on the shallowest handler on program teardown.

May block while pending events are flushed and HTTP requests complete.

func (*SeqHandler) Enabled

func (h *SeqHandler) Enabled(_ context.Context, l slog.Level) bool

Enabled reports whether the handler is configured to log at the given level.

func (*SeqHandler) Events

func (h *SeqHandler) Events(workerIndex int) <-chan CLEFEvent

Events returns the event channel for the given worker index.

Intended only for use in tests to inspect dispatched events.

func (*SeqHandler) Handle

func (h *SeqHandler) Handle(ctx context.Context, r slog.Record) error

Handle processes a log record, converting it to a CLEF event and dispatching it to a worker for asynchronous delivery to Seq.

func (*SeqHandler) HandleCLEFEvent

func (h *SeqHandler) HandleCLEFEvent(event CLEFEvent)

HandleCLEFEvent dispatches a pre-built CLEF event to a worker for asynchronous delivery to Seq. The caller must not mutate the event or its contents after calling this method.

func (*SeqHandler) Ping added in v0.8.0

func (h *SeqHandler) Ping() error

Ping checks whether the Seq server is reachable and in service by calling its /health endpoint. Uses the handler's configured HTTP client.

Intended for startup checks before setting a constructed handler as default.

func (*SeqHandler) WithAttrs

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

WithAttrs returns a new handler with the given attributes added to every subsequent log event. The returned handler shares the same workers and connection as the original.

func (*SeqHandler) WithGroup

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

WithGroup returns a new handler that nests all subsequent attributes and record attributes under the given group name. The returned handler shares the same workers and connection as the original.

type SeqOption

type SeqOption interface {
	// contains filtered or unexported methods
}

SeqOption is an option to configure a Seq handler.

func WithAPIKey

func WithAPIKey(apiKey string) SeqOption

WithAPIKey sets the API key for the Seq server.

If unset, no key is sent to the server (no authentication).

func WithBatchSize

func WithBatchSize(batchSize int) SeqOption

WithBatchSize sets the number of events to batch before sending to Seq.

If unset, or less than 1, the default is 50.

func WithBlocking

func WithBlocking() SeqOption

WithBlocking causes the handler to block when the worker channel (see WithBufferSize) is full, waiting until space becomes available or the handler is closed.

By default, the handler is non-blocking and silently drops events when the channel is full.

func WithBufferSize

func WithBufferSize(n int) SeqOption

WithBufferSize sets the event channel capacity per worker. In non-blocking mode, events are dropped when the buffer is full. In blocking mode, Handle blocks until space is available (see WithBlocking).

If unset, or less than 1, the default is 1000.

func WithErrorHandlerFunc

func WithErrorHandlerFunc(fn func(error)) SeqOption

WithErrorHandlerFunc sets a callback that is invoked when the handler encounters an error sending events to Seq.

If unset, the default is a no-op.

func WithEventEnricher

func WithEventEnricher(fn func(context.Context, *CLEFEvent)) SeqOption

WithEventEnricher adds a function that enriches each CLEF event with additional context before dispatch. Called during Handle with the log record's context and event pointer. Multiple enrichers run in the order they were added. Use this for custom integrations (such as tracing).

If unset, the default is a no-op.

func WithFlushInterval

func WithFlushInterval(flushInterval time.Duration) SeqOption

WithFlushInterval sets the interval at which to flush the batch, even if the batch size has not been reached.

If unset, or less than zero, the default is 2 seconds. If zero, periodic flushing is disabled (and only WithBatchSize observed).

func WithGlobalAttrs

func WithGlobalAttrs(attrs ...slog.Attr) SeqOption

WithGlobalAttrs sets slog.Attr that are included in every log event emitted by this handler. slog.LogValuer values are resolved eagerly at option time.

func WithHTTPClient

func WithHTTPClient(client *http.Client) SeqOption

WithHTTPClient sets the HTTP client used for sending events to Seq.

If unset, a default client is created with sensible timeouts (30s).

func WithHandlerOptions

func WithHandlerOptions(opts *slog.HandlerOptions) SeqOption

WithHandlerOptions sets slog.HandlerOptions on the handler.

func WithInsecure

func WithInsecure() SeqOption

WithInsecure disables TLS certificate verification. Has no effect if WithHTTPClient is also set, the custom client controls its own configuration.

If unset, the default is to allow only valid certificates.

func WithNoFlush

func WithNoFlush() SeqOption

WithNoFlush disables flushing (workers exit immediately).

Intended only for use in tests to inspect dispatched events.

func WithRetryBufferSize

func WithRetryBufferSize(n int) SeqOption

WithRetryBufferSize sets the maximum number of failed events held for retry per worker. When full, the oldest unsent events (at this point usually retried several times) are dropped.

If unset, or less than 1, the default is 1000.

func WithSourceKey

func WithSourceKey(key string) SeqOption

WithSourceKey sets the slog.SourceKey used for source location information when AddSource is enabled in handler's given slog.HandlerOptions options.

If unset, the default is "source".

func WithWorkers

func WithWorkers(count int) SeqOption

WithWorkers sets the number of background workers that send events to Seq. Consider increasing this if you have a very high volume of events.

If unset, or less than 1, the default is 1.

Directories

Path Synopsis
seqotel module

Jump to

Keyboard shortcuts

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