Documentation
¶
Overview ¶
Package logger provides HTTP request logging middleware for celeris.
New returns a celeris.HandlerFunc that logs each request with method, path, status, latency, bytes written, client IP, and request ID. All output is routed through Go's log/slog package; supply any slog.Handler via Config.Output, or use NewFastHandler for zero-alloc pooled-buffer output.
Two preset constructors cover the most common cases: CLFConfig produces Common Log Format style output; JSONConfig produces structured JSON via slog.JSONHandler. Both return a Config that can be further customised before passing to New.
Notable Config fields:
- CaptureRequestBody / CaptureResponseBody — log bodies, truncated to MaxCaptureBytes (default 4096).
- SensitiveHeaders — header values to redact; nil uses DefaultSensitiveHeaders.
- LogFormValues / SensitiveFormFields — log form fields with optional redaction; use DefaultSensitiveFormFields as a safe starting list.
- Skip / SkipPaths — bypass the middleware dynamically or for exact-match paths.
- Fields / Done — callbacks for custom attributes and post-log hooks.
- LogContextKeys — emit arbitrary context-store values as "ctx.<key>" attributes.
Register after the requestid middleware so request IDs are available in every log entry. When running behind a reverse proxy, install the proxy middleware via Server.Pre() before logger.New() so the real client IP is recorded.
Documentation ¶
Full guides and examples: https://goceleris.dev/docs/observability
Index ¶
- func DefaultSensitiveFormFields() []string
- func DefaultSensitiveHeaders() []string
- func New(config ...Config) celeris.HandlerFunc
- type Config
- type FastHandler
- func (h *FastHandler) Enabled(_ context.Context, level slog.Level) bool
- func (h *FastHandler) Handle(_ context.Context, r slog.Record) error
- func (h *FastHandler) HandleDirect(ts time.Time, level slog.Level, msg string, attrs []slog.Attr)
- func (h *FastHandler) WithAttrs(attrs []slog.Attr) slog.Handler
- func (h *FastHandler) WithGroup(name string) slog.Handler
- type FastHandlerOptions
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DefaultSensitiveFormFields ¶
func DefaultSensitiveFormFields() []string
DefaultSensitiveFormFields returns a copy of the default sensitive form field names. Unlike DefaultSensitiveHeaders, this list is NOT applied automatically — Config.SensitiveFormFields nil means no redaction. Pass this list explicitly when enabling Config.LogFormValues to get reasonable defaults:
logger.New(logger.Config{
LogFormValues: true,
SensitiveFormFields: logger.DefaultSensitiveFormFields(),
})
func DefaultSensitiveHeaders ¶
func DefaultSensitiveHeaders() []string
DefaultSensitiveHeaders returns a copy of the default sensitive header names redacted when Config.SensitiveHeaders is nil. Set SensitiveHeaders to an empty slice ([]string{}) to disable all redaction.
func New ¶
func New(config ...Config) celeris.HandlerFunc
New creates a logger middleware with the given config.
Example ¶
package main
import (
"github.com/goceleris/celeris/middleware/logger"
)
func main() {
// Zero-config: logs to slog.Default() with INFO/WARN/ERROR levels.
// DefaultSensitiveHeaders are redacted automatically.
_ = logger.New()
}
Output:
Example (CustomFields) ¶
package main
import (
"io"
"log/slog"
"time"
"github.com/goceleris/celeris"
"github.com/goceleris/celeris/middleware/logger"
)
func main() {
// Add custom fields to every log entry.
log := slog.New(slog.NewJSONHandler(io.Discard, nil))
_ = logger.New(logger.Config{
Output: log,
SkipPaths: []string{"/health", "/ready"},
Fields: func(c *celeris.Context, latency time.Duration) []slog.Attr {
return []slog.Attr{
slog.String("trace_id", c.Header("x-trace-id")),
slog.Bool("slow", latency > time.Second),
}
},
})
}
Output:
Example (DisableRedaction) ¶
package main
import (
"io"
"log/slog"
"github.com/goceleris/celeris/middleware/logger"
)
func main() {
// Pass an empty slice to disable all header redaction.
log := slog.New(slog.NewJSONHandler(io.Discard, nil))
_ = logger.New(logger.Config{
Output: log,
SensitiveHeaders: []string{},
})
}
Output:
Example (FastHandler) ¶
package main
import (
"log/slog"
"os"
"github.com/goceleris/celeris/middleware/logger"
)
func main() {
// FastHandler: zero-allocation structured logging with color output.
log := slog.New(logger.NewFastHandler(os.Stderr, &logger.FastHandlerOptions{
Color: true,
}))
_ = logger.New(logger.Config{Output: log})
}
Output:
Example (SensitiveHeaders) ¶
package main
import (
"io"
"log/slog"
"github.com/goceleris/celeris/middleware/logger"
)
func main() {
// Override the default sensitive headers list.
log := slog.New(slog.NewJSONHandler(io.Discard, nil))
_ = logger.New(logger.Config{
Output: log,
SensitiveHeaders: []string{
"Authorization",
"X-Api-Key",
"X-Internal-Token",
},
})
}
Output:
Types ¶
type Config ¶
type Config struct {
// Skip defines a function to conditionally bypass this middleware.
// When it returns true for a given request, no log entry is emitted
// and c.Next() is called directly.
Skip func(c *celeris.Context) bool
// Output is the slog.Logger used to emit log records.
// When nil, slog.Default() is used.
Output *slog.Logger
// Level maps an HTTP response status code to a slog.Level, controlling
// the severity of each log entry. The default maps 5xx to Error,
// 4xx to Warn, and everything else to Info.
Level func(status int) slog.Level
// Fields is an optional callback that returns additional slog.Attr
// values to include in each log entry. It is called after the
// downstream handler completes, so latency and response data are
// available.
Fields func(c *celeris.Context, latency time.Duration) []slog.Attr
// Done is an optional callback invoked after the log entry is written.
// It is always called, even when the log level is disabled, making it
// suitable for alerting on 5xx responses or collecting metrics outside
// the log pipeline.
Done func(c *celeris.Context, latency time.Duration, status int)
// SkipPaths is a list of request paths to exclude from logging.
// Matching is exact (no glob or prefix support).
SkipPaths []string
// CaptureRequestBody enables logging of the request body, truncated
// to MaxCaptureBytes. The body is emitted as the "request_body" attr.
CaptureRequestBody bool
// CaptureResponseBody enables logging of the response body, truncated
// to MaxCaptureBytes. The body is emitted as the "response_body" attr
// and requires the response to be captured via c.CaptureResponse().
CaptureResponseBody bool
// MaxCaptureBytes is the maximum number of body bytes to include in
// the log entry when CaptureRequestBody or CaptureResponseBody is
// enabled. Default: 4096. This cap prevents OOM on large payloads.
MaxCaptureBytes int
// SensitiveHeaders lists header names whose values should be redacted
// in log output. Matched values are replaced with "[REDACTED]" and
// header names are compared case-insensitively.
//
// When nil, DefaultSensitiveHeaders is used automatically. Set to an
// empty slice ([]string{}) to disable all header redaction.
SensitiveHeaders []string
// TimeZone sets the timezone applied to log timestamps before they
// are passed to the slog handler. When nil, the local timezone is
// used.
TimeZone *time.Location
// TimeFormat sets a custom Go time layout string (e.g. time.RFC3339)
// for FastHandler output. When empty, FastHandler uses its built-in
// zero-alloc RFC3339-millis formatter.
TimeFormat string
// LogHost includes the request Host header as the "host" slog attr.
// Disabled by default to keep log entries compact.
LogHost bool
// LogUserAgent includes the User-Agent request header as the
// "user_agent" slog attr. Disabled by default.
LogUserAgent bool
// LogReferer includes the Referer request header as the "referer"
// slog attr. Disabled by default.
LogReferer bool
// LogRoute includes the matched route pattern from c.FullPath() as
// the "route" slog attr. Omitted when the route is empty (e.g., 404).
LogRoute bool
// LogPID includes the process ID (cached at init) as the "pid" slog
// attr. Useful for distinguishing workers in multi-process deployments.
LogPID bool
// LogQueryParams includes the raw query string from the request URL
// as the "query" slog attr. Omitted when there is no query string.
LogQueryParams bool
// LogFormValues includes URL-encoded form field names and values as
// the "form" slog attr. Only active when the request content-type is
// application/x-www-form-urlencoded. Use SensitiveFormFields to
// redact secrets.
LogFormValues bool
// LogCookies includes cookie names (not values, for security) as the
// "cookies" slog attr. Values are intentionally omitted to avoid
// leaking session tokens into logs.
LogCookies bool
// LogBytesIn includes the request Content-Length as the "bytes_in"
// slog attr (int64). Omitted when the content length is negative or
// absent.
LogBytesIn bool
// LogScheme includes the request scheme from c.Scheme() (e.g.,
// "https") as the "scheme" slog attr.
LogScheme bool
// DisableColors overrides the Color flag on FastHandlerOptions. When
// true, the FastHandler emits plain text without ANSI escape codes.
// Useful for log files, CI pipelines, or any non-terminal output.
DisableColors bool
// LogResponseHeaders lists specific response header names whose values
// should be included in the log entry. Names are compared
// case-insensitively and each matched header is logged as
// "resp_header.<lowercased-name>".
LogResponseHeaders []string
// SensitiveFormFields lists form field names whose values should be
// redacted when LogFormValues is true. Matched values are replaced
// with "[REDACTED]" and field names are compared case-insensitively.
//
// IMPORTANT: Unlike SensitiveHeaders (where nil uses defaults), nil
// here means NO redaction at all — every form value is logged verbatim,
// including passwords and secrets. This asymmetry is intentional
// because form field names are application-specific.
//
// Use [DefaultSensitiveFormFields] for a reasonable starting list:
//
// SensitiveFormFields: logger.DefaultSensitiveFormFields()
//
// Semantics:
// - nil → no form field redaction (all values logged as-is).
// - []string{} (empty) → no form field redaction (same as nil).
// - non-empty → matching fields are replaced with "[REDACTED]".
SensitiveFormFields []string
// LogContextKeys lists context store keys whose values should be
// included in the log entry. For each key, the middleware calls
// c.Get(key) and, if found, emits the value as a "ctx.<key>" slog
// attr using fmt.Sprint for non-string types.
LogContextKeys []string
}
Config defines the logger middleware configuration.
func CLFConfig ¶
func CLFConfig() Config
CLFConfig returns a Config pre-configured for Common Log Format (CLF) style output. It enables LogHost, LogUserAgent, and LogReferer, and uses a Fields callback that emits a single "clf" attribute with the traditional combined log format line: host ident user time "method path proto" status bytes "referer" "user-agent".
func JSONConfig ¶
func JSONConfig() Config
JSONConfig returns a Config pre-configured for structured JSON output using slog.JSONHandler writing to os.Stdout. It enables LogHost, LogUserAgent, LogReferer, LogRoute, and LogQueryParams for comprehensive structured logging.
type FastHandler ¶
type FastHandler struct {
// contains filtered or unexported fields
}
FastHandler is a high-performance slog.Handler that formats log records directly into a pooled byte buffer with zero allocations in steady state. It produces output compatible with slog.TextHandler's format.
Use it as a drop-in replacement for slog.TextHandler when performance matters more than customization:
log := slog.New(logger.NewFastHandler(os.Stderr, nil))
mw := logger.New(logger.Config{Output: log})
func NewFastHandler ¶
func NewFastHandler(w io.Writer, opts *FastHandlerOptions) *FastHandler
NewFastHandler creates a new FastHandler writing to w.
When w is io.Discard, Handle / HandleDirect short-circuit the format-then-write path entirely — useful for benchmarks and for production deployments that want all the per-request middleware instrumentation (request_id propagation, latency capture, sensitive header redaction validation) but no log output.
func (*FastHandler) Enabled ¶
Enabled reports whether the handler handles records at the given level. When the underlying writer is io.Discard the handler reports false (independent of level) — there's no observable difference between "handle" and "skip" for a discard sink, and reporting false lets the logger middleware short-circuit its attr-building work entirely.
func (*FastHandler) HandleDirect ¶
HandleDirect formats a log record directly from a pre-built attrs slice, bypassing slog.Record entirely. This avoids the closure escape in Record.Attrs and the copy-before-write, eliminating 4 allocations compared to the standard Handle path.
type FastHandlerOptions ¶
type FastHandlerOptions struct {
// Level is the minimum log level. Default: slog.LevelInfo.
Level slog.Level
// Color enables ANSI color codes in output.
// Level names are colored: red=ERROR, yellow=WARN, green=INFO, cyan=DEBUG.
Color bool
// TimeFormat sets a custom Go time layout (e.g. time.RFC3339).
// When empty, the built-in RFC3339-millis formatter is used.
TimeFormat string
}
FastHandlerOptions configures a FastHandler.