slog-gin Middleware

repository·main·Indexed 41 days ago

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

A middleware for the Gin web framework that enables structured HTTP request logging using Go's standard slog library. It supports customizable log levels for different HTTP status categories, OpenTelemetry (OTEL) tracing, and verbose logging of request/response headers and bodies. The package includes a flexible filtering system to accept or ignore logs based on status codes, HTTP methods, URL paths, and hosts.

Tokens
5K
Snippets
21
Records
24
Agent score
51%

What's inside slog-gin

  1. Enable OTEL (OpenTelemetry) tracing in logs

    main

    To include span_id and trace_id in your log attributes, use sloggin.NewWithConfig with the corresponding boolean flags set to true in the Config object.

    logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
    
    config := sloggin.Config{
    	WithSpanID:  true,
    	WithTraceID: true,
    }
    
    router := gin.New()
    router.Use(sloggin.NewWithConfig(logger, config))
  2. Use custom time formatters

    main

    If you need to format the timestamps in your logs (e.g., using RFC3339), wrap your slog.Handler with a formatter from github.com/samber/slog-formatter before passing it to the middleware.

    import (
    	"github.com/gin-gonic/gin"
    	sloggin "github.com/samber/slog-gin"
    	slogformatter "github.com/samber/slog-formatter"
    	"log/slog"
    	"os"
    	"time"
    )
    
    logger := slog.New(
        slogformatter.NewFormatterHandler(
            slogformatter.TimezoneConverter(time.UTC),
            slogformatter.TimeFormatter(time.DateTime, nil),
        )(slog.NewTextHandler(os.Stdout, nil)),
    )
    
    router := gin.New()
    router.Use(sloggin.New(logger))
  3. Enable verbose logging (Headers and Bodies)

    main

    To log request/response headers and bodies, enable the following flags in sloggin.Config:

    • WithRequestBody
    • WithResponseBody
    • WithRequestHeader
    • WithResponseHeader
    config := sloggin.Config{
    	WithRequestBody: true,
    	WithResponseBody: true,
    	WithRequestHeader: true,
    	WithResponseHeader: true,
    }
    
    router.Use(sloggin.NewWithConfig(logger, config))
  4. Add the logger to a single route

    main

    Instead of applying the middleware globally via router.Use(), you can pass it as a specific handler for a single route.

    router.GET("/pong", sloggin.New(logger), func(c *gin.Context) {
        c.String(http.StatusOK, "pong")
    })
  5. Configure custom log levels

    main

    You can define different slog.Level values for standard requests, client errors, and server errors using the Config struct.

    config := sloggin.Config{
    	DefaultLevel:     slog.LevelInfo,
    	ClientErrorLevel: slog.LevelWarn,
    	ServerErrorLevel: slog.LevelError,
    }
    
    router.Use(sloggin.NewWithConfig(logger, config))
  6. Group log attributes with a sub-group

    main

    To avoid polluting the top-level log namespace, you can pass a logger that has already been configured with a group (e.g., using logger.WithGroup("http")). This will nest all middleware-injected attributes under that group key.

    // All request/response attributes will be under the "http" key in the log payload
    router.Use(sloggin.New(logger.WithGroup("http")))
  7. Filter logs using sloggin.Filters

    main

    Use sloggin.NewWithFilters to include logic that decides whether a request should be logged. You can use built-in filters to accept or ignore requests based on status codes, methods, paths, hosts, etc.

    Common filter patterns include:

    • Accept / Ignore (using a custom function)
    • AcceptStatus / IgnoreStatus (and related comparison operators like GreaterThan, LessThan)
    • AcceptMethod / IgnoreMethod
    • AcceptPath / IgnorePath (and related pattern matches like Prefix, Suffix, Match)
    • AcceptHost / IgnoreHost (and related pattern matches)
    router.Use(
    	sloggin.NewWithFilters(
    		logger,
    		sloggin.Accept(func (c *gin.Context) bool {
    			return true // your logic
    		}),
    		sloggin.IgnoreStatus(401, 404),
    	),
    )
  8. Understand the Filter type

    main

    A Filter is a function type used to determine whether an HTTP request should be logged or ignored by the slog-gin middleware. It takes a *gin.Context and returns a bool.

    • If the function returns true, the request is accepted (logged).
    • If the function returns false, the request is ignored (not logged).

    You can compose these filters using Accept() and Ignore() to create more complex logic.

  9. Use minimal sloggin middleware

    main

    To quickly add logging to all routes in a Gin engine, use sloggin.New(logger). This will log standard request and response attributes to your provided slog.Logger.

    import (
    	"github.com/gin-gonic/gin"
    	sloggin "github.com/samber/slog-gin"
    	"log/slog"
    	"os"
    	"net/http"
    )
    
    logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
    router := gin.New()
    
    // Add the sloggin middleware to all routes.
    router.Use(sloggin.New(logger))
    router.Use(gin.Recovery())
    
    router.GET("/pong", func(c *gin.Context) {
        c.String(http.StatusOK, "pong")
    })
    
    router.Run(":1234")
  10. Configure sloggin middleware with Config

    main

    The sloggin.Config struct allows you to customize how the middleware logs request and response data. You can control log levels for different HTTP status categories, toggle the inclusion of specific attributes (like headers, body, or OTEL IDs), and apply filters.

    Use sloggin.NewWithConfig(logger, config) to apply these settings.

    type Config struct {
    	DefaultLevel     slog.Level
    	ClientErrorLevel slog.Level
    	ServerErrorLevel slog.Level
    
    	WithUserAgent      bool
    	WithRequestID      bool
    	WithRequestBody    bool
    	WithRequestHeader  bool
    	WithResponseBody   bool
    	WithResponseHeader bool
    	WithSpanID         bool
    	WithTraceID        bool
    	WithClientIP       bool
    	WithCustomMessage  func(c *gin.Context) string
    
    	HandleGinDebug bool
    
    	Filters []Filter
    }
  11. Add custom attributes to a single log entry

    main

    Inside a Gin handler, you can use sloggin.AddCustomAttributes(c, ...) to inject additional key-value pairs into the specific log entry generated by the middleware for that request.

    router.GET("/pong", func(c *gin.Context) {
        // This attribute will appear in the middleware's log entry for this request
        sloggin.AddCustomAttributes(c, slog.String("foo", "bar"))
        c.String(http.StatusOK, "pong")
    })