Skip to main content

logx

The logx package defines the structured-logging contract used across the framework — every framework component that logs (API, security, event bus, CRUD, approval, and so on) depends on logx.Logger, never on a concrete logging library directly.

API Reference​

APIContractPurpose
logx.Leveltype Level int8Logging priority; higher levels are more important.
logx.LevelDebug = 1debug level constantVoluminous logs, usually disabled in production.
logx.LevelInfo = 2info level constantDefault logging priority.
logx.LevelWarn = 3warning level constantMore important than info but not necessarily human-reviewed one by one.
logx.LevelError = 4error level constantHigh-priority logs for unexpected application behavior.
logx.LevelPanic = 5panic level constantLogs a message and then panics.
logx.Loggerlogging interfaceContract implemented by framework-provided and custom loggers.
logx.LoggerConfigurable[T]generic interfaceImplemented by immutable components that return a logger-configured copy from WithLogger.

Level.String() string returns debug, info, warn, error, or panic. Unknown level values, including the zero value, return unknown.

Logger Interface​

MethodContract
Logger.Named(name string) logx.Loggerreturns a child logger with the given namespace
Logger.WithCallerSkip(skip int) logx.Loggerreturns a logger that adjusts caller stack-frame reporting
Logger.Enabled(level logx.Level) boolreports whether the given level is enabled
Logger.Sync()flushes buffered log entries; the interface does not return an error
Logger.Debug(message string)logs a debug message
Logger.Debugf(template string, args ...any)logs a formatted debug message
Logger.Info(message string)logs an info message
Logger.Infof(template string, args ...any)logs a formatted info message
Logger.Warn(message string)logs a warning message
Logger.Warnf(template string, args ...any)logs a formatted warning message
Logger.Error(message string)logs an error message
Logger.Errorf(template string, args ...any)logs a formatted error message
Logger.Panic(message string)logs a panic message and then panics
Logger.Panicf(template string, args ...any)logs a formatted panic message and then panics
LoggerConfigurable[T].WithLogger(logger logx.Logger) Treturns a copy of the component configured with the given logger

LoggerConfigurable[T] is the pattern used by immutable, DI-constructed components that need a logger injected after construction: WithLogger returns a new configured copy rather than mutating the receiver in place.

Default Implementation​

Out of the box, every logx.Logger is backed by a zap.SugaredLogger (go.uber.org/zap) built inside the framework's internal logging implementation:

AspectBehavior
Encodingconsole — single-line, human-readable text; there is no JSON option
Destinationlog entries go to stdout; zap's own internal errors go to stderr
Line layoutdimmed 2006-01-02T15:04:05.000 timestamp, capitalized level, [name] namespace, trimmed caller path, message
Colorthe level column is always ANSI-colorized (zapcore.CapitalColorLevelEncoder); timestamp, caller, and name styling is applied only when stdout supports it (termenv detection: disabled for non-TTY output and under NO_COLOR)
Stack tracesdisabled

The root logger is a package-level value constructed once at process start; every logger the framework hands out — component loggers, vef.NamedLogger, and the request-scoped logger — is a Named child of that single root. FX lifecycle events and config-loader (viper) messages funnel into the same logger through an internal log/slog bridge, filtered to warn and above.

There is no configuration surface for encoding or destination: to ship logs elsewhere, capture stdout at the deployment level.

Log Levels​

The active threshold comes from the VEF_LOG_LEVEL environment variable (config.EnvLogLevel), read once at process start:

VEF_LOG_LEVELThreshold
debuglogx.LevelDebug
warnlogx.LevelWarn
errorlogx.LevelError
paniclogx.LevelPanic
anything else, including unset and infologx.LevelInfo (the default)

Matching is case-insensitive. The threshold is a single shared zap.AtomicLevel that governs every named logger at once — there is no per-component level configuration, and no public runtime API to change the level after startup (an internal setter exists but is not exported). Use Logger.Enabled(level) to guard expensive log-message construction.

Request-Scoped Logging​

Two built-in app middlewares cooperate to give every HTTP request its own logger:

  1. The request-ID middleware (order -650) reuses a valid incoming X-Request-ID header, or generates a UUID v7 via id.GenerateUUID, and echoes the value in the response header.
  2. The logger middleware (order -600) derives a child logger named request_id:<id> from the root logger and stores it with contextx.SetLogger — into both the fiber locals and the embedded context.Context, so fiber handlers and plain-context.Context consumers resolve the same logger.

Downstream, the request logger travels three ways:

  • contextx.Logger(ctx, fallbacks...) retrieves it anywhere the request context flows; it returns nil when nothing is stored and no non-empty fallback is given.
  • An API handler parameter of type logx.Logger is resolved automatically from the request context.
  • When a handler parameter is served from a resource struct field whose type has a WithLogger(logx.Logger) method (the LoggerConfigurable shape), the injected value is a per-request copy configured with the request logger.

The request ID is carried as a logger namespace — log lines show [request_id:<uuid>] — not as a structured field. logx.Logger has no With(key, value) field API; attach context by deriving Named(...) children or by formatting values into the message.

Replacing Or Wrapping The Logger​

The concrete implementation is not replaceable. The root zap logger is a package-level value constructed once at process start, framework packages capture their named children at package initialization, and logx.Logger is not a DI-provided type — so there is no fx.Decorate point and no exported replacement hook. Framework-internal output (library, format, destination) is fixed.

What an application can do instead:

  • Implement logx.Logger for its own code — the framework never forces application code through the built-in implementation.
  • Override the request-scoped logger downstream of a point it controls by calling contextx.SetLogger(ctx, custom); contextx.Logger lookups and handler-parameter injection then resolve the custom logger, while framework-internal component logs still use the built-in one.
  • Pass a custom logger into components it constructs via LoggerConfigurable[T].WithLogger.
  • Redirect or collect stdout at the deployment level for log shipping.

Using The Framework Logger Outside DI​

Application integration code can call vef.NamedLogger(name string) logx.Logger when it needs the framework logger outside dependency injection. That helper is exported from the root vef package; it is not an additional top-level symbol in logx.

import "github.com/coldsmirk/vef-framework-go"

logger := vef.NamedLogger("myjob")
logger.Infof("processed %d records", count)

Note that logx.Logger is not in the DI graph — declaring it as an FX constructor parameter fails startup. Inside FX-constructed components, call vef.NamedLogger(...) in the constructor body (or accept a logger through logx.LoggerConfigurable[T] when the framework configures your component); request handlers can simply declare a logx.Logger parameter, which the API engine injects per request.

Practical Advice​

  • Set VEF_LOG_LEVEL=error in CI and test runs to silence framework chatter; the framework's own CI does exactly this.
  • Inside request handling, prefer contextx.Logger(ctx) or a logx.Logger handler parameter so entries correlate by request ID; reserve vef.NamedLogger for background and integration code.
  • Guard voluminous debug logging with logger.Enabled(logx.LevelDebug).
  • Expect ANSI escape codes in the level column when parsing captured output; the console encoding is designed for humans, not machines.
  • Panic/Panicf really panic — reserve them for unrecoverable initialization failures.

See Also​