slog-formatter
                                
                                
                                
                                    slog-formatter copied to clipboard
                            
                            
                            
                        🚨 slog: Attribute formatting
slog: Attribute formatting
Common formatters for slog library + helpers for building your own.
Handlers:
- NewFormatterHandler: main handler
 - NewFormatterMiddleware: compatible with 
slog-multimiddlewares 
Common formatters:
- TimeFormatter: transforms a 
time.Timeinto a readable string - UnixTimestampFormatter: transforms a 
time.Timeinto a unix timestamp. - TimezoneConverter: set a 
time.Timeto a different timezone - ErrorFormatter: transforms a go error into a readable error
 - HTTPRequestFormatter: transforms a *http.Request into a readable object
 - HTTPResponseFormatter: transforms a *http.Response into a readable object
 - PIIFormatter: hide private Personal Identifiable Information (PII)
 - IPAddressFormatter: hide ip address from logs
 - FlattenFormatterMiddleware: returns a formatter middleware that flatten attributes recursively
 
Custom formatter:
- Format: pass any attribute into a formatter
 - FormatByKind: pass attributes matching 
slog.Kindinto 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:
- slog-multi: 
slog.Handlerchaining, fanout, routing, failover, load balancing... - slog-formatter: 
slogattribute formatting - slog-sampling: 
slogsampling policy 
HTTP middlewares:
- slog-gin: Gin middleware for 
sloglogger - slog-echo: Echo middleware for 
sloglogger - slog-fiber: Fiber middleware for 
sloglogger - slog-chi: Chi middleware for 
sloglogger - slog-http: 
net/httpmiddleware forsloglogger 
Loggers:
- slog-zap: A 
sloghandler forZap - slog-zerolog: A 
sloghandler forZerolog - slog-logrus: A 
sloghandler forLogrus 
Log sinks:
- slog-datadog: A 
sloghandler forDatadog - slog-betterstack: A 
sloghandler forBetterstack - slog-rollbar: A 
sloghandler forRollbar - slog-loki: A 
sloghandler forLoki - slog-sentry: A 
sloghandler forSentry - slog-syslog: A 
sloghandler forSyslog - slog-logstash: A 
sloghandler forLogstash - slog-fluentd: A 
sloghandler forFluentd - slog-graylog: A 
sloghandler forGraylog - slog-quickwit: A 
sloghandler forQuickwit - slog-slack: A 
sloghandler forSlack - slog-telegram: A 
sloghandler forTelegram - slog-mattermost: A 
sloghandler forMattermost - slog-microsoft-teams: A 
sloghandler forMicrosoft Teams - slog-webhook: A 
sloghandler forWebhook - slog-kafka: A 
sloghandler forKafka - slog-nats: A 
sloghandler forNATS - slog-parquet: A 
sloghandler forParquet+Object Storage - slog-channel: A 
sloghandler for Go channels 
🚀 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.LogValuerinstead 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", "[email protected]"),
            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", "[email protected]").
    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": "[email protected]",
//   "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
- Ping me on twitter @samuelberthe (DMs, mentions, whatever :))
 - Fork the project
 - Fix open issues or request new features
 
Don't hesitate ;)
# Install some dev dependencies
make tools
# Run tests
make test
# or
make watch-test
👤 Contributors
💫 Show your support
Give a ⭐️ if this project helped you!
📝 License
Copyright © 2023 Samuel Berthe.
This project is MIT licensed.