Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/go-kratos/kratos/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The recovery middleware catches panics that occur during request processing and converts them into proper error responses. This prevents the entire service from crashing when a panic occurs in a handler.

Installation

go get github.com/go-kratos/kratos/v2/middleware/recovery

Basic Usage

The Recovery function creates a panic recovery middleware:
func Recovery(opts ...Option) middleware.Middleware

Server Example

import (
    "github.com/go-kratos/kratos/v2"
    "github.com/go-kratos/kratos/v2/middleware/recovery"
    "github.com/go-kratos/kratos/v2/transport/http"
    "github.com/go-kratos/kratos/v2/transport/grpc"
)

func main() {
    // Create HTTP server with recovery
    httpSrv := http.NewServer(
        http.Address(":8000"),
        http.Middleware(
            recovery.Recovery(),
        ),
    )
    
    // Create gRPC server with recovery
    grpcSrv := grpc.NewServer(
        grpc.Address(":9000"),
        grpc.Middleware(
            recovery.Recovery(),
        ),
    )
    
    app := kratos.New(
        kratos.Server(httpSrv, grpcSrv),
    )
    
    if err := app.Run(); err != nil {
        log.Fatal(err)
    }
}
Always place the recovery middleware first in the middleware chain to ensure it catches panics from all other middleware and handlers.
http.Middleware(
    recovery.Recovery(),    // Must be first!
    tracing.Server(),
    logging.Server(logger),
    metrics.Server(),
)

Default Behavior

When a panic occurs, the middleware:
  1. Recovers from the panic
  2. Logs the panic with stack trace
  3. Records the latency in context
  4. Returns an ErrUnknownRequest error

Default Error

var ErrUnknownRequest = errors.InternalServer("UNKNOWN", "unknown request error")
This returns:
  • HTTP status: 500 Internal Server Error
  • gRPC code: INTERNAL

Custom Recovery Handler

You can provide a custom handler to process panics:
type HandlerFunc func(ctx context.Context, req, err any) error

WithHandler Option

func WithHandler(h HandlerFunc) Option

Usage Example

import (
    "context"
    "fmt"
    
    "github.com/go-kratos/kratos/v2/errors"
    "github.com/go-kratos/kratos/v2/log"
    "github.com/go-kratos/kratos/v2/middleware/recovery"
)

// Custom recovery handler
func customRecoveryHandler(ctx context.Context, req, err any) error {
    // Log the panic
    log.Context(ctx).Errorf("Panic recovered: %v", err)
    
    // Get latency from context
    if latency, ok := ctx.Value(recovery.Latency{}).(float64); ok {
        log.Context(ctx).Infof("Request latency: %.3fs", latency)
    }
    
    // Return custom error based on panic type
    switch e := err.(type) {
    case string:
        if e == "not found" {
            return errors.NotFound("RESOURCE_NOT_FOUND", "resource not found")
        }
        return errors.InternalServer("PANIC", fmt.Sprintf("panic: %s", e))
    case error:
        return errors.InternalServer("PANIC", e.Error())
    default:
        return errors.InternalServer("PANIC", fmt.Sprintf("%v", e))
    }
}

// Use custom handler
recovery.Recovery(
    recovery.WithHandler(customRecoveryHandler),
)

Latency Context

The middleware stores request latency in the context when a panic occurs:
type Latency struct{}
You can retrieve the latency in your custom handler:
func customHandler(ctx context.Context, req, err any) error {
    // Get latency
    if latency, ok := ctx.Value(recovery.Latency{}).(float64); ok {
        fmt.Printf("Request took %.3f seconds before panic\n", latency)
    }
    
    return errors.InternalServer("PANIC", "panic occurred")
}

Stack Trace Logging

The middleware automatically logs the full stack trace when a panic occurs:
log.Context(ctx).Errorf("%v: %+v\n%s\n", rerr, req, buf)

Example Log Output

ERROR: runtime error: index out of range [5] with length 3: {"id":"123"}
goroutine 123 [running]:
github.com/go-kratos/kratos/v2/middleware/recovery.Recovery.func1.1()
    /go/pkg/mod/github.com/go-kratos/kratos/v2/middleware/recovery/recovery.go:54
panic({0x1234567, 0xc0001a4000})
    /usr/local/go/src/runtime/panic.go:890
main.(*service).GetUser(...)
    /app/service/user.go:42
...

Complete Example

package main

import (
    "context"
    "fmt"
    "log"
    
    "github.com/go-kratos/kratos/v2"
    "github.com/go-kratos/kratos/v2/errors"
    kratoslog "github.com/go-kratos/kratos/v2/log"
    "github.com/go-kratos/kratos/v2/middleware/logging"
    "github.com/go-kratos/kratos/v2/middleware/recovery"
    "github.com/go-kratos/kratos/v2/transport/http"
)

// Custom panic handler with detailed logging
func panicHandler(logger kratoslog.Logger) recovery.HandlerFunc {
    return func(ctx context.Context, req, err any) error {
        // Log panic details
        helper := kratoslog.NewHelper(logger)
        
        // Get latency
        var latency float64
        if v, ok := ctx.Value(recovery.Latency{}).(float64); ok {
            latency = v
        }
        
        // Log with context
        helper.Errorw(
            "kind", "panic",
            "panic", fmt.Sprintf("%v", err),
            "request", fmt.Sprintf("%+v", req),
            "latency", latency,
        )
        
        // Send alert (e.g., to Sentry, PagerDuty)
        // sendAlert(ctx, err)
        
        // Return appropriate error
        return errors.InternalServer(
            "INTERNAL_ERROR",
            "An internal error occurred. Please try again later.",
        )
    }
}

func main() {
    // Create logger
    logger := kratoslog.NewStdLogger(os.Stdout)
    
    // Create HTTP server with recovery
    httpSrv := http.NewServer(
        http.Address(":8000"),
        http.Middleware(
            recovery.Recovery(
                recovery.WithHandler(panicHandler(logger)),
            ),
            logging.Server(logger),
        ),
    )
    
    app := kratos.New(
        kratos.Name("recovery-example"),
        kratos.Logger(logger),
        kratos.Server(httpSrv),
    )
    
    if err := app.Run(); err != nil {
        log.Fatal(err)
    }
}

Testing Recovery Middleware

package main

import (
    "context"
    "testing"
    
    "github.com/go-kratos/kratos/v2/middleware"
    "github.com/go-kratos/kratos/v2/middleware/recovery"
)

func TestRecovery(t *testing.T) {
    // Handler that panics
    panicHandler := func(ctx context.Context, req any) (any, error) {
        panic("test panic")
    }
    
    // Apply recovery middleware
    handler := recovery.Recovery()(panicHandler)
    
    // Call handler
    reply, err := handler(context.Background(), "test")
    
    // Should not panic, should return error
    if err == nil {
        t.Error("expected error, got nil")
    }
    if reply != nil {
        t.Errorf("expected nil reply, got %v", reply)
    }
}

func TestCustomHandler(t *testing.T) {
    customErr := errors.BadRequest("CUSTOM", "custom error")
    
    // Custom handler
    customHandler := func(ctx context.Context, req, err any) error {
        return customErr
    }
    
    // Handler that panics
    panicHandler := func(ctx context.Context, req any) (any, error) {
        panic("test panic")
    }
    
    // Apply recovery with custom handler
    handler := recovery.Recovery(
        recovery.WithHandler(customHandler),
    )(panicHandler)
    
    // Call handler
    _, err := handler(context.Background(), "test")
    
    // Should return custom error
    if err != customErr {
        t.Errorf("expected custom error, got %v", err)
    }
}

Best Practices

Every production service should use recovery middleware to prevent crashes. Place it first in the middleware chain.
http.Middleware(
    recovery.Recovery(), // First!
    // ... other middleware
)
Implement a custom handler that sends alerts when panics occur. This helps you identify and fix bugs quickly.
func alertingHandler(ctx context.Context, req, err any) error {
    sendToSentry(err)
    notifyPagerDuty("panic occurred")
    return errors.InternalServer("PANIC", "internal error")
}
Always log the request, panic value, stack trace, and any relevant context when a panic occurs.
Never return stack traces or internal details to clients. Use generic error messages.
// Good
return errors.InternalServer("INTERNAL", "an error occurred")

// Bad
return errors.InternalServer("PANIC", fmt.Sprintf("%+v", panicValue))
Track panic frequency and rate to identify problematic code paths.
func metricsHandler(ctx context.Context, req, err any) error {
    panicCounter.Inc()
    return errors.InternalServer("PANIC", "internal error")
}
Write tests that verify your service handles panics gracefully.

Common Panic Scenarios

The recovery middleware helps protect against common panic scenarios:

Nil Pointer Dereference

func (s *service) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
    user := s.findUser(req.Id) // May return nil
    return user, nil             // Panics if user.Name accessed
}

Index Out of Range

func (s *service) GetItem(ctx context.Context, req *pb.GetItemRequest) (*pb.Item, error) {
    items := s.getItems()
    return items[req.Index], nil // Panics if index >= len(items)
}

Type Assertion Failure

func (s *service) Process(ctx context.Context, req any) (any, error) {
    data := req.(string) // Panics if req is not a string
    return process(data), nil
}

Channel Operations

func (s *service) Send(ctx context.Context, msg string) error {
    s.ch <- msg // Panics if channel is closed
    return nil
}

Source Reference

The recovery middleware implementation can be found in:
  • middleware/recovery/recovery.go:36 - Recovery middleware
  • middleware/recovery/recovery.go:19 - HandlerFunc type
  • middleware/recovery/recovery.go:13 - Latency context key
  • middleware/recovery/recovery.go:16 - ErrUnknownRequest

Next Steps

Logging

Add logging to track panic details

Metrics

Monitor service reliability