Skip to main content

Logger Middleware

The Logger middleware logs the start and end of each request, including useful data about the request, response status, processing time, and more.

Usage

Basic Usage

import (
"go-slim.dev/slim"
"go-slim.dev/slim/middleware"
)

func main() {
s := slim.New()

// Configure error handler to log errors
s.ErrorHandler = func(c slim.Context, err error) error {
middleware.LogEnd(c, err) // Ensure errors are logged
return err
}

// Logger should come before Recovery
s.Use(middleware.Logger())
s.Use(slim.Recovery())

s.GET("/", func(c slim.Context) error {
return c.String(200, "Hello, World!")
})

s.Start(":8080")
}

Custom Configuration

s := slim.New()

s.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
TimeLayout: "2006-01-02 15:04:05",
NewEntry: func(c slim.Context) middleware.LogEntry {
return middleware.NewLogEntry(os.Stdout)
},
}))

Configuration Options

LoggerConfig

type LoggerConfig struct {
// TimeLayout defines the time format
// Optional. Default value "2006/01/02 15:04:05.000"
TimeLayout string

// NewEntry creates a log entry for each request
// Optional. Defaults to standard output
NewEntry func(slim.Context) LogEntry
}

LogEntry Interface

type LogEntry interface {
Begin(c slim.Context) map[string]any
End(c slim.Context, err error) map[string]any
SetTimeLayout(layout string)
SetColorable(colorable bool)
Print(c slim.Context, p LogPayload)
Panic(v any, stack []byte)
}

LogPayload Structure

type LogPayload struct {
StartTime time.Time // Request start time
Proto string // HTTP protocol version
RequestURI string // Request URI
RawQuery string // Query string
Method string // HTTP method
RemoteAddr string // Client address
Extra map[string]any // Extra data
Error error // Error information
StatusCode int // Response status code
Written int // Response bytes
Elapsed time.Duration // Processing time
}

Log Output Format

Default log output includes:

2006/01/02 15:04:05.000 [request-id] "GET /api/users HTTP/1.1" from 127.0.0.1:54321 - 200 1234B in 45ms

Log format components:

  • Timestamp (configurable format)
  • Request ID (if present)
  • HTTP method and request path
  • Client address
  • Response status code (with color)
  • Response size
  • Processing time (with color)

Examples

1. Basic Logging

s := slim.New()

s.ErrorHandler = func(c slim.Context, err error) error {
middleware.LogEnd(c, err)
return err
}

s.Use(middleware.Logger())

s.GET("/api/users", func(c slim.Context) error {
return c.JSON(200, map[string]string{"message": "success"})
})

Output example:

2024/11/08 10:30:15.123 "GET /api/users HTTP/1.1" from 127.0.0.1:54321 - 200 28B in 2.5ms

2. Custom Time Format

s.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
TimeLayout: "2006-01-02 15:04:05",
}))

Output example:

2024-11-08 10:30:15 "GET /api/users HTTP/1.1" from 127.0.0.1:54321 - 200 28B in 2.5ms

3. Custom Log Entry

type customLogEntry struct {
*middleware.defaultLogEntry
logger *log.Logger
}

func (c *customLogEntry) Begin(ctx slim.Context) map[string]any {
// Log additional info at request start
return map[string]any{
"user_agent": ctx.Request().UserAgent(),
}
}

func (c *customLogEntry) End(ctx slim.Context, err error) map[string]any {
// Log additional info at request end
extra := make(map[string]any)
if userID := ctx.Get("user_id"); userID != nil {
extra["user_id"] = userID
}
return extra
}

s.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
NewEntry: func(c slim.Context) middleware.LogEntry {
return &customLogEntry{
logger: log.New(os.Stdout, "", 0),
}
},
}))

4. Add Request ID

s.Use(middleware.RequestID()) // Add request ID middleware
s.Use(middleware.Logger()) // Logger will automatically log request ID

s.GET("/api/users", func(c slim.Context) error {
return c.JSON(200, map[string]string{"message": "success"})
})

Output example:

2024/11/08 10:30:15.123 [abc123-def456] "GET /api/users HTTP/1.1" from 127.0.0.1:54321 - 200 28B in 2.5ms

5. Log Error Information

s.ErrorHandler = func(c slim.Context, err error) error {
middleware.LogEnd(c, err) // Log the error
return c.JSON(500, map[string]string{"error": err.Error()})
}

s.Use(middleware.Logger())

s.GET("/api/error", func(c slim.Context) error {
return errors.New("something went wrong")
})

Output example:

2024/11/08 10:30:15.123 "GET /api/error HTTP/1.1" from 127.0.0.1:54321 - 500 35B in 1.2ms

Spot a mistake:
something went wrong

6. Log Additional Data

s.Use(middleware.Logger())

s.GET("/api/users/:id", func(c slim.Context) error {
// Add extra info to logs
payload, _ := middleware.GetLogPayload(c)
if payload.Extra == nil {
payload.Extra = make(map[string]any)
}
payload.Extra["user_id"] = c.Param("id")
middleware.ProvideLogPayload(c, payload)

return c.JSON(200, map[string]string{"id": c.Param("id")})
})

Output example:

2024/11/08 10:30:15.123 "GET /api/users/123 HTTP/1.1" from 127.0.0.1:54321 - 200 15B in 3ms

Additional data:
{
"user_id": "123"
}

Color Output

The Logger middleware automatically uses colored output in TTY environments:

  • Status code colors:

    • 1xx (Informational): Blue
    • 2xx (Success): Green
    • 3xx (Redirection): Cyan
    • 4xx (Client Error): Yellow
    • 5xx (Server Error): Red
  • Elapsed time colors:

    • < 500ms: Green
    • 500ms - 5s: Yellow
    • 5s: Red

How It Works

  1. Request Start: The Logger middleware records start time and basic info in LogBegin()
  2. Process Request: Calls the next handler to process the request
  3. Request End:
    • If no error, calls LogEnd(c, nil) in the middleware to log
    • If error, needs to call LogEnd(c, err) in the ErrorHandler to log
  4. Output Log: The Print() method formats and outputs the log

Middleware Order

Important: Logger should come before Recovery to ensure panics are properly logged:

s.ErrorHandler = func (c slim.Context, err error) error {
middleware.LogEnd(c, err) // Log in error handler
return err
}
s.Use(middleware.Logger()) // Logger first
s.Use(slim.Recovery()) // Recovery after

Best Practices

1. Configure Error Handler

s.ErrorHandler = func(c slim.Context, err error) error {
middleware.LogEnd(c, err)

// Return different responses based on error type
if he, ok := err.(*slim.HTTPError); ok {
return c.JSON(he.Code, he)
}
return c.JSON(500, map[string]string{"error": "Internal Server Error"})
}

2. Use Structured Logging

type structuredLogEntry struct {
logger *slog.Logger
}

func (s *structuredLogEntry) Print(c slim.Context, p middleware.LogPayload) {
s.logger.Info("request completed",
slog.String("method", p.Method),
slog.String("uri", p.RequestURI),
slog.Int("status", p.StatusCode),
slog.Duration("elapsed", p.Elapsed),
slog.String("remote_addr", p.RemoteAddr),
)
}

s.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
NewEntry: func(c slim.Context) middleware.LogEntry {
return &structuredLogEntry{
logger: slog.New(slog.NewJSONHandler(os.Stdout, nil)),
}
},
}))

3. Adjust Log Level by Environment

func newLogger() slim.MiddlewareFunc {
if os.Getenv("ENV") == "production" {
// Production: use structured logging
return middleware.LoggerWithConfig(middleware.LoggerConfig{
NewEntry: func(c slim.Context) middleware.LogEntry {
return newStructuredLogger()
},
})
}
// Development: use default colored logging
return middleware.Logger()
}

s.Use(newLogger())

4. Log Slow Requests

s.Use(middleware.Logger())

s.Use(func(c slim.Context, next slim.HandlerFunc) error {
start := time.Now()
err := next(c)
elapsed := time.Since(start)

// Log slow requests (> 1 second)
if elapsed > time.Second {
log.Printf("SLOW REQUEST: %s %s took %v",
c.Request().Method,
c.Request().URL.Path,
elapsed)
}
return err
})

Advanced Features

LogBegin and LogEnd

You can manually control logging:

// Manually log in handler
s.GET("/api/custom", func(c slim.Context) error {
// Begin logging
middleware.LogBegin(c)

// Process request
result := doSomething()

// End logging
middleware.LogEnd(c, nil)

return c.JSON(200, result)
})

Get and Set Log Data

s.Use(middleware.Logger())

s.GET("/api/users", func(c slim.Context) error {
// Get log payload
payload, ok := middleware.GetLogPayload(c)
if ok {
// Add custom data
if payload.Extra == nil {
payload.Extra = make(map[string]any)
}
payload.Extra["custom_field"] = "custom_value"

// Update log payload
middleware.ProvideLogPayload(c, payload)
}

return c.JSON(200, map[string]string{"message": "success"})
})

Integration with Other Logging Libraries

Integration with slog

import (
"log/slog"
"go-slim.dev/slim/middleware"
)

type slogEntry struct {
logger *slog.Logger
}

func (s *slogEntry) Begin(c slim.Context) map[string]any { return nil }
func (s *slogEntry) End(c slim.Context, err error) map[string]any { return nil }
func (s *slogEntry) SetTimeLayout(layout string) {}
func (s *slogEntry) SetColorable(colorable bool) {}

func (s *slogEntry) Print(c slim.Context, p middleware.LogPayload) {
attrs := []slog.Attr{
slog.String("method", p.Method),
slog.String("uri", p.RequestURI),
slog.Int("status", p.StatusCode),
slog.Duration("elapsed", p.Elapsed),
}

if p.Error != nil {
s.logger.LogAttrs(context.Background(), slog.LevelError, "request failed", attrs...)
} else {
s.logger.LogAttrs(context.Background(), slog.LevelInfo, "request completed", attrs...)
}
}

func (s *slogEntry) Panic(v any, stack []byte) {
s.logger.Error("panic recovered",
slog.Any("panic", v),
slog.String("stack", string(stack)))
}

References