Logging
In gpsystem, the default log/slog logger is not just one handler: telemetry.Setup fans a record out to three sinks at once, gated by a single minimum-level filter. Application code imports only standard log/slog, nothing framework-specific; the composition itself is built from the standalone logx module.
The composition
telemetry.Setup builds the default logger like this:
slog.SetDefault(slog.New(
logx.LevelFilter(cfg.Log.Level, logx.Fanout(
logx.ConsoleHandler(os.Stdout, cfg.Log, dev, serviceName), // in dev, or with LOG_STDOUT
otelHandle.SlogHandler(), // OTLP bridge, in prod
sentryHandle.SlogHandler(), // Sentry capture, when the DSN is set
)),
))
LevelFilter sits outermost because not every leaf handler filters on its own: the OTLP bridge, for instance, forwards every record it is given. Fanout hands each handler its own clone of the record and honors Enabled as a per-handler gate, and with a single handler it returns it as-is, allocation-free. Both combinators, and the console handler itself, come from logx; telemetry only supplies the other two handlers (the OTel bridge, the Sentry capture handler) and wires all three together, which is the one thing this page is about.
LOG_FORMAT (a logx.Config field) only selects the console handler's rendering: the OTLP bridge and the Sentry handler always receive the same structured record.Inside a chassis you never write this: server.Run / worker.Run / realtime.Run get it for free. If you want the same console experience in a non-chassis binary (a CLI, a migrator), or want to weave an extra sink of your own into the fanout (say, a file handler), reach for logx directly: the Config fields and the ConsoleHandler/LevelFilter/Fanout composition are documented, standalone, on logx: Overview.
The Monolog-format dev console
With LOG_FORMAT=monolog (the recommended dev setting), the console renders PHP/Monolog-style lines, [date] channel.LEVEL: message {context}, with full-path stack traces pulled out of an errs error, familiar from PHP. That formatter is logx/monolog, and it moved along with the rest of logx into its own module: the format itself, the Laravel/Symfony concept mapping and the "why stdout only" rationale are on logx: Monolog formatter.
(In dev mode the per-request access-log lines come from the framework's structured slog access log (the accesslog stack entry), so they follow LOG_FORMAT and, through the OTel bridge, are trace-correlated like every other record.)
How to log in your services
There are two rules, and both are about context.Context:
func (s *OrderService) PlaceOrder(ctx context.Context, in PlaceOrder) error {
// 1. Always the *Context variant, with the request ctx:
slog.InfoContext(ctx, "order placed",
slog.String("order_id", order.ID),
slog.Int("total", order.Total))
// ...
}
slog.InfoContext/WarnContext/ErrorContext, neverslog.Info. The ctx carries the active trace: the otelslog bridge attaches the trace/span ID to the record from it, the Sentry handler links the error to the trace waterfall through it, and breadcrumbs land on the request's hub through it. A ctx-less log is an uncorrelated log.- Pass errors with
slog.Any("error", err), not as anerr.Error()string: that is what preserves the structured chain, the stack and the code for every sink, console, OTLP or Sentry alike.
There is no logger to inject, construct or wire into DI: the chassis sets slog.Default(), and the package-level slog.*Context calls are exactly right. And the ERROR level is not just a "loud log": it is also a Sentry event. The framework logs 5xx for you; you log ERROR when something truly is an incident.
errs error goes out as a 4xx, with no request failed line and no Sentry event.Where to next
- logx: Overview: the
Configfields (LOG_LEVEL/LOG_STDOUT/LOG_FORMAT), andConsoleHandler/LevelFilter/Fanoutfor standalone use outside telemetry. - logx: Monolog formatter: the PHP-style dev console format, in full, with the Laravel/Symfony concept mapping.
- OpenTelemetry: the OTLP log bridge this page's fanout feeds into.
- Sentry: the capture handler this page's fanout feeds into, and code-based issue grouping.