logger

package
v1.5.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 24, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

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

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()
}
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),
			}
		},
	})
}
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{},
	})
}
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})
}
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",
		},
	})
}

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

func (h *FastHandler) Enabled(_ context.Context, level slog.Level) bool

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) Handle

func (h *FastHandler) Handle(_ context.Context, r slog.Record) error

Handle formats the record and writes it to the output.

func (*FastHandler) HandleDirect

func (h *FastHandler) HandleDirect(ts time.Time, level slog.Level, msg string, attrs []slog.Attr)

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.

func (*FastHandler) WithAttrs

func (h *FastHandler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs returns a new handler with the given attributes pre-formatted.

func (*FastHandler) WithGroup

func (h *FastHandler) WithGroup(name string) slog.Handler

WithGroup returns a new handler with the given group name prepended to all subsequent attribute keys.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL