logging

package module
v0.0.12 Latest Latest
Warning

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

Go to latest
Published: Dec 26, 2025 License: MIT Imports: 19 Imported by: 7

README

Station Manager: logging package

A thin, concurrency-safe wrapper around rs/zerolog with a structured-first API, safe lifecycle (Initialize/Close), file rotation via lumberjack, and rich error history enrichment.

Highlights

  • Structured logs only: compose fields with typed helpers
  • Context loggers via With() for request/job scoping
  • Graceful shutdown: waits for in-flight logs with a bounded timeout
  • File rotation (size/age/backups) and optional console formatting
  • Error history enrichment: automatically logs full error chains and operations

Quick start

svc := &logging.Service{ConfigService: cfgSvc}
if err := svc.Initialize(); err != nil { panic(err) }
defer svc.Close()

svc.InfoWith().Str("user_id", id).Int("count", 3).Msg("processed")

req := svc.With().Str("request_id", reqID).Logger()
req.DebugWith().Str("path", path).Msg("incoming request")

// Error history enrichment
if err != nil {
    svc.ErrorWith().Err(err).Msg("operation failed")
}

Error history enrichment

When you attach an error with Err/AnErr, the logger emits:

  • error: the standard zerolog error field (string)
  • error_chain: array of messages from outermost -> root
  • error_root: the root cause message
  • error_history: the joined chain string (outer -> ... -> root)
  • error_ops: array of operation identifiers per chain element (if using DetailedError; empty strings for non-DetailedError links)
  • error_root_op: the root operation identifier (if available)

For AnErr("db_err", err), the keys are prefixed accordingly (db_err_chain, db_err_root, db_err_history, db_err_ops, db_err_root_op).

Example output (JSON, abbreviated):

{
  "level":"error",
  "msg":"operation failed",
  "error":"startup failed",
  "error_chain":[
    "startup failed",
    "failed to connect to database",
    "dial tcp 127.0.0.1:5432: connect: connection refused"
  ],
  "error_ops":["server.Start","db.Open","db.Connect"],
  "error_root":"dial tcp 127.0.0.1:5432: connect: connection refused",
  "error_root_op":"db.Connect",
  "error_history":"startup failed -> failed to connect to database -> dial tcp 127.0.0.1:5432: connect: connection refused"
}

Configuration (types.LoggingConfig)

Relevant fields (non-exhaustive):

  • Level: trace|debug|info|warn|error|fatal|panic
  • WithTimestamp: include timestamp field
  • SkipFrameCount: enable caller info with given skip frames when > 0
  • ConsoleLogging / FileLogging: enable writers; if both false, file logging is enabled by default
  • RelLogFileDir: relative directory for log files (validated for safety; created on init)
  • LogFileMaxBackups, LogFileMaxAgeDays, LogFileMaxSizeMB, LogFileCompress
  • ConsoleNoColor, ConsoleTimeFormat
  • ShutdownTimeoutMS, ShutdownTimeoutWarning

Lifecycle and concurrency

  • Initialize(): validates config, ensures directory, sets up writers, applies level, timestamp/caller, stores logger
  • Close(): stops accepting new logs, waits up to ShutdownTimeoutMS for in-flight events, optionally warns, closes file writer
  • All event builders use internal reference counting to avoid races during Close()

Context loggers

req := svc.With().Str("request_id", id).Logger()
req.InfoWith().Str("route", "/v1/items").Int("count", 10).Msg("processed")

Dump helper

svc.Dump(struct{ A int; B string }{A:1, B:"x"})

Safely logs nested structures at Debug level with cycle protection and depth limits.

Testing

  • Unit tests cover lifecycle, concurrent usage, event builders, Dump, and error history enrichment.

Notes

  • Error ops are included when errors are created via github.com/Station-Manager/errors.DetailedError.
  • Standard library wrapped errors (errors.Unwrap / fmt.Errorf("%w")) are traversed for messages, but have empty ops.

Documentation

Overview

Package logging provides a thin, concurrency-safe wrapper over rs/zerolog with a structured-first API, safe lifecycle management, and file rotation.

Key features

  • Structured logging only: prefer typed fields over printf-style helpers
  • Context loggers via With() for per-request scoping
  • Graceful shutdown that waits for in-flight logs (bounded timeout)
  • File rotation via lumberjack and configurable console formatting
  • Error history enrichment: for any Err/AnErr, the logger includes the full error chain (outermost -> root), the root cause string, a joined human-readable history, the operations chain (when using Station-Manager DetailedError), and the root operation if available.

Typical usage

svc := &logging.Service{ConfigService: cfg}
if err := svc.Initialize(); err != nil { panic(err) }
defer svc.Close()

svc.InfoWith().Str("user_id", id).Msg("processed")
req := svc.With().Str("request_id", rid).Logger()
req.ErrorWith().Err(err).Msg("failed")

Index

Constants

View Source
const (
	// ServiceName is the DI/service locator name for the logging service.
	ServiceName = types.LoggingServiceName
)

Variables

This section is empty.

Functions

This section is empty.

Types

type LogContext

type LogContext interface {
	Str(key, val string) LogContext
	Strs(key string, vals []string) LogContext
	Int(key string, val int) LogContext
	Int64(key string, val int64) LogContext
	Uint(key string, val uint) LogContext
	Uint64(key string, val uint64) LogContext
	Float64(key string, val float64) LogContext
	Bool(key string, val bool) LogContext
	Time(key string, val time.Time) LogContext
	Err(err error) LogContext
	Interface(key string, val interface{}) LogContext
	// Logger creates and returns the new context logger
	Logger() Logger
}

LogContext provides a fluent interface for building a context logger with pre-populated fields. Fields added through LogContext will be included in all subsequent log messages created from the resulting child logger. The returned LogContext is immutable; each method returns a new state.

type LogEvent

type LogEvent interface {
	Str(key, val string) LogEvent
	Strs(key string, vals []string) LogEvent
	Stringer(key string, val interface{ String() string }) LogEvent
	Int(key string, val int) LogEvent
	Int8(key string, val int8) LogEvent
	Int16(key string, val int16) LogEvent
	Int32(key string, val int32) LogEvent
	Int64(key string, val int64) LogEvent
	Uint(key string, val uint) LogEvent
	Uint8(key string, val uint8) LogEvent
	Uint16(key string, val uint16) LogEvent
	Uint32(key string, val uint32) LogEvent
	Uint64(key string, val uint64) LogEvent
	Float32(key string, val float32) LogEvent
	Float64(key string, val float64) LogEvent
	Bool(key string, val bool) LogEvent
	Bools(key string, vals []bool) LogEvent
	Time(key string, val time.Time) LogEvent
	Dur(key string, val time.Duration) LogEvent
	// Err attaches an error and enriches the event with full chain fields
	// (error_chain, error_root, error_history, error_ops, error_root_op).
	Err(err error) LogEvent
	// AnErr attaches a named error and enriches the event with prefixed chain fields.
	AnErr(key string, err error) LogEvent
	Bytes(key string, val []byte) LogEvent
	Hex(key string, val []byte) LogEvent
	IPAddr(key string, val net.IP) LogEvent
	MACAddr(key string, val net.HardwareAddr) LogEvent
	Interface(key string, val interface{}) LogEvent
	Dict(key string, dict func(LogEvent)) LogEvent
	// Msg writes the event with a literal message
	Msg(msg string)
	// Msgf writes the event using a format string
	Msgf(format string, v ...interface{})
	// Send writes the event without a message
	Send()
}

LogEvent provides a fluent interface for structured logging with type-safe field methods. It wraps zerolog.Event to provide a clean API for adding typed fields to log entries. Calling Msg/Msgf/Send finalizes the event. If the event is a trackedLogEvent, finalizing the event also decrements the internal reference counters used for graceful shutdown.

type Logger

type Logger interface {
	TraceWith() LogEvent
	DebugWith() LogEvent
	InfoWith() LogEvent
	WarnWith() LogEvent
	ErrorWith() LogEvent
	FatalWith() LogEvent
	PanicWith() LogEvent

	// With for context logger creation: creates a new logger with pre-populated
	// fields that will be included in all subsequent logs.
	With() LogContext
}

Logger exposes structured logging event builders and context creation. Usage pattern: logger.InfoWith().Str("user_id", id).Int("count", 5).Msg("processed") Create scoped loggers via With():

req := logger.With().Str("request_id", id).Logger()

Then use req.InfoWith()/ErrorWith() etc. String-format helpers are intentionally not provided; prefer structured logs for queryability.

type Service

type Service struct {
	WorkingDir    string          `di.inject:"workingdir"`
	ConfigService *config.Service `di.inject:"configservice"`
	LoggingConfig *types.LoggingConfig
	// contains filtered or unexported fields
}

Service is the primary logging facility for the application.

It wraps rs/zerolog with:

  • one-time initialization and graceful shutdown
  • concurrency-safe event builders
  • file rotation via lumberjack
  • optional console writer with configurable color/time format
  • error history enrichment for Err/AnErr calls

A Service must be initialized via Initialize() before use and closed with Close(). It is safe for concurrent use by multiple goroutines.

func (*Service) ActiveOperations added in v0.0.6

func (s *Service) ActiveOperations() int32

ActiveOperations returns the current number of active logging operations. This is primarily used by shutdown logic to wait for in-flight operations to complete.

func (*Service) Close

func (s *Service) Close() error

Close stops accepting new log operations, waits for in-flight logging to finish up to a configured timeout, optionally warns on timeout, and closes any open file writer. It is safe to call multiple times.

func (*Service) DebugWith

func (s *Service) DebugWith() LogEvent

DebugWith returns a LogEvent for structured Debug-level logging.

func (*Service) Dump

func (s *Service) Dump(v interface{})

Dump logs the contents of the provided value at Debug level. It handles various types including structs, maps, slices, and basic types. For structs, it logs all exported fields. For complex types like maps and slices, it logs their elements. For basic types, it logs their values. The traversal depth is limited to avoid stack overflows and it tracks visited addresses to avoid cycles.

func (*Service) ErrorWith

func (s *Service) ErrorWith() LogEvent

ErrorWith returns a LogEvent for structured Error-level logging. Example: logger.ErrorWith().Err(err).Str("operation", "database").Msg("Query failed")

func (*Service) FatalWith

func (s *Service) FatalWith() LogEvent

FatalWith returns a LogEvent for structured Fatal-level logging. The program will exit after the log is written.

func (*Service) InfoWith

func (s *Service) InfoWith() LogEvent

InfoWith returns a LogEvent for structured Info-level logging. Use this for queryable, structured logs instead of Info/Infof. Example: logger.InfoWith().Str("user_id", id).Int("count", 5).Msg("User processed")

func (*Service) Initialize

func (s *Service) Initialize() error

Initialize prepares the Service for use: it validates configuration, ensures the log directory exists, sets up file/console writers, sets the log level, and builds the zerolog logger with any requested timestamp or caller info. Initialize is safe to call multiple times; subsequent calls are no-ops.

func (*Service) PanicWith

func (s *Service) PanicWith() LogEvent

PanicWith returns a LogEvent for structured Panic-level logging. The program will panic after the log is written.

func (*Service) TraceWith

func (s *Service) TraceWith() LogEvent

TraceWith returns a LogEvent for structured Trace-level logging. Trace is the most verbose logging level, typically used for very detailed debugging.

func (*Service) Wait added in v0.0.7

func (s *Service) Wait()

Wait blocks until all active logging operations have completed. This is useful for ensuring all logs are written before program exit.

func (*Service) WarnWith

func (s *Service) WarnWith() LogEvent

WarnWith returns a LogEvent for structured Warn-level logging.

func (*Service) With

func (s *Service) With() LogContext

With returns a LogContext for creating a child logger with pre-populated fields. Example: reqLogger := logger.With().Str("request_id", id).Logger() Returns a no-op context if the service is not initialized.

Jump to

Keyboard shortcuts

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