Slim

Recovery Middleware

The Recovery middleware catches panics in your application, prevents server crashes, and gracefully passes errors to the centralized error handler.

Usage

Basic Usage

import (    "go-slim.dev/slim"    "go-slim.dev/slim/middleware")func main() {    s := slim.New()    // Recovery should come after Logger    s.Use(middleware.Logger())    s.Use(middleware.Recovery())    s.GET("/panic", func(c slim.Context) error {        panic("something went wrong!")    })    s.Start(":8080")}

Custom Configuration

s := slim.New()s.Use(middleware.RecoveryWithConfig(middleware.RecoveryConfig{    StackSize:         4 << 10,  // 4 KB    DisableStackAll:   false,    DisablePrintStack: false,}))

Configuration Options

RecoveryConfig

type RecoveryConfig struct {    // StackSize defines the size of the stack to be printed    // Optional. Default value 4KB    StackSize int    // DisableStackAll disables formatting stack traces of all other goroutines    // Optional. Default value false    DisableStackAll bool    // DisablePrintStack disables printing stack trace    // Optional. Default value false    DisablePrintStack bool}

Examples

1. Basic Panic Recovery

s := slim.New()s.Use(middleware.Logger())s.Use(middleware.Recovery())s.GET("/panic", func(c slim.Context) error {    panic("unexpected error!")})s.Start(":8080")

When accessing /panic:

  • Recovery middleware catches the panic
  • Prints colored stack trace to stderr
  • Returns 500 Internal Server Error
  • Server continues running

2. Disable Stack Printing

s.Use(middleware.RecoveryWithConfig(middleware.RecoveryConfig{    DisablePrintStack: true, // Don't print stack info}))

Suitable for production environments to avoid leaking sensitive information.

3. Print Only Current Goroutine Stack

s.Use(middleware.RecoveryWithConfig(middleware.RecoveryConfig{    DisableStackAll: true, // Only print current goroutine stack}))

Reduces log output and improves readability.

4. Custom Stack Size

s.Use(middleware.RecoveryWithConfig(middleware.RecoveryConfig{    StackSize: 8 << 10, // 8 KB for deeper call stacks}))

5. Integration with Logger Middleware

s := slim.New()s.ErrorHandler = func(c slim.Context, err error) error {    middleware.LogEnd(c, err)    // Custom error response    return c.JSON(500, map[string]string{        "error": "Internal Server Error",    })}// Logger should come before Recoverys.Use(middleware.Logger())s.Use(middleware.Recovery())s.GET("/panic", func(c slim.Context) error {    panic("oops!")})

Output example:

2024/11/08 10:30:15.123 "GET /panic HTTP/1.1" from 127.0.0.1:54321 - 500 35B in 1.2ms panic: oops! -> github.com/your/project/handler.PanicHandler      /path/to/handler.go:123    main.main      /path/to/main.go:45

6. Handle Specific Panic Types

s.Use(middleware.Recovery())s.Use(func(c slim.Context, next slim.HandlerFunc) error {    defer func() {        if r := recover(); r != nil {            // Handle specific panic types            if err, ok := r.(error); ok {                c.Error(err)                return            }            // Re-throw other panics            panic(r)        }    }()    return next(c)})

Stack Trace Format

Recovery middleware provides beautified stack trace output:

Colored Output (TTY)

 panic: something went wrong! -> go-slim.dev/slim/middleware.panicHandler      /path/to/middleware.go:123    go-slim.dev/slim.(*Context).Next      /path/to/context.go:456    main.main      /path/to/main.go:789

Color legend:

  • Red arrow (->): Where the panic occurred
  • Purple: Package name
  • Red: Function name (panic location)
  • Green: Function name (other locations)
  • Cyan: File name
  • Green: Line number

Plain Text Output (Non-TTY)

In non-TTY environments (e.g., log files), output does not include color codes.

How It Works

  1. Catch Panic: Uses defer recover() to catch panics in the handler chain
  2. Collect Stack: Calls runtime.Stack() to get stack trace
  3. Format Output: Beautifies stack information and outputs to log
  4. Set Response:
    • If response not written, sets 500 status code
    • If WebSocket upgrade, does not modify response
  5. Continue Running: Server continues processing other requests

Middleware Order

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

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

Panics Not Recovered

Recovery middleware will not recover the following panics:

http.ErrAbortHandler

s.GET("/abort", func(c slim.Context) error {    panic(http.ErrAbortHandler) // Will not be recovered})

http.ErrAbortHandler is a special panic used to abort responses. Recovery will re-throw it.

Best Practices

1. Always Use Recovery Middleware

s := slim.New()// After all other middleware (except Logger)s.Use(middleware.Logger())s.Use(middleware.Recovery())

2. Production Environment Configuration

func newRecoveryMiddleware() slim.MiddlewareFunc {    if os.Getenv("ENV") == "production" {        return middleware.RecoveryWithConfig(middleware.RecoveryConfig{            DisablePrintStack: true, // Don't print stack in production        })    }    return middleware.Recovery() // Print full stack in development}s.Use(newRecoveryMiddleware())

3. Centralized Error Handling

s.ErrorHandler = func(c slim.Context, err error) error {    middleware.LogEnd(c, err)    // Differentiate error types    if he, ok := err.(*slim.HTTPError); ok {        return c.JSON(he.Code, he)    }    // Hide internal error details    return c.JSON(500, map[string]string{        "error": "Internal Server Error",        "id":    c.Header(slim.HeaderXRequestID),    })}

4. Log Panics to Monitoring Systems

type customLogEntry struct {    logger *slog.Logger}func (c *customLogEntry) Panic(v any, stack []byte) {    // Log to logging system    c.logger.Error("panic recovered",        slog.Any("panic", v),        slog.String("stack", string(stack)))    // Send to monitoring system (e.g., Sentry)    sentry.CaptureException(fmt.Errorf("panic: %v", v))    // Print to stderr    middleware.PrintPrettyStack(v, stack)}s.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{    NewEntry: func(c slim.Context) middleware.LogEntry {        return &customLogEntry{            logger: slog.Default(),        }    },}))s.Use(middleware.Recovery())

5. Graceful Degradation

s.GET("/api/data", func(c slim.Context) error {    defer func() {        if r := recover(); r != nil {            // Log error            log.Printf("Error processing request: %v", r)            // Return cached data or default value            c.JSON(200, getCachedData())        }    }()    // Code that might panic    data := fetchDataFromExternalAPI()    return c.JSON(200, data)})

Integration with Other Monitoring Tools

Integration with Sentry

import (    "github.com/getsentry/sentry-go"    "go-slim.dev/slim/middleware")func init() {    sentry.Init(sentry.ClientOptions{        Dsn: os.Getenv("SENTRY_DSN"),    })}s.Use(func(c slim.Context, next slim.HandlerFunc) error {    defer func() {        if r := recover(); r != nil {            // Send to Sentry            sentry.CaptureException(fmt.Errorf("panic: %v", r))            // Re-throw to let Recovery middleware handle            panic(r)        }    }()    return next(c)})s.Use(middleware.Recovery())

Custom Panic Handler

func customRecovery() slim.MiddlewareFunc {    return func(c slim.Context, next slim.HandlerFunc) error {        defer func() {            if r := recover(); r != nil {                // Get stack                stack := make([]byte, 4<<10)                n := runtime.Stack(stack, false)                stack = stack[:n]                // Log                log.Printf("PANIC: %v\n%s", r, stack)                // Send alert                sendAlert(fmt.Sprintf("Panic: %v", r))                // Return error response                if !c.Response().Written() {                    c.JSON(500, map[string]string{                        "error": "Internal Server Error",                    })                }            }        }()        return next(c)    }}s.Use(customRecovery())

Testing Recovery Middleware

func TestRecovery(t *testing.T) {    s := slim.New()    s.Use(middleware.Recovery())    s.GET("/panic", func(c slim.Context) error {        panic("test panic")    })    req := httptest.NewRequest("GET", "/panic", nil)    rec := httptest.NewRecorder()    s.ServeHTTP(rec, req)    // Verify response status code    if rec.Code != 500 {        t.Errorf("expected 500, got %d", rec.Code)    }}

Default Configuration

DefaultRecoveryConfig = RecoveryConfig{    StackSize:         4 << 10,  // 4 KB    DisableStackAll:   false,    DisablePrintStack: false,}

Performance Considerations

  1. Stack Collection: Collecting stack information has some overhead, but only triggers on panic
  2. Stack Size: Default 4KB is sufficient for most cases, can be increased for deep call stacks
  3. Log Output: Beautifying output requires parsing stack, but doesn’t significantly impact performance

Security Considerations

  1. Don’t expose stack information in production:

    s.Use(middleware.RecoveryWithConfig(middleware.RecoveryConfig{    DisablePrintStack: true, // Production}))
  2. Avoid leaking sensitive information:

    s.ErrorHandler = func(c slim.Context, err error) error {    middleware.LogEnd(c, err)    // Don't return error details directly    return c.JSON(500, map[string]string{        "error": "Internal Server Error",    })}

Common Questions

Q: Can Recovery catch panics in async code?

A: No. Recovery can only catch panics in the current goroutine. Async code needs separate handling:

s.GET("/async", func(c slim.Context) error {    go func() {        defer func() {            if r := recover(); r != nil {                log.Printf("Panic in goroutine: %v", r)            }        }()        // Async code        panic("async panic")    }()    return c.String(200, "OK")})

Q: Why should Recovery come after Logger?

A: This ensures panics are properly logged. If Recovery comes before Logger, Logger may not be able to log panic information.

Q: Can I customize panic responses?

A: Yes. Recovery only catches the panic and sets a 500 status code. The actual response content is determined by the error handler:

s.ErrorHandler = func(c slim.Context, err error) error {    return c.JSON(500, customErrorResponse)}

References

Navigate Select esc Close search