slogjournal

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Nov 29, 2025 License: MIT Imports: 7 Imported by: 0

README ¶

slog: systemd journal handler

tag Go Version GoDoc Build Status Go report Coverage Contributors License

A systemd journal Handler for slog Go library.

🚀 Install

go get github.com/tschaefer/slog-journal

Compatibility: go >= 1.23

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

💡 Usage

GoDoc: https://pkg.go.dev/github.com/tschaefer/slog-journal

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

	// optional: customize journal 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
}

Attributes will be injected in journal entry fields. Fields must be composed of uppercase letters, numbers, and underscores, but must not start with an underscore. Within these restrictions, any arbitrary field name may be used. Some names have special significance: see the journalctl documentation for more details. The converter will skip invalid attribute key names and transform to upper case. Additionally the fields will be prefixed with SLOG_.

Other global parameters:

slogjournal.SourceKey = "source"
slogjournal.ErrorKeys = []string{"error", "err"}
slogjournal.FieldPrefix string
slogjournal.LogLevelToPriority = map[string]journal.Priority{
	"DEBUG": journal.PriDebug,
	"INFO":  journal.PriInfo,
	"WARN":  journal.PriWarning,
	"ERROR": journal.PriErr,
}

Use slogjournal.FieldPrefix to customize the fields prefix.

Example

For further examples view the tests: slogjournal_test.go

import (
	"fmt"
	"log"
	"log/slog"
	"time"

	slogjournal "github.com/tschaefer/slog-journal"
)

func main() {
	logger := slog.New(slogjournal.Option{Level: slog.LevelDebug}.NewJournalHandler())
	logger = logger.
		With("environment", "dev").
		With("release", "v1.0.0")

	logger.
		With("category", "sql").
		With("query_statement", "SELECT COUNT(*) FROM users;").
		With("query_duration", 1*time.Second).
		With("error", fmt.Errorf("could not count users")).
		Error("caramba!")

	logger.
		With(
			slog.Group("user",
				slog.String("id", "user-123"),
				slog.Time("created_at", time.Now()),
			),
		).
		Info("user registration")
}

Output:

journaltl --output json-pretty --lines 2 --no-pager SLOG_LOGGER=tschaefer/slog-journal:v0.1.0
{
  "_PID": "919303",
  "SLOG_QUERY_DURATION": "1s",
  "_BOOT_ID": "100da27bd8b94096b5c80cdac34d6063",
  "_SELINUX_CONTEXT": "unconfined\n",
  "_SYSTEMD_OWNER_UID": "1000",
  "_CAP_EFFECTIVE": "0",
  "_TRANSPORT": "journal",
  "SLOG_ERROR_ERROR": "could not count users",
  "_CMDLINE": "/tmp/go-build3177835552/b001/slog-journal.test -test.testlogfile=/tmp/go-build3177835552/b001/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true",
  "SLOG_CATEGORY": "sql",
  "SLOG_ENVIRONMENT": "dev",
  "PRIORITY": "3",
  "_SYSTEMD_SLICE": "user-1000.slice",
  "__SEQNUM": "6791157",
  "_AUDIT_LOGINUID": "1000",
  "SLOG_QUERY_STATEMENT": "SELECT COUNT(*) FROM users;",
  "_SOURCE_REALTIME_TIMESTAMP": "1763322710754233",
  "SLOG_ERROR_KIND": "*errors.errorString",
  "_GID": "100",
  "_UID": "1000",
  "_AUDIT_SESSION": "1",
  "_SYSTEMD_UNIT": "session-1.scope",
  "MESSAGE": "caramba!",
  "_SYSTEMD_SESSION": "1",
  "SLOG_ERROR_STACK": "<nil>",
  "__SEQNUM_ID": "b3c7821dbfce47a59b06797aea9028ca",
  "SLOG_RELEASE": "v1.0.0",
  "_SYSTEMD_INVOCATION_ID": "021760b3373342b98aaeabf9d12d8d74",
  "__REALTIME_TIMESTAMP": "1763322710754273",
  "_RUNTIME_SCOPE": "system",
  "__CURSOR": "s=b3c7821dbfce47a59b06797aea9028ca;i=679ff5;b=100da27bd8b94096b5c80cdac34d6063;m=6c1d75d2e6;t=643bb8fcc83e1;x=ec6dbb930fa4a0cd",
  "_HOSTNAME": "bullseye",
  "_SYSTEMD_CGROUP": "/user.slice/user-1000.slice/session-1.scope",
  "_EXE": "/tmp/go-build3177835552/b001/slog-journal.test",
  "_MACHINE_ID": "75b649379b874beea04d95463e59c3a1",
  "__MONOTONIC_TIMESTAMP": "464350728934",
  "_SYSTEMD_USER_SLICE": "-.slice",
  "SLOG_LOGGER": "tschaefer/slog-journal:v0.1.0",
  "_COMM": "slog-journal.te"
}
{
  "_SYSTEMD_CGROUP": "/user.slice/user-1000.slice/session-1.scope",
  "SLOG_ENVIRONMENT": "dev",
  "_SYSTEMD_SESSION": "1",
  "_RUNTIME_SCOPE": "system",
  "_COMM": "slog-journal.te",
  "_SYSTEMD_SLICE": "user-1000.slice",
  "_TRANSPORT": "journal",
  "_SYSTEMD_UNIT": "session-1.scope",
  "_CMDLINE": "/tmp/go-build3177835552/b001/slog-journal.test -test.testlogfile=/tmp/go-build3177835552/b001/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true",
  "__SEQNUM_ID": "b3c7821dbfce47a59b06797aea9028ca",
  "_SYSTEMD_OWNER_UID": "1000",
  "_UID": "1000",
  "SLOG_USER_ID": "user-123",
  "__MONOTONIC_TIMESTAMP": "464350730219",
  "SLOG_USER_CREATED_AT": "2025-11-16 20:51:50.753821198 +0100 CET",
  "_SELINUX_CONTEXT": "unconfined\n",
  "_BOOT_ID": "100da27bd8b94096b5c80cdac34d6063",
  "SLOG_RELEASE": "v1.0.0",
  "_SOURCE_REALTIME_TIMESTAMP": "1763322710754397",
  "_SYSTEMD_USER_SLICE": "-.slice",
  "_SYSTEMD_INVOCATION_ID": "021760b3373342b98aaeabf9d12d8d74",
  "_GID": "100",
  "_CAP_EFFECTIVE": "0",
  "_HOSTNAME": "bullseye",
  "SLOG_LOGGER": "tschaefer/slog-journal:v0.1.0",
  "_PID": "919303",
  "MESSAGE": "user registration",
  "_AUDIT_LOGINUID": "1000",
  "__REALTIME_TIMESTAMP": "1763322710755560",
  "_AUDIT_SESSION": "1",
  "__CURSOR": "s=b3c7821dbfce47a59b06797aea9028ca;i=679ff6;b=100da27bd8b94096b5c80cdac34d6063;m=6c1d75d7eb;t=643bb8fcc88e8;x=771c136fc02cf1ea",
  "_MACHINE_ID": "75b649379b874beea04d95463e59c3a1",
  "_EXE": "/tmp/go-build3177835552/b001/slog-journal.test",
  "__SEQNUM": "6791158",
  "PRIORITY": "6"
}

Documentation ¶

Index ¶

Constants ¶

This section is empty.

Variables ¶

View Source
var ErrorKeys = []string{"error", "err"}
View Source
var FieldPrefix string
View Source
var LogLevelToPriority = map[string]journal.Priority{
	"DEBUG": journal.PriDebug,
	"INFO":  journal.PriInfo,
	"WARN":  journal.PriWarning,
	"ERROR": journal.PriErr,
}
View Source
var SourceKey = "source"

Functions ¶

func DefaultConverter ¶

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

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) (string, journal.Priority, map[string]string)

type JournalHandler ¶

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

func (*JournalHandler) Enabled ¶

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

func (*JournalHandler) Handle ¶

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

func (*JournalHandler) WithAttrs ¶

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

func (*JournalHandler) WithGroup ¶

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

type Option ¶

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

	// optional: customize journal 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
}

func (Option) NewJournalHandler ¶

func (o Option) NewJournalHandler() slog.Handler

Jump to

Keyboard shortcuts

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