slogformatter

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2024 License: MIT Imports: 11 Imported by: 14

README ΒΆ

slog: Attribute formatting

tag Go Version GoDoc Build Status Go report Coverage Contributors License

Common formatters for slog library + helpers for building your own.

Handlers:

Common formatters:

Custom formatter:

  • Format: pass any attribute into a formatter
  • FormatByKind: pass attributes matching slog.Kind into a formatter
  • FormatByType: pass attributes matching generic type into a formatter
  • FormatByKey: pass attributes matching key into a formatter
  • FormatByFieldType: pass attributes matching both key and generic type into a formatter
  • FormatByGroup: pass attributes under a group into a formatter
  • FormatByGroupKey: pass attributes under a group and matching key, into a formatter
  • FormatByGroupKeyType: pass attributes under a group, matching key and matching a generic type, into a formatter

See also:

HTTP middlewares:

Loggers:

Log sinks:

πŸš€ Install

go get github.com/samber/slog-formatter

Compatibility: go >= 1.21

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

⚠️ Warnings:

  • in some case, you should consider implementing slog.LogValuer instead of using this library.
  • use this library carefully, log processing can be very costly (!)

πŸš€ Getting started

The following example has 3 formatters that anonymize data, format errors and format user. πŸ‘‡

import (
	slogformatter "github.com/samber/slog-formatter"
	"log/slog"
)

formatter1 := slogformatter.FormatByKey("very_private_data", func(v slog.Value) slog.Value {
    return slog.StringValue("***********")
})
formatter2 := slogformatter.ErrorFormatter("error")
formatter3 := slogformatter.FormatByType(func(u User) slog.Value {
	return slog.StringValue(fmt.Sprintf("%s %s", u.firstname, u.lastname))
})

logger := slog.New(
    slogformatter.NewFormatterHandler(formatter1, formatter2, formatter3)(
        slog.NewTextHandler(os.Stdout, nil),
    ),
)

err := fmt.Errorf("an error")
logger.Error("a message",
    slog.Any("very_private_data", "abcd"),
    slog.Any("user", user),
    slog.Any("err", err))

// outputs:
// time=2023-04-10T14:00:0.000000+00:00 level=ERROR msg="a message" error.message="an error" error.type="*errors.errorString" user="John doe" very_private_data="********"

πŸ’‘ Spec

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

NewFormatterHandler

Returns a slog.Handler that applies formatters to.

import (
	slogformatter "github.com/samber/slog-formatter"
	"log/slog"
)

type User struct {
	email     string
	firstname string
	lastname  string
}

formatter1 := slogformatter.FormatByKey("very_private_data", func(v slog.Value) slog.Value {
    return slog.StringValue("***********")
})
formatter2 := slogformatter.ErrorFormatter("error")
formatter3 := slogformatter.FormatByType(func(u User) slog.Value {
	return slog.StringValue(fmt.Sprintf("%s %s", u.firstname, u.lastname))
})

logger := slog.New(
    slogformatter.NewFormatterHandler(formatter1, formatter2, formatter3)(
        slog.NewTextHandler(os.StdErr, nil),
    ),
)

err := fmt.Errorf("an error")
logger.Error("a message",
    slog.Any("very_private_data", "abcd"),
    slog.Any("user", user),
    slog.Any("err", err))

// outputs:
// time=2023-04-10T14:00:0.000000+00:00 level=ERROR msg="a message" error.message="an error" error.type="*errors.errorString" user="John doe" very_private_data="********"
NewFormatterMiddleware

Returns a slog-multi middleware that applies formatters to.

import (
	slogformatter "github.com/samber/slog-formatter"
	slogmulti "github.com/samber/slog-multi"
	"log/slog"
)

formatter1 := slogformatter.FormatByKey("very_private_data", func(v slog.Value) slog.Value {
    return slog.StringValue("***********")
})
formatter2 := slogformatter.ErrorFormatter("error")
formatter3 := slogformatter.FormatByType(func(u User) slog.Value {
	return slog.StringValue(fmt.Sprintf("%s %s", u.firstname, u.lastname))
})

formattingMiddleware := slogformatter.NewFormatterHandler(formatter1, formatter2, formatter3)
sink := slog.NewJSONHandler(os.Stderr, slog.HandlerOptions{})

logger := slog.New(
    slogmulti.
        Pipe(formattingMiddleware).
        Handler(sink),
)

err := fmt.Errorf("an error")
logger.Error("a message",
    slog.Any("very_private_data", "abcd"),
    slog.Any("user", user),
    slog.Any("err", err))

// outputs:
// time=2023-04-10T14:00:0.000000+00:00 level=ERROR msg="a message" error.message="an error" error.type="*errors.errorString" user="John doe" very_private_data="********"
TimeFormatter

Transforms a time.Time into a readable string.

slogformatter.NewFormatterHandler(
    slogformatter.TimeFormatter(time.DateTime, time.UTC),
)
UnixTimestampFormatter

Transforms a time.Time into a unix timestamp.

slogformatter.NewFormatterHandler(
    slogformatter.UnixTimestampFormatter(time.Millisecond),
)
TimezoneConverter

Set a time.Time to a different timezone.

slogformatter.NewFormatterHandler(
    slogformatter.TimezoneConverter(time.UTC),
)
ErrorFormatter

Transforms a Go error into a readable error.

import (
	slogformatter "github.com/samber/slog-formatter"
	"log/slog"
)

logger := slog.New(
    slogformatter.NewFormatterHandler(
        slogformatter.ErrorFormatter("error"),
    )(
        slog.NewTextHandler(os.Stdout, nil),
    ),
)

err := fmt.Errorf("an error")
logger.Error("a message", slog.Any("error", err))

// outputs:
// {
//   "time":"2023-04-10T14:00:0.000000+00:00",
//   "level": "ERROR",
//   "msg": "a message",
//   "error": {
//     "message": "an error",
//     "type": "*errors.errorString"
//     "stacktrace": "main.main()\n\t/Users/samber/src/github.com/samber/slog-formatter/example/example.go:108 +0x1c\n"
//   }
// }
HTTPRequestFormatter and HTTPResponseFormatter

Transforms *http.Request and *http.Response into readable objects.

import (
	slogformatter "github.com/samber/slog-formatter"
	"log/slog"
)

logger := slog.New(
    slogformatter.NewFormatterHandler(
        slogformatter.HTTPRequestFormatter(false),
        slogformatter.HTTPResponseFormatter(false),
    )(
        slog.NewJSONHandler(os.Stdout, nil),
    ),
)

req, _ := http.NewRequest(http.MethodGet, "https://api.screeb.app", nil)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-TOKEN", "1234567890")

res, _ := http.DefaultClient.Do(req)

logger.Error("a message",
    slog.Any("request", req),
    slog.Any("response", res))
PIIFormatter

Hides private Personal Identifiable Information (PII).

IDs are kept as is. Values longer than 5 characters have a plain text prefix.

import (
	slogformatter "github.com/samber/slog-formatter"
	"log/slog"
)

logger := slog.New(
    slogformatter.NewFormatterHandler(
        slogformatter.PIIFormatter("user"),
    )(
        slog.NewTextHandler(os.Stdout, nil),
    ),
)

logger.
    With(
        slog.Group(
            "user",
            slog.String("id", "bd57ffbd-8858-4cc4-a93b-426cef16de61"),
            slog.String("email", "foobar@example.com"),
            slog.Group(
                "address",
                slog.String("street", "1st street"),
                slog.String("city", "New York"),
                slog.String("country", "USA"),
                slog.Int("zip", 12345),
            ),
        ),
    ).
    Error("an error")

// outputs:
// {
//   "time":"2023-04-10T14:00:0.000000+00:00",
//   "level": "ERROR",
//   "msg": "an error",
//   "user": {
//     "id": "bd57ffbd-8858-4cc4-a93b-426cef16de61",
//     "email": "foob*******",
//     "address": {
//       "street": "1st *******",
//       "city": "New *******",
//       "country": "*******",
//       "zip": "*******"
//     }
//   }
// }
IPAddressFormatter

Transforms an IP address into "********".

import (
	slogformatter "github.com/samber/slog-formatter"
	"log/slog"
)

logger := slog.New(
    slogformatter.NewFormatterHandler(
        slogformatter.IPAddressFormatter("ip_address"),
    )(
        slog.NewTextHandler(os.Stdout, nil),
    ),
)

logger.
    With("ip_address", "1.2.3.4").
    Error("an error")

// outputs:
// {
//   "time":"2023-04-10T14:00:0.000000+00:00",
//   "level": "ERROR",
//   "msg": "an error",
//   "ip_address": "*******",
// }
FlattenFormatterMiddleware

A formatter middleware that flatten attributes recursively.

import (
	slogformatter "github.com/samber/slog-formatter"
	slogmulti "github.com/samber/slog-multi"
	"log/slog"
)

logger := slog.New(
    slogmulti.
        Pipe(slogformatter.FlattenFormatterMiddlewareOptions{Separator: ".", Prefix: "attrs", IgnorePath: false}.NewFlattenFormatterMiddlewareOptions()).
        Handler(slog.NewJSONHandler(os.Stdout, nil)),
)

logger.
    With("email", "samuel@acme.org").
    With("environment", "dev").
    WithGroup("group1").
    With("hello", "world").
    WithGroup("group2").
    With("hello", "world").
    Error("A message", "foo", "bar")

// outputs:
// {
//   "time": "2023-05-20T22:14:55.857065+02:00",
//   "level": "ERROR",
//   "msg": "A message",
//   "attrs.email": "samuel@acme.org",
//   "attrs.environment": "dev",
//   "attrs.group1.hello": "world",
//   "attrs.group1.group2.hello": "world",
//   "foo": "bar"
// }
Format

Pass every attributes into a formatter.

slogformatter.NewFormatterHandler(
    slogformatter.Format(func(groups []string, key string, value slog.Value) slog.Value {
        // hide everything under "user" group
        if lo.Contains(groups, "user") {
            return slog.StringValue("****")
        }

        return value
    }),
)
FormatByKind

Pass attributes matching slog.Kind into a formatter.

slogformatter.NewFormatterHandler(
    slogformatter.FormatByKind(slog.KindDuration, func(value slog.Value) slog.Value {
        return ...
    }),
)
FormatByType

Pass attributes matching generic type into a formatter.

slogformatter.NewFormatterHandler(
    // format a custom error type
    slogformatter.FormatByType[*customError](func(err *customError) slog.Value {
        return slog.GroupValue(
            slog.Int("code", err.code),
            slog.String("message", err.msg),
        )
    }),
    // format other errors
    slogformatter.FormatByType[error](func(err error) slog.Value {
        return slog.GroupValue(
            slog.Int("code", err.Error()),
            slog.String("type", reflect.TypeOf(err).String()),
        )
    }),
)

⚠️ Consider implementing slog.LogValuer when possible:

type customError struct {
    ...
}

func (customError) Error() string {
    ...
}

// implements slog.LogValuer
func (customError) LogValue() slog.Value {
	return slog.StringValue(...)
}
FormatByKey

Pass attributes matching key into a formatter.

slogformatter.NewFormatterHandler(
    slogformatter.FormatByKey("abcd", func(value slog.Value) slog.Value {
        return ...
    }),
)
FormatByFieldType

Pass attributes matching both key and generic type into a formatter.

slogformatter.NewFormatterHandler(
    slogformatter.FormatByFieldType[User]("user", func(u User) slog.Value {
        return ...
    }),
)
FormatByGroup

Pass attributes under a group into a formatter.

slogformatter.NewFormatterHandler(
    slogformatter.FormatByGroup([]{"user", "address"}, func(attr []slog.Attr) slog.Value {
        return ...
    }),
)
FormatByGroupKey

Pass attributes under a group and matching key, into a formatter.

slogformatter.NewFormatterHandler(
    slogformatter.FormatByGroupKey([]{"user", "address"}, "country", func(value slog.Value) slog.Value {
        return ...
    }),
)
FormatByGroupKeyType

Pass attributes under a group, matching key and matching a generic type, into a formatter.

slogformatter.NewFormatterHandler(
    slogformatter.FormatByGroupKeyType[string]([]{"user", "address"}, "country", func(value string) slog.Value {
        return ...
    }),
)

🀝 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 ΒΆ

This section is empty.

Functions ΒΆ

func FlattenAttrs ΒΆ added in v0.5.0

func FlattenAttrs(attrs []slog.Attr) []slog.Attr

FlattenAttrs flatten attributes recursively.

func FlattenAttrsWithPrefix ΒΆ added in v0.5.0

func FlattenAttrsWithPrefix(separator string, prefix string, attrs []slog.Attr) []slog.Attr

FlattenAttrsWithPrefix flatten attributes recursively, with prefix.

func NewFormatterHandler ΒΆ

func NewFormatterHandler(formatters ...Formatter) func(slog.Handler) slog.Handler

NewFormatterHandler returns a slog.Handler that applies formatters to.

func NewFormatterMiddleware ΒΆ

func NewFormatterMiddleware(formatters ...Formatter) slogmulti.Middleware

NewFormatterMiddleware returns slog-multi middleware.

func PrefixAttrKeys ΒΆ added in v0.5.0

func PrefixAttrKeys(prefix string, attrs []slog.Attr) []slog.Attr

PrefixAttrKeys prefix attribute keys.

Types ΒΆ

type FlattenFormatterMiddleware ΒΆ added in v0.5.0

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

func (*FlattenFormatterMiddleware) Enabled ΒΆ added in v0.5.0

func (h *FlattenFormatterMiddleware) Enabled(ctx context.Context, level slog.Level) bool

Implements slog.Handler

func (*FlattenFormatterMiddleware) Handle ΒΆ added in v0.5.0

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

Implements slog.Handler

func (*FlattenFormatterMiddleware) WithAttrs ΒΆ added in v0.5.0

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

Implements slog.Handler

func (*FlattenFormatterMiddleware) WithGroup ΒΆ added in v0.5.0

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

Implements slog.Handler

type FlattenFormatterMiddlewareOptions ΒΆ added in v0.5.0

type FlattenFormatterMiddlewareOptions struct {
	// Ignore attribute path and therefore ignore attribute key prefix.
	// Some attribute keys may collide.
	IgnorePath bool
	// Separator between prefix and key.
	Separator string
	// Attribute key prefix.
	Prefix string
}

func (FlattenFormatterMiddlewareOptions) NewFlattenFormatterMiddlewareOptions ΒΆ added in v0.5.0

func (o FlattenFormatterMiddlewareOptions) NewFlattenFormatterMiddlewareOptions() slogmulti.Middleware

NewFlattenFormatterMiddlewareOptions returns a formatter middleware that flatten attributes recursively.

type Formatter ΒΆ

type Formatter func(groups []string, attr slog.Attr) (slog.Value, bool)

func ErrorFormatter ΒΆ

func ErrorFormatter(fieldName string) Formatter

ErrorFormatter transforms a go error into a readable error.

Example:

err := reader.Close()
err = fmt.Errorf("could not close reader: %v", err)
logger.With("error", reader.Close()).Log("error")

passed to ErrorFormatter("error"), will be transformed into:

"error": {
  "message": "could not close reader: file already closed",
  "type": "*io.ErrClosedPipe"
}

func Format ΒΆ

func Format[T any](formatter func([]string, string, slog.Value) slog.Value) Formatter

Format pass every attributes into a formatter.

func FormatByFieldType ΒΆ

func FormatByFieldType[T any](key string, formatter func(T) slog.Value) Formatter

FormatByFieldType pass attributes matching both key and generic type into a formatter.

func FormatByGroup ΒΆ

func FormatByGroup(groups []string, formatter func([]slog.Attr) slog.Value) Formatter

FormatByGroup pass attributes under a group into a formatter.

func FormatByGroupKey ΒΆ

func FormatByGroupKey(groups []string, key string, formatter func(slog.Value) slog.Value) Formatter

FormatByGroupKey pass attributes under a group and matching key, into a formatter.

func FormatByGroupKeyType ΒΆ

func FormatByGroupKeyType[T any](groups []string, key string, formatter func(T) slog.Value) Formatter

FormatByGroupKeyType pass attributes under a group, matching key and matching a generic type, into a formatter.

func FormatByKey ΒΆ

func FormatByKey(key string, formatter func(slog.Value) slog.Value) Formatter

FormatByKey pass attributes matching key into a formatter.

func FormatByKind ΒΆ added in v0.3.0

func FormatByKind(kind slog.Kind, formatter func(slog.Value) slog.Value) Formatter

FormatByKind pass attributes matching `slog.Kind` into a formatter.

func FormatByType ΒΆ

func FormatByType[T any](formatter func(T) slog.Value) Formatter

FormatByType pass attributes matching generic type into a formatter.

func HTTPRequestFormatter ΒΆ added in v0.2.0

func HTTPRequestFormatter(ignoreHeaders bool) Formatter

HTTPRequestFormatter transforms a *http.Request into a readable object.

func HTTPResponseFormatter ΒΆ added in v0.2.0

func HTTPResponseFormatter(ignoreHeaders bool) Formatter

HTTPResponseFormatter transforms a *http.Response into a readable object.

func IPAddressFormatter ΒΆ

func IPAddressFormatter(key string) Formatter

IPAddressFormatter transforms an IP address into "********".

Example:

"context": {
  "ip_address": "bd57ffbd-8858-4cc4-a93b-426cef16de61"
}

passed to IPAddressFormatter("ip_address"), will be transformed into:

"context": {
  "ip_address": "********",
}

func PIIFormatter ΒΆ

func PIIFormatter(key string) Formatter

PIIFormatter transforms any value under provided key into "********". IDs are kept as is.

Example:

  "user": {
    "id": "bd57ffbd-8858-4cc4-a93b-426cef16de61",
    "email": "foobar@example.com",
    "address": {
      "street": "1st street",
	     "city": "New York",
      "country": USA",
	     "zip": 123456
    }
  }

passed to PIIFormatter("user"), will be transformed into:

  "user": {
    "id": "bd57ffbd-8858-4cc4-a93b-426cef16de61",
    "email": "foob********",
    "address": {
      "street": "1st *******",
	     "city": "New *******",
  	   "country": "*******",
	     "zip": "*******"
    }
  }

func TimeFormatter ΒΆ added in v0.3.0

func TimeFormatter(timeFormat string, location *time.Location) Formatter

TimeFormatter transforms a `time.Time` into a readable string.

func TimezoneConverter ΒΆ added in v0.3.0

func TimezoneConverter(location *time.Location) Formatter

TimezoneConverter set a `time.Time` to a different timezone.

func UnixTimestampFormatter ΒΆ added in v0.3.0

func UnixTimestampFormatter(precision time.Duration) Formatter

UnixTimestampFormatter transforms a `time.Time` into a unix timestamp.

type FormatterHandler ΒΆ

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

func (*FormatterHandler) Enabled ΒΆ

func (h *FormatterHandler) Enabled(ctx context.Context, l slog.Level) bool

Enabled implements slog.Handler.

func (*FormatterHandler) Handle ΒΆ

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

Handle implements slog.Handler.

func (*FormatterHandler) WithAttrs ΒΆ

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

WithAttrs implements slog.Handler.

func (*FormatterHandler) WithGroup ΒΆ

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

WithGroup implements slog.Handler.

type LogValuerFunc ΒΆ

type LogValuerFunc func(any) (slog.Value, bool)

Directories ΒΆ

Path Synopsis

Jump to

Keyboard shortcuts

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