Package slogx contains extensions for standard library's slog package.
go get github.com/cappuccinotm/slogx
-
slogx.Accumulator(slog.Handler) slog.Handler
- returns a handler that accumulates attributes and groups from theWithGroup
andWithAttrs
calls, to pass them to the underlying handler only onHandle
call. Allows middlewares to capture the handler-level attributes and groups, but may be consuming. -
slogx.NopHandler() slog.Handler
- returns a handler that does nothing. Can be used in tests, to disable logging. -
slog.Chain
- chains the multiple "middlewares" - handlers, which can modify the log entry. -
slogt.TestHandler
- returns a handler that logs the log entry throughtesting.T
'sLog
function. It will shorten attributes, so the output will be more readable. -
fblog.Handler
- a handler that logs the log entry in the fblog-like format, like:2024-02-05 09:11:37 [INFO]: info message key: 1 some_multi_line_string: "line1\nline2\nline3" multiline_any: "line1\nline2\nline3" group.int: 1 group.string: "string" group.float64: 1.1 group.bool: false ...some_very_very_long_key: 1
Some benchmarks (though this handler wasn't designed for performance, but for comfortable reading of the logs in debug/local mode):
BenchmarkHandler BenchmarkHandler/fblog.NewHandler BenchmarkHandler/fblog.NewHandler-8 1479525 800.9 ns/op 624 B/op 8 allocs/op BenchmarkHandler/slog.NewJSONHandler BenchmarkHandler/slog.NewJSONHandler-8 2407322 500.0 ns/op 48 B/op 1 allocs/op BenchmarkHandler/slog.NewTextHandler BenchmarkHandler/slog.NewTextHandler-8 2404581 490.0 ns/op 48 B/op 1 allocs/op
All the benchmarks were run on a MacBook Pro (14-inch, 2021) with Apple M1 processor, the benchmark contains the only log
lg.Info("message", slog.Int("int", 1))
slogm.RequestID()
- adds a request ID to the context and logs it.slogm.ContextWithRequestID(ctx context.Context, requestID string) context.Context
- adds a request ID to the context.
slogm.StacktraceOnError()
- adds a stacktrace to the log entry if log entry's level is ERROR.slogm.TrimAttrs(limit int)
- trims the length of the attributes tolimit
.slogm.MaskSecrets(replacement string)
- masks secrets in logs, which are stored in the contextslogm.AddSecrets(ctx context.Context, secret ...string) context.Context
- adds a secret value to the context- Note: secrets are stored in the context as a pointer to the container object, guarded by a mutex. Child context
can safely add secrets to the context, and the secrets will be available for the parent context, but before
using the secrets container, the container must be initialized in the parent context with this function, e.g.:
ctx = slogm.AddSecrets(ctx)
- Note: secrets are stored in the context as a pointer to the container object, guarded by a mutex. Child context
can safely add secrets to the context, and the secrets will be available for the parent context, but before
using the secrets container, the container must be initialized in the parent context with this function, e.g.:
slogx.Error(err error)
- adds an error to the log entry under "error" key.
package main
import (
"context"
"errors"
"os"
"github.com/cappuccinotm/slogx"
"github.com/cappuccinotm/slogx/slogm"
"github.com/google/uuid"
"log/slog"
)
func main() {
h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelInfo,
})
logger := slog.New(slogx.Accumulator(slogx.NewChain(h,
slogm.RequestID(),
slogm.StacktraceOnError(),
slogm.MaskSecrets("***"),
)))
ctx := slogm.ContextWithRequestID(context.Background(), uuid.New().String())
ctx = slogm.AddSecrets(ctx, "secret")
logger.InfoContext(ctx,
"some message",
slog.String("key", "value"),
)
logger.ErrorContext(ctx, "oh no, an error occurred",
slog.String("details", "some important secret error details"),
slogx.Error(errors.New("some error")),
)
logger.WithGroup("group1").
With(slog.String("omg", "the previous example was wrong")).
WithGroup("group2").
With(slog.String("omg", "this is the right example")).
With(slog.String("key", "value")).
InfoContext(ctx, "some message",
slog.String("key", "value"))
}
Produces:
{
"time": "2023-08-17T02:04:19.281961+06:00",
"level": "INFO",
"source": {
"function": "main.main",
"file": "/.../github.com/cappuccinotm/slogx/_example/main.go",
"line": 25
},
"msg": "some message",
"key": "value",
"request_id": "bcda1960-fa4d-46b3-9c1b-fec72c7c07a3"
}
{
"time": "2023-08-17T03:35:21.251385+06:00",
"level": "ERROR",
"source": {
"function": "main.main",
"file": "/Users/semior/go/src/github.com/cappuccinotm/slogx/_example/main.go",
"line": 47
},
"msg": "oh no, an error occurred",
"details": "some important *** error details",
"error": "some error",
"request_id": "8ba29407-5d58-4dca-99e9-54528b1ae3f0",
"stacktrace": "main.main()\n\t/Users/semior/go/src/github.com/cappuccinotm/slogx/_example/main.go:47 +0x4a4\n"
}
{
"time": "2024-02-18T05:02:13.030604+06:00",
"level": "INFO",
"source": {
"function": "main.main",
"file": "/Users/semior/go/src/github.com/cappuccinotm/slogx/_example/main.go",
"line": 74
},
"msg": "some message",
"key": "value",
"group1": {
"omg": "the previous example was wrong",
"group2": {
"omg": "this is the right example",
"key": "value"
}
},
"request_id": "1a34889f-a5b4-464e-9a86-0a30b50376cc"
}
Package slogx also contains a logger
package, which provides a Logger
service, that could be used
as an HTTP server middleware and a http.RoundTripper
, that logs HTTP requests and responses.
l := logger.New(
logger.WithLogger(slog.Default()),
logger.WithBody(1024),
logger.WithUser(func(*http.Request) (string, error) { return "username", nil }),
)
logger.WithLogger(logger slog.Logger)
- sets the slog logger.logger.WithLogFn(fn func(context.Context, *LogParts))
- sets a custom function to log request and response.logger.WithBody(maxBodySize int)
- logs the request and response body, maximum size of the logged body is set bymaxBodySize
.logger.WithUser(fn func(*http.Request) (string, error))
- sets a function to get the user data from the request.
Library provides a slogt.TestHandler
function to build a test handler, which will print out the log entries through testing.T
's Log
function. It will shorten attributes, so the output will be more readable.
func TestSomething(t *testing.T) {
h := slogt.Handler(t, slogt.SplitMultiline)
logger := slog.New(h)
logger.Info("some\nmultiline\nmessage", slog.String("key", "value"))
}
// Output:
// === RUN TestSomething
// handler.go:306: t=11:36:28.649 l=DEBUG s=main_test.go:12 msg="some single-line message" key=value group.groupKey=groupValue
//
// testing.go:52: some
// testing.go:52: multiline
// testing.go:52: message
// handler.go:306: t=11:36:28.649 l=INFO s=main_test.go:17 msg="message with newlines has been printed to t.Log" key=value