slogformatter

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 9, 2023 License: MIT Imports: 11 Imported by: 2

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:

🚀 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),
    ),
)

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),
    ),
)

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),
    ),
)

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),
    ),
)

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),
    ),
)

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),
    ),
)

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)),
)

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