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
- Request Start: The
Loggermiddleware records start time and basic info inLogBegin() - Process Request: Calls the next handler to process the request
- Request End:
- If no error, calls
LogEnd(c, nil)in the middleware to log - If error, needs to call
LogEnd(c, err)in theErrorHandlerto log
- If no error, calls
- 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)))
}