Slim

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 middlewares.Use(middleware.Logger())    // Logger will automatically log request IDs.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.2msSpot 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 3msAdditional 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 firsts.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 handlers.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

Navigate Select esc Close search