slogutil

package
v0.31.0 Latest Latest
Warning

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

Go to latest
Published: Dec 9, 2024 License: Unlicense Imports: 20 Imported by: 17

Documentation

Overview

Package slogutil contains extensions and utilities for package log/slog from the standard library.

Index

Examples

Constants

View Source
const (
	KeyError  = "err"
	KeyPrefix = "prefix"

	KeyMessage = slog.MessageKey
	KeySource  = slog.SourceKey
	KeyTime    = slog.TimeKey
	KeyLevel   = slog.LevelKey
)

Additional or renamed key constants.

View Source
const (
	LevelTrace = slog.Level(-8)
	LevelDebug = slog.LevelDebug
	LevelInfo  = slog.LevelInfo
	LevelWarn  = slog.LevelWarn
	LevelError = slog.LevelError
)

Acceptable slog.Level levels.

Variables

This section is empty.

Functions

func CloseAndLog added in v0.23.1

func CloseAndLog(ctx context.Context, l *slog.Logger, closer io.Closer, lvl slog.Level)

CloseAndLog is a convenient helper to log errors returned by closer. The point is to not lose information from deferred Close calls. The error is logged with the specified logging level.

Instead of:

defer f.Close()

You can now write:

defer slogutil.CloseAndLog(ctx, l, f, slog.LevelDebug)

Note that if closer is nil, it is simply ignored.

Example
package main

import (
	"context"
	"fmt"
	"log/slog"

	"github.com/AdguardTeam/golibs/errors"
	"github.com/AdguardTeam/golibs/logutil/slogutil"
	"github.com/AdguardTeam/golibs/testutil/fakeio"
)

func main() {
	ctx := context.Background()
	l := slogutil.New(&slogutil.Config{
		Level: slog.LevelDebug,
	})

	func() {
		defer slogutil.CloseAndLog(ctx, l, nil, slog.LevelDebug)

		fmt.Println("nil closer:")
	}()

	c := &fakeio.Closer{
		OnClose: func() (err error) {
			return nil
		},
	}

	func() {
		defer slogutil.CloseAndLog(ctx, l, c, slog.LevelDebug)

		fmt.Println("actual closer without error:")
	}()

	c = &fakeio.Closer{
		OnClose: func() (err error) {
			return errors.Error("close failed")
		},
	}

	func() {
		defer slogutil.CloseAndLog(ctx, l, c, slog.LevelDebug)

		fmt.Println("actual closer with error:")
	}()

}
Output:


nil closer:
actual closer without error:
actual closer with error:
DEBUG deferred closing err="close failed"

func ContextWithLogger added in v0.23.1

func ContextWithLogger(parent context.Context, l *slog.Logger) (ctx context.Context)

ContextWithLogger returns a new context with the given logger.

Example
package main

import (
	"context"

	"github.com/AdguardTeam/golibs/logutil/slogutil"
)

func main() {
	handler := func(ctx context.Context) {
		l := slogutil.MustLoggerFromContext(ctx)

		l.Info("handling")
	}

	l := slogutil.New(nil)
	l = l.With("request_id", 123)

	ctx := context.Background()
	ctx = slogutil.ContextWithLogger(ctx, l)

	handler(ctx)

}
Output:

INFO handling request_id=123

func LoggerFromContext added in v0.23.1

func LoggerFromContext(ctx context.Context) (l *slog.Logger, ok bool)

LoggerFromContext returns a logger for this request, if any.

func MustLoggerFromContext added in v0.23.1

func MustLoggerFromContext(ctx context.Context) (l *slog.Logger)

MustLoggerFromContext returns a logger for this request and panics if there is no logger.

func New

func New(c *Config) (l *slog.Logger)

New creates a slog logger with the given parameters. If c is nil, the defaults are used.

NOTE: If c.Format is FormatAdGuardLegacy, the legacy logger parameters, such as output, should be set separately.

Example (Default)
package main

import (
	"log/slog"

	"github.com/AdguardTeam/golibs/logutil/slogutil"
)

func main() {
	l := slogutil.New(&slogutil.Config{
		Level: slog.LevelDebug,
	})

	l.Info("test info")
	l.Debug("test debug")

}
Output:

INFO test info
DEBUG test debug
Example (Json)
package main

import (
	"log/slog"

	"github.com/AdguardTeam/golibs/logutil/slogutil"
)

func main() {
	l := slogutil.New(&slogutil.Config{
		Format: slogutil.FormatJSON,
		Level:  slog.LevelDebug,
	})

	l.Info("test info")
	l.Debug("test debug")

	l.WithGroup("test_group").Info("group test info", "time", "too late")
	l.WithGroup("test_group").Debug("group test debug", "time", "too late")

}
Output:

{"level":"INFO","msg":"test info"}
{"level":"DEBUG","msg":"test debug"}
{"level":"INFO","msg":"group test info","test_group":{"time":"too late"}}
{"level":"DEBUG","msg":"group test debug","test_group":{"time":"too late"}}
Example (Text)
package main

import (
	"log/slog"

	"github.com/AdguardTeam/golibs/logutil/slogutil"
)

func main() {
	l := slogutil.New(&slogutil.Config{
		Format: slogutil.FormatText,
		Level:  slog.LevelDebug,
	})

	l.Info("test info")
	l.Debug("test debug")

	l.WithGroup("test_group").Info("group test info", "time", "too late")
	l.WithGroup("test_group").Debug("group test debug", "time", "too late")

}
Output:

level=INFO msg="test info"
level=DEBUG msg="test debug"
level=INFO msg="group test info" test_group.time="too late"
level=DEBUG msg="group test debug" test_group.time="too late"
Example (Trace)
package main

import (
	"context"

	"github.com/AdguardTeam/golibs/logutil/slogutil"
)

func main() {
	l := slogutil.New(&slogutil.Config{
		Format: slogutil.FormatText,
		Level:  slogutil.LevelTrace,
	})

	l.Log(context.Background(), slogutil.LevelTrace, "test trace")
	l.Info("test info")
	l.Debug("test debug")

}
Output:

level=TRACE msg="test trace"
level=INFO msg="test info"
level=DEBUG msg="test debug"

func NewDiscardLogger added in v0.17.3

func NewDiscardLogger() (l *slog.Logger)

NewDiscardLogger returns a new logger that uses DiscardHandler.

func PrintByteLines added in v0.24.1

func PrintByteLines(ctx context.Context, l *slog.Logger, lvl slog.Level, msg string, b []byte)

PrintByteLines splits b and logs the lines to l using the given level, including empty lines.

func PrintLines added in v0.24.1

func PrintLines(ctx context.Context, l *slog.Logger, lvl slog.Level, msg, s string)

PrintLines splits s and logs the lines to l using the given level, including empty lines.

Example
package main

import (
	"context"
	"log/slog"

	"github.com/AdguardTeam/golibs/logutil/slogutil"
)

func main() {
	text := `A Very Long Text

This is a very long text with many lines.`
	l := slogutil.New(nil)

	ctx := context.Background()
	slogutil.PrintLines(ctx, l, slog.LevelInfo, "my text", text)

}
Output:

INFO my text line_num=1 line="A Very Long Text"
INFO my text line_num=2 line=""
INFO my text line_num=3 line="This is a very long text with many lines."

func PrintStack added in v0.17.3

func PrintStack(ctx context.Context, l *slog.Logger, lvl slog.Level)

PrintStack logs the stacktrace into l on the given level.

func RecoverAndExit added in v0.25.0

func RecoverAndExit(ctx context.Context, l *slog.Logger, code osutil.ExitCode)

RecoverAndExit recovers a panic, logs it using l, and then exits with the given exit code.

func RecoverAndLog added in v0.17.3

func RecoverAndLog(ctx context.Context, l *slog.Logger)

RecoverAndLog is a deferred helper that recovers from a panic and logs the panic value into l along with the stacktrace.

func RemoveTime

func RemoveTime(groups []string, a slog.Attr) (res slog.Attr)

RemoveTime is a function for slog.HandlerOptions.ReplaceAttr that removes the "time" attribute.

func ReplaceLevel added in v0.28.0

func ReplaceLevel(groups []string, a slog.Attr) (res slog.Attr)

ReplaceLevel is a function for slog.HandlerOptions.ReplaceAttr that adds LevelTrace custom name for level attribute.

func VerbosityToLevel added in v0.28.0

func VerbosityToLevel(l uint8) (lvl slog.Level, err error)

VerbosityToLevel returns log level for given verbosity.

Types

type AdGuardLegacyHandler added in v0.20.0

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

AdGuardLegacyHandler is a text slog.Handler that uses package github.com/AdguardTeam/golibs/log for output. It is a legacy handler that will be removed in a future version.

The attribute with the name KeyPrefix is handled separately. Like in the legacy logging conventions, it is prepended to the message. There should only be one such attribute between the logger and the message.

Example of output:

12#34 [debug] debug with no attributes
12#34 [info] hdlr: info with prefix and attrs number=123
Example
package main

import (
	"bytes"
	"fmt"
	"log/slog"
	"strings"

	aglog "github.com/AdguardTeam/golibs/log"
	"github.com/AdguardTeam/golibs/logutil/slogutil"
)

func main() {
	// Use a buffer to remove the PID#GID prefix.
	output := &bytes.Buffer{}

	aglog.SetOutput(output)
	aglog.SetFlags(0)
	aglog.SetLevel(aglog.DEBUG)

	h := slogutil.NewAdGuardLegacyHandler(slog.LevelDebug)
	l := slog.New(h)

	l.Debug("debug with no attributes")
	l.Debug("debug with attributes", "number", 123)

	l.Info("info with no attributes")
	l.Info("info with attributes", "number", 123)

	l = l.With(slogutil.KeyPrefix, "hdlr")
	l.Info("info with prefix")

	l.Warn("warning with two prefixes (bad!)", slogutil.KeyPrefix, "bad")

	// Remove the PID#GID prefix for a reproducible example.
	for _, line := range strings.Split(output.String(), "\n") {
		_, line, _ = strings.Cut(line, " ")
		fmt.Println(line)
	}

}
Output:

[debug] debug with no attributes
[debug] debug with attributes number=123
[info] info with no attributes
[info] info with attributes number=123
[info] hdlr: info with prefix
[debug] legacy logger: got prefix "bad" in record for logger with prefix "hdlr"
[info] hdlr: warning: warning with two prefixes (bad!)

func NewAdGuardLegacyHandler added in v0.20.0

func NewAdGuardLegacyHandler(lvl slog.Leveler) (h *AdGuardLegacyHandler)

NewAdGuardLegacyHandler creates a new properly initialized *AdGuardLegacyHandler. Output, level, and other flags should be set in the legacy logging package. lvl is used for AdGuardLegacyHandler.Enabled.

func (*AdGuardLegacyHandler) Enabled added in v0.20.0

func (h *AdGuardLegacyHandler) Enabled(ctx context.Context, lvl slog.Level) (ok bool)

Enabled implements the slog.Handler interface for *AdGuardLegacyHandler.

func (*AdGuardLegacyHandler) Handle added in v0.20.0

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

Handle implements the slog.Handler interface for *AdGuardLegacyHandler.

func (*AdGuardLegacyHandler) WithAttrs added in v0.20.0

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

WithAttrs implements the slog.Handler interface for *AdGuardLegacyHandler.

func (*AdGuardLegacyHandler) WithGroup added in v0.20.0

func (h *AdGuardLegacyHandler) WithGroup(g string) (res slog.Handler)

WithGroup implements the slog.Handler interface for *AdGuardLegacyHandler.

NOTE: It is currently not supported and panics.

TODO(a.garipov): Support groups, see https://github.com/golang/example/blob/master/slog-handler-guide/README.md.

type BadFormatError

type BadFormatError struct {
	Format string
}

BadFormatError is an error about a bad logging format.

func (*BadFormatError) Error

func (err *BadFormatError) Error() (msg string)

Error implements the [error] interface for *BadFormatError.

type Config

type Config struct {
	// Output is the output destination.  If not set, [os.Stdout] is used.
	Output io.Writer

	// Format is the format for the logs.  If not set, [FormatDefault] is used.
	// If set, it must be valid.
	Format Format

	// Level is the minimum record level that will be logged.
	Level slog.Level

	// AddTimestamp, if true, adds a timestamp to every record.
	AddTimestamp bool
}

Config contains the configuration for a logger.

type DiscardHandler added in v0.17.3

type DiscardHandler struct{}

DiscardHandler ignores all messages.

func (DiscardHandler) Enabled added in v0.17.3

func (h DiscardHandler) Enabled(_ context.Context, _ slog.Level) (ok bool)

Enabled implements the slog.Handler interface for DiscardHandler. It always returns false.

func (DiscardHandler) Handle added in v0.17.3

func (h DiscardHandler) Handle(_ context.Context, _ slog.Record) (err error)

Handle implements the slog.Handler interface for DiscardHandler. It always returns nil.

func (DiscardHandler) WithAttrs added in v0.17.3

func (h DiscardHandler) WithAttrs(_ []slog.Attr) (res slog.Handler)

WithAttrs implements the slog.Handler interface for DiscardHandler. It always returns h.

func (DiscardHandler) WithGroup added in v0.17.3

func (h DiscardHandler) WithGroup(_ string) (res slog.Handler)

WithGroup implements the slog.Handler interface for DiscardHandler. It always returns h.

type Format

type Format string

Format represents an acceptable format of logs.

const (
	FormatAdGuardLegacy Format = "adguard_legacy"
	FormatDefault       Format = "default"
	FormatJSON          Format = "json"
	FormatJSONHybrid    Format = "jsonhybrid"
	FormatText          Format = "text"
)

Valid formats.

func NewFormat

func NewFormat(s string) (f Format, err error)

NewFormat returns a new valid format.

type JSONHybridHandler added in v0.18.2

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

JSONHybridHandler is a hybrid JSON-and-text slog.Handler more suitable for stricter environments. It guarantees that the only properties present in the resulting objects are "severity" and "message". All other attributes are packed into the "message" property using the same format as slog.TextHandler.

NOTE: JSONHybridHandler.WithGroup is not currently supported and panics.

Example of output:

{"severity":"NORMAL","message":"time=2024-10-22T12:09:59.525+03:00 level=INFO msg=listening prefix=websvc server=http://127.0.0.1:8181"}
Example
package main

import (
	"log/slog"
	"os"

	"github.com/AdguardTeam/golibs/logutil/slogutil"
)

func main() {
	h := slogutil.NewJSONHybridHandler(os.Stdout, &slog.HandlerOptions{
		AddSource: false,
		Level:     slog.LevelDebug,
		// Use slogutil.RemoveTime to make the example reproducible.
		ReplaceAttr: slogutil.RemoveTime,
	})
	l := slog.New(h)

	l.Debug("debug with no attributes")
	l.Debug("debug with attributes", "number", 123)

	l.Info("info with no attributes")
	l.Info("info with attributes", "number", 123)

	l = l.With("attr", "abc")
	l.Info("new info with no attributes")
	l.Info("new info with attributes", "number", 123)

	l.Error("error with no attributes")
	l.Error("error with attributes", "number", 123)

}
Output:

{"severity":"NORMAL","message":"level=DEBUG msg=\"debug with no attributes\""}
{"severity":"NORMAL","message":"level=DEBUG msg=\"debug with attributes\" number=123"}
{"severity":"NORMAL","message":"level=INFO msg=\"info with no attributes\""}
{"severity":"NORMAL","message":"level=INFO msg=\"info with attributes\" number=123"}
{"severity":"NORMAL","message":"level=INFO msg=\"new info with no attributes\" attr=abc"}
{"severity":"NORMAL","message":"level=INFO msg=\"new info with attributes\" number=123 attr=abc"}
{"severity":"ERROR","message":"level=ERROR msg=\"error with no attributes\" attr=abc"}
{"severity":"ERROR","message":"level=ERROR msg=\"error with attributes\" number=123 attr=abc"}

func NewJSONHybridHandler added in v0.18.2

func NewJSONHybridHandler(w io.Writer, opts *slog.HandlerOptions) (h *JSONHybridHandler)

NewJSONHybridHandler creates a new properly initialized *JSONHybridHandler. opts are used for the underlying text handler.

func (*JSONHybridHandler) Enabled added in v0.18.2

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

Enabled implements the slog.Handler interface for *JSONHybridHandler.

func (*JSONHybridHandler) Handle added in v0.18.2

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

Handle implements the slog.Handler interface for *JSONHybridHandler.

func (*JSONHybridHandler) WithAttrs added in v0.18.2

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

WithAttrs implements the slog.Handler interface for *JSONHybridHandler.

func (*JSONHybridHandler) WithGroup added in v0.18.2

func (h *JSONHybridHandler) WithGroup(g string) (res slog.Handler)

WithGroup implements the slog.Handler interface for *JSONHybridHandler.

NOTE: It is currently not supported and panics.

TODO(a.garipov): Support groups, see https://github.com/golang/example/blob/master/slog-handler-guide/README.md.

type LevelHandler

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

A LevelHandler wraps a Handler with an Enabled method that returns false for levels below a minimum.

See https://cs.opensource.google/go/x/exp/+/master:slog/example_level_handler_test.go.

func NewLevelHandler

func NewLevelHandler(level slog.Leveler, h slog.Handler) (lh *LevelHandler)

NewLevelHandler returns a LevelHandler with the given level. All methods except Enabled delegate to h.

func (*LevelHandler) Enabled

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

Enabled implements the slog.Handler interface for *LevelHandler. It reports whether level is as high as h's level.

func (*LevelHandler) Handle

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

Handle implements the slog.Handler interface for *LevelHandler.

func (*LevelHandler) Handler

func (h *LevelHandler) Handler() (unwrapped slog.Handler)

Handler returns the slog.Handler wrapped by h.

func (*LevelHandler) WithAttrs

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

WithAttrs implements the slog.Handler interface for *LevelHandler.

func (*LevelHandler) WithGroup

func (h *LevelHandler) WithGroup(name string) (res slog.Handler)

WithGroup implements the slog.Handler interface for *LevelHandler.

Jump to

Keyboard shortcuts

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