Overview
logx is a standalone Go module (github.com/gp-system/logx, +logx/monolog): a small toolkit for composing the default log/slog logger. Three combinators (ConsoleHandler, LevelFilter, Fanout) give you a console format switch, a minimum-level gate and a Monolog-style multi-handler stack, all as plain slog.Handler values. On top, logx/monolog is a standalone formatter that renders [date] channel.LEVEL: message {context} lines, PHP-style, with full-path stack traces pulled out of an errs error.
It used to live as a subpackage of telemetry, imported as telemetry's own logx subpackage; it has since moved into its own module, because composing a console logger has nothing to do with OpenTelemetry or Sentry wiring, and a program that just wants readable console logs shouldn't have to pull in the OTel SDK or sentry-go to get them. telemetry.Setup still uses logx exactly as before, just as a regular module dependency instead of a subpackage: nothing about the runtime behavior changed, only which module the code lives in.
Its only dependency is errs, and only from logx/monolog: the formatter reads errs.CodeOf and walks errs.FullFrames/errs.Frame to render a wrapped error's stack. The logx package itself (ConsoleHandler, LevelFilter, Fanout, Config) imports nothing beyond the standard library.
Install
go get github.com/gp-system/logx@main # no tagged release yet
| Import path | What it adds |
|---|---|
github.com/gp-system/logx | the slog composition: ConsoleHandler, LevelFilter, Fanout, Config |
github.com/gp-system/logx/monolog | the PHP/Monolog-style console formatter, usable as a plain slog.Handler on its own |
Configuration
logx.Config carries no env prefix of its own: the variables are meant to be embedded unprefixed, at the top level of whatever composes them (in the framework, telemetry.Config's Log field).
| Variable | Default | Meaning |
|---|---|---|
LOG_LEVEL | INFO | the logger's minimum level (applies to every handler in the fanout) |
LOG_STDOUT | false | also write to the console in production mode (JSON); leave off when a collector scrapes container output, or you ingest twice |
LOG_FORMAT | empty (auto) | the console format: empty means text in dev / JSON in prod; monolog selects PHP/Monolog-style lines regardless of mode |
An unknown LOG_FORMAT value fails Config.Validate() with an error instead of silently falling back.
The composition: Fanout and LevelFilter
Three small slog.Handler combinators are the whole package:
func ConsoleHandler(w io.Writer, cfg Config, dev bool, channel string) slog.Handler
func Fanout(handlers ...slog.Handler) slog.Handler // several handlers, one record each
func LevelFilter(min slog.Level, next slog.Handler) slog.Handler // a minimum-level gate
LevelFilter exists because not every leaf handler filters on its own (an OTLP bridge, for instance, forwards every record it is given): put it outermost and every handler behind it only ever sees records at or above the minimum level. Fanout hands each handler its own clone of the record and honors Enabled as a per-handler gate; with a single handler it returns it as-is, allocation-free.
Usage
A plain Go program, no framework, no telemetry:
package main
import (
"log/slog"
"os"
"github.com/gp-system/logx"
)
func main() {
cfg := logx.Config{Level: slog.LevelInfo, Format: "monolog"}
slog.SetDefault(slog.New(
logx.LevelFilter(cfg.Level, logx.Fanout(
logx.ConsoleHandler(os.Stdout, cfg, true, "myapp"),
// add more slog.Handler values here: a file handler, a custom sink, ...
)),
))
slog.Info("service starting", "addr", ":3000")
}
Inside the framework, telemetry.Setup builds the default logger the same way, just with two more handlers in the fanout: the OTel log bridge and the Sentry capture handler. See Telemetry: Logging for that composition.
Related pages
- logx: Monolog formatter: the
[date] channel.LEVEL: messageformat, stack traces fromerrs, and a Laravel/Symfony concept mapping. - Telemetry: Overview: how
telemetry.Setupfanslogx's console handler out alongside the OTel bridge and the Sentry handler. - errs: Overview: the error type
logx/monologrenders stack traces from.