slog-formatter

repository·main·Indexed 42 days ago

https://github.com/samber/slog-formatter

A suite of tools for the standard Go slog library to transform and format log attributes. It provides capabilities to anonymize sensitive PII and IP addresses, format complex types like errors and HTTP requests/responses, and customize time formats via TimeFormatter, UnixTimestampFormatter, and TimezoneConverter. It allows developers to intercept attributes using NewFormatterHandler or NewFormatterMiddleware without manually transforming every log call. Requires Go 1.21 or higher.

Tokens
8.4K
Snippets
35
Records
39
Agent score
52%

What's inside slog-formatter

  1. Overview of slog-formatter components

    main

    The library is organized into three main categories of functionality:

    Handlers and Middlewares

    • NewFormatterHandler: The primary entry point to wrap an existing slog.Handler with formatting logic.
    • NewFormatterMiddleware: A middleware compatible with slog-multi for advanced handler chaining.
    • RecoverHandlerError: A specialized handler to catch panics and errors occurring within other handlers.

    Common Formatters

    Pre-built formatters for standard types and common tasks:

    • Time: TimeFormatter (readable strings), UnixTimestampFormatter (unix timestamps), and TimezoneConverter (timezone adjustment).
    • Errors: ErrorFormatter for transforming Go errors.
    • HTTP: HTTPRequestFormatter and HTTPResponseFormatter for logging web traffic.
    • Security/Privacy: PIIFormatter (hide PII) and IPAddressFormatter (hide IP addresses).
    • Structure: FlattenFormatterMiddleware to recursively flatten attributes.
  2. Install slog-formatter

    main

    Install the slog-formatter package using go get.

    Compatibility: Requires Go version 1.21 or higher.

    Warning: Log processing can be computationally expensive. For simple use cases, consider implementing the standard slog.LogValuer interface instead of using this library.

    go get github.com/samber/slog-formatter
  3. Get started with slog-formatter

    main

    To use slog-formatter, you wrap an existing slog.Handler with NewFormatterHandler. You provide one or more formatters to NewFormatterHandler, which will then intercept and transform specific attributes during the logging process.

    Common use cases include anonymizing sensitive keys, formatting error objects, or transforming custom structs into readable strings.

    import (
    	slogformatter "github.com/samber/slog-formatter"
    	"log/slog"
    	"fmt"
    	"os"
    )
    
    // 1. Define formatters
    // Anonymize a specific key
    formatter1 := slogformatter.FormatByKey("very_private_data", func(v slog.Value) slog.Value {
        return slog.StringValue("***********")
    })
    
    // Format error objects
    formatter2 := slogformatter.ErrorFormatter("error")
    
    // Format a custom type
    formatter3 := slogformatter.FormatByType(func(u User) slog.Value {
    	return slog.StringValue(fmt.Sprintf("%s %s", u.firstname, u.lastname))
    })
    
    // 2. Wrap your existing handler
    logger := slog.New(
        slogformatter.NewFormatterHandler(formatter1, formatter2, formatter3)(
            slog.NewTextHandler(os.Stdout, nil),
        ),
    )
    
    // 3. Log data
    err := fmt.Errorf("an error")
    logger.Error("a message",
        slog.Any("very_private_data", "abcd"),
        slog.Any("user", user),
        slog.Any("err", err))
    
    // Output example:
    // 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="********"
  4. How FormatterHandler transforms attributes

    main

    When Handle, WithAttrs, or WithGroup is called, the FormatterHandler performs the following transformations on attributes:

    1. LogValuer Resolution: If an attribute's value implements slog.LogValuer, it is automatically resolved to its LogValue() before formatters are applied.
    2. Formatter Application: The handler iterates through all registered formatters. Each formatter is a function with the signature func(groups []string, attr slog.Attr) (slog.Value, bool). If a formatter returns ok == true, the attribute's value is updated with the returned slog.Value.
    3. Group Awareness: Formatters receive the current groups (a slice of strings representing the nesting level of groups) to allow for context-aware formatting.
  5. Format HTTP requests and responses

    main

    Use HTTPRequestFormatter and HTTPResponseFormatter to transform *http.Request and *http.Response objects into readable log attributes. The boolean parameter controls whether to include certain details (e.g., headers).

    logger := slog.New(
        slogformatter.NewFormatterHandler(
            slogformatter.HTTPRequestFormatter(false),
            slogformatter.HTTPResponseFormatter(false),
        )( 
            slog.NewJSONHandler(os.Stdout, nil),
        ),
    )
    
    // ... perform request ...
    
    logger.Error("a message",
        slog.Any("request", req),
        slog.Any("response", res),
    )
  6. Hide PII with PIIFormatter

    main

    The PIIFormatter hides Personal Identifiable Information (PII) for a specific key. It keeps IDs as is, but masks values longer than 5 characters with a plain text prefix.

    logger := slog.New(
        slogformatter.NewFormatterHandler(
            slogformatter.PIIFormatter("user"),
        )( 
            slog.NewTextHandler(os.Stdout, nil),
        ),
    )
    
    logger.With(slog.Group("user", slog.String("email", "foobar@example.com"))).Error("an error")
    // email will be masked
  7. Recover from handler panics with RecoverHandlerError

    main

    Use RecoverHandlerError to return a slog.Handler that recovers from panics or errors occurring within the subsequent chain of handlers. You provide a callback function that is executed if a failure occurs.

    import (
    	slogformatter "github.com/samber/slog-formatter"
    	slogmulti "github.com/samber/slog-multi"
    	"log/slog"
    	"context"
    )
    
    recovery := slogformatter.RecoverHandlerError(
        func(ctx context.Context, record slog.Record, err error) {
            // called if subsequent handlers fail
            log.Println(err.Error())
        },
    )
    
    sink := NewSinkHandler(...) // your actual handler
    
    logger := slog.New(
        slogmulti.
            Pipe(recovery).
            Handler(sink),
    )
  8. Use NewFormatterMiddleware with slog-multi

    main

    If you are using slog-multi, you can use NewFormatterMiddleware (returned by NewFormatterHandler) as a middleware in a Pipe chain. This allows you to apply formatting logic within a multi-handler pipeline.

    import (
    	slogformatter "github.com/samber/slog-formatter"
    	slogmulti "github.com/samber/slog-multi"
    	"log/slog"
    	"os"
    )
    
    // ... define formatters ...
    
    formattingMiddleware := slogformatter.NewFormatterHandler(formatter1, formatter2, formatter3)
    sink := slog.NewJSONHandler(os.Stderr, slog.HandlerOptions{})
    
    logger := slog.New(
        slogmulti.
            Pipe(formattingMiddleware).
            Handler(sink),
    )
  9. Mask IP addresses with IPAddressFormatter

    main

    The IPAddressFormatter transforms any IP address attribute matching the specified key into "********".

    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")
  10. Format errors with ErrorFormatter

    main

    The ErrorFormatter transforms a Go error into a structured, readable object containing the error message, type, and stacktrace.

    logger := slog.New(
        slogformatter.NewFormatterHandler(
            slogformatter.ErrorFormatter("error"),
        )( 
            slog.NewJSONHandler(os.Stdout, nil),
        ),
    )
    
    err := fmt.Errorf("an error")
    logger.Error("a message", slog.Any("error", err))
  11. Format time with TimeFormatter, UnixTimestampFormatter, or TimezoneConverter

    main

    These formatters allow you to customize how time.Time values appear in logs:

    • TimeFormatter(layout, timezone): Transforms time into a readable string using a specific layout and timezone.
    • UnixTimestampFormatter(unit): Transforms time into a unix timestamp (e.g., time.Millisecond).
    • TimezoneConverter(timezone): Sets all time.Time values to a specific timezone.
    // Example: TimeFormatter
    slogformatter.NewFormatterHandler(slogformatter.TimeFormatter(time.DateTime, time.UTC))
    
    // Example: UnixTimestampFormatter
    slogformatter.NewFormatterHandler(slogformatter.UnixTimestampFormatter(time.Millisecond))
    
    // Example: TimezoneConverter
    slogformatter.NewFormatterHandler(slogformatter.TimezoneConverter(time.UTC))
  12. Flatten attributes with FlattenFormatterMiddleware

    main

    Use FlattenFormatterMiddleware to recursively flatten log attributes into a single level. You can configure the Separator, Prefix, and whether to IgnorePath using FlattenFormatterMiddlewareOptions.

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