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 internal/logx:

AspectBehavior
Encodingconsole — single-line, human-readable text; there is no JSON option at v0.37.0
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 at v0.37.0; attach context by deriving Named(...) children or by formatting values into the message.

Replacing Or Wrapping The Logger

The concrete implementation is not replaceable at v0.37.0. The root zap logger is a package-level value in internal/logx, 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)

Inside FX-constructed components, prefer injecting logx.Logger directly (or logx.LoggerConfigurable[T] for immutable components) over reaching for vef.NamedLogger.

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