Gin middleware for Go 1.23+ slog logging.
gin-slog is a Gin middleware that provides structured logging using Go's standard slog package (available since Go 1.21). It allows you to customize log formatting, target, level per path/status, and injection of additional context for each request, making it easy to produce standardized, production-ready logs in your Gin applications.
- Log HTTP requests with structured output via slog
- Configurable log levels per status code or endpoint path
- Easily skip logging for specific paths (by string or regexp)
- Supports custom log writers and messages
- Add custom fields or alter records/context per request
- Output in standard text format compatible with slog
- Fully extensible via Options pattern
- Sensitive request headers (like Authorization/Cookie/etc) are hidden by default in logs, and can be customized
go get github.com/gin-contrib/slogRequires Go 1.23+.
package main
import (
  "net/http"
  "github.com/gin-contrib/slog"
  "github.com/gin-gonic/gin"
)
func main() {
  r := gin.New()
  // Add slog middleware with default settings
  r.Use(slog.SetLogger())
  // Example route
  r.GET("/", func(c *gin.Context) {
    slog.Get(c).Info("Hello World!")
    c.String(http.StatusOK, "ok")
  })
  r.Run()
}You can customize the middleware via options:
import (
  "os"
  "log/slog"
  "regexp"
  
  "github.com/gin-gonic/gin"
  "github.com/gin-contrib/slog"
)
func main() {
  r := gin.New()
  r.Use(slog.SetLogger(
    // Change log writer
    slog.WithWriter(os.Stdout),
    // Use UTC timestamps
    slog.WithUTC(true),
    // Skip health check and static routes
    slog.WithSkipPath([]string{"/healthz", "/metrics"}),
    // Skip paths matching pattern (e.g., static assets)
    slog.WithSkipPathRegexps(regexp.MustCompile(`\.ico$`), regexp.MustCompile(`^/static/`)),
    // Change default log levels
    slog.WithDefaultLevel(slog.LevelDebug),
    slog.WithClientErrorLevel(slog.LevelWarn),
    slog.WithServerErrorLevel(slog.LevelError),
    // Log message customization
    slog.WithMessage("Handled request"),
    // Set specific log level for a given path
    slog.WithPathLevel(map[string]slog.Level{"/foo": slog.LevelInfo}),
    // Set log level by status code
    slog.WithSpecificLogLevelByStatusCode(map[int]slog.Level{418: slog.LevelDebug}),
    // Inject custom info/context into logs
    slog.WithContext(func(c *gin.Context, rec *slog.Record) *slog.Record {
      rec.Add("user_agent", c.Request.UserAgent())
      return rec
    }),
    // Provide your own logger (to add global fields, etc.)
    slog.WithLogger(func(c *gin.Context, l *slog.Logger) *slog.Logger {
      return l.With("request_id", c.GetString("request_id"))
    }),
    // Custom Skipper (function: skip logging if ...), example:
    slog.WithSkipper(func(c *gin.Context) bool {
      return c.Request.Method == "OPTIONS"
    }),
    // Hide sensitive request headers from logs (optional, default hides Authorization/Cookie/etc)
    slog.WithRequestHeader(true), // Set to false to disable logging all request headers
    slog.WithHiddenRequestHeaders([]string{
      "authorization", "cookie", "x-csrf-token", // set your own or reset
    }),
  ))
  r.Run()
}Each HTTP request log will include by default:
- status(int): Response HTTP status code
- method(string): HTTP method
- path(string): URL path
- query(string): Raw query string (excluding- ?if empty)
- route(string): Registered Gin route path (e.g.- /api/:name)
- ip(string): Client IP address
- latency(duration): Time to handle request
- referer(string): Client's Referer header, if present
- user_agent(string): Client's User-Agent header
- headers(object): (Optional) All HTTP request headers, as a group—see- WithRequestHeaderand- WithHiddenRequestHeaders. Sensitive headers such as Authorization, Cookie, Set-Cookie, x-csrf-token, x-auth-token, x-xsrf-token are hidden by default.
- body_size(int): Size of the response body
Additional fields can be injected via WithContext.
Log level is determined by status code, per-route configuration, or explicit mapping (see below).
Creates a Gin middleware handler. All customization is done via options (see next section).
Retrieves the underlying *slog.Logger from Gin's context. Access this in your handlers for structured custom logging.
All the options below can be passed to SetLogger().
| Option | Description | 
|---|---|
| WithLogger(fn) | Inject a custom logger for each request: func(*gin.Context, *slog.Logger) *slog.Logger | 
| WithContext(fn) | Alter the log record per request: func(*gin.Context, *slog.Record) *slog.Record | 
| WithWriter(w io.Writer) | Set log output (default: gin.DefaultWriter; e.g.,os.Stdout) | 
| WithMessage(msg string) | Set a custom message for each log line (default: "Request") | 
| WithSkipPath([]string) | List of URL paths to skip logging | 
| WithSkipPathRegexps(...*regexp.Regexp) | Regexps to match paths to skip logging | 
| WithSkipper(fn) | Custom Skipper function: func(c *gin.Context) bool—returntrueto skip this request | 
| WithUTC(bool) | Use UTC instead of local time | 
| WithDefaultLevel(slog.Level) | Level for requests with status < 400 (default: Info) | 
| WithClientErrorLevel(slog.Level) | Level for 4xx (default: Warn) | 
| WithServerErrorLevel(slog.Level) | Level for 5xx (default: Error) | 
| WithPathLevel(map[string]slog.Level) | Map of URL paths to log levels | 
| WithSpecificLogLevelByStatusCode(map[int]slog.Level) | Set log level for specific status codes | 
| WithRequestHeader(enabled) | Enable or disable logging all HTTP request headers (except hidden ones) | 
| WithHiddenRequestHeaders([]string) | Set which request headers to hide from logs (default hides Authorization, Cookie, Set-Cookie, x-auth-token, x-csrf-token, x-xsrf-token). Case-insensitive. |