-
Notifications
You must be signed in to change notification settings - Fork 0
Logging
rex ships SlogLogger, backed by the standard library's log/slog. No external
dependency.
import "github.com/kryovyx/rex/logger"
app := rex.New(rex.WithLogLevel(logger.LogLevelDebug))
// or
app.WithLogger(logger.NewSlogLoggerWithLevel(logger.LogLevelDebug))type Logger interface {
Trace(format string, args ...interface{})
Debug(format string, args ...interface{})
Info(format string, args ...interface{})
Warn(format string, args ...interface{})
Error(format string, args ...interface{})
SetLogLevel(level LogLevel)
WithField(key string, value interface{}) Logger
WithFields(fields map[string]interface{}) Logger
WithError(err error) Logger
}Messages are printf-style. Structured context comes from the With*
methods, which return a child logger:
log := app.Logger().WithFields(map[string]interface{}{
"component": "checkout",
"tenant": tenantID,
})
log.WithError(err).Error("could not reserve stock for order %s", orderID)LogLevelTrace // most verbose
LogLevelDebug
LogLevelInfo // default
LogLevelWarn
LogLevelError
LogLevelOff // silentlogger.ParseLogLevel(s) maps "trace", "debug", "info", "warn",
"error" (case-sensitive) to a level; anything unrecognised returns
LogLevelInfo rather than erroring, so a typo in LOG_LEVEL is silently
"info", not a crash:
app := rex.New(rex.WithLogLevel(logger.ParseLogLevel(os.Getenv("LOG_LEVEL"))))If a typo should be a deployment failure, validate the string yourself before calling it.
// Both work.
rex.New(rex.WithLogLevel(logger.LogLevelDebug), rex.WithLogger(myLogger))
rex.New(rex.WithLogger(myLogger), rex.WithLogLevel(logger.LogLevelDebug))WithLogLevel stores the level and applies it when a logger is set, so it works
before one is assigned. (This was documented behaviour before it was implemented
behaviour: the option previously only called SetLogLevel on whatever logger
existed at the time, so WithLogLevel before WithLogger silently did
nothing.)
Implement the interface and pass it in:
type zapAdapter struct{ l *zap.SugaredLogger }
func (a *zapAdapter) Info(format string, args ...interface{}) { a.l.Infof(format, args...) }
// … Warn, Error, Debug, Trace, SetLogLevel …
func (a *zapAdapter) WithField(k string, v interface{}) rextension.Logger {
return &zapAdapter{l: a.l.With(k, v)}
}
// … WithFields, WithError …
app.WithLogger(&zapAdapter{l: sugared})Logger is declared in rextension and aliased by rex/logger, so the
framework, your application and every extension share one type with no
conversion.
The With* methods must return a new logger, not mutate the receiver — the
framework and extensions both take field-scoped children and expect the parent
to be unaffected.
| Level | Examples |
|---|---|
Info |
starting, running, stopping, extension initialisation, bound addresses |
Warn |
unresolved request (404), body over the limit (413), a router serving without TLS because ListenSSL is off |
Error |
hook failures, bind failures, a request reaching a router before its table was built |
Debug |
incoming and handled requests with status, size and duration; hook progress |
At Debug, every request produces two lines. That is fine for development and
too much for production — a real access log belongs in middleware, where it can
be formatted and sampled, or in a
router.request.handled subscriber if approximate is acceptable.
An extension logs through r.Logger(). By convention it takes a field-scoped
child so its output can be filtered:
e.logger = r.Logger().WithField("extension", "myext")rex — Restful Extended eXperience · MIT · © 2026 Kryovyx · pre-1.0 (alpha), interfaces may change
Getting started
Core
Operating
Ecosystem