Skip to main content
Lightning ships two middleware functions out of the box: Logger for request logging and Recovery for panic recovery. You can register them individually or get both at once via DefaultApp.

Logger

Logger() logs a single line for every completed request. It calls ctx.Next() first, so the log entry is written after the handler finishes, which means the status code and duration are accurate. Each log line contains the following fields in order:
FieldExample
Remote address127.0.0.1:54321
HTTP methodGET
Status code200
Path/api/users
Duration3ms
User agentcurl/8.7.1

Register Logger

app.Use(lightning.Logger())

Example output

[my-service] INFO 127.0.0.1:54321 GET 200 /api/users 3ms curl/8.7.1

Recovery

Recovery() wraps the downstream handler chain in a defer/recover block. When a panic occurs, it:
  1. Writes the panic value and full stack trace to stderr.
  2. Responds to the client with HTTP 500 Internal Server Error.

Register Recovery with the default handler

app.Use(lightning.Recovery())

Register Recovery with a custom panic handler

Pass a single HandlerFunc to Recovery to replace the default 500 response with your own logic:
app.Use(lightning.Recovery(func(ctx *lightning.Context) {
    ctx.JSON(lightning.StatusInternalServerError, lightning.Map{
        "error": "something went wrong",
    })
}))
The panic value and stack trace are always written to stderr by Recovery, regardless of whether you supply a custom handler. Your custom handler only controls the HTTP response sent to the client.

Custom handler that logs to a structured logger

If your application uses a structured logger, you can capture the panic context and forward it before writing the response:
package main

import (
    "log/slog"
    "os"

    "github.com/go-labx/lightning"
)

func main() {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

    panicHandler := func(ctx *lightning.Context) {
        // At this point the panic has already been recovered by Lightning.
        // Log a structured error and return a JSON response.
        logger.Error("panic recovered",
            "method", ctx.Method,
            "path",   ctx.Path,
        )

        ctx.JSON(lightning.StatusInternalServerError, lightning.Map{
            "error":  "internal server error",
            "method": ctx.Method,
            "path":   ctx.Path,
        })
    }

    app := lightning.NewApp(&lightning.Config{AppName: "my-service"})
    app.Use(lightning.Logger())
    app.Use(lightning.Recovery(panicHandler))

    app.Get("/panic", func(ctx *lightning.Context) {
        panic("something unexpected")
    })

    app.Run(":8080")
}

DefaultApp

DefaultApp() is the quickest way to get a fully configured application with both middleware already registered:
app := lightning.DefaultApp()
This is exactly equivalent to:
app := lightning.NewApp()
app.Use(lightning.Logger())
app.Use(lightning.Recovery())

Composing middleware manually

When you need DefaultApp-style setup but also want a custom Config, use NewApp and call app.Use yourself:
package main

import "github.com/go-labx/lightning"

func main() {
    app := lightning.NewApp(&lightning.Config{
        AppName:     "orders-api",
        EnableDebug: true,
    })

    app.Use(lightning.Logger())
    app.Use(lightning.Recovery())

    app.Get("/orders", func(ctx *lightning.Context) {
        ctx.JSON(lightning.StatusOK, lightning.Map{"orders": []string{}})
    })

    app.Run(":8080")
}
Middleware runs in the order you register it. Because Logger calls ctx.Next() before writing its log line, placing Logger before Recovery means the log entry correctly reflects any status code set by the panic handler. Register Logger first, then Recovery.