ADR-0001 — Pino (framework) + Winston/EvoLogger (app-code) coexistence¶
Status: Accepted Date: 2026-04-16 Author(s): Platform engineering
Context¶
All five NestJS apps need structured, trace-correlated JSON logs in Loki. The codebase already had ~40 call sites using EvoLogger (a winston-backed facade from @bebkovan/server-core). The OTel ecosystem has a stable pino instrumentation (@opentelemetry/instrumentation-pino) that bridges log records into the OTLP logs SDK for free. Winston's equivalent (@opentelemetry/instrumentation-winston) exists but is less mature and does not bridge records into the OTLP logs pipeline — it only injects trace context fields.
Decision¶
Run two loggers side by side:
-
nestjs-pino is the Nest framework logger. It captures HTTP request/response lifecycle, Nest module events, and any
constructor(private logger: Logger)injection sites. Records are JSON on stdout, bridged into OTel's logs API byPinoInstrumentation, and exported via OTLP to the collector → Loki. -
EvoLogger (winston) continues to back the ~40 existing app-code call sites.
WinstonInstrumentation(enabled by default ingetNodeAutoInstrumentations) injectstrace_id/span_id/trace_flagsat the winston transport layer, so records are trace-tagged. These records go to docker stdout and reach Loki via thefilelog/dockercollector receiver.
Wiring: libs/shared/src/logger/pino-logger.module.ts provides NestLoggerModule.forRoot(). Every app.module.ts imports it before EvoLoggerModule. libs/shared/src/basic/base.main.ts swaps the framework logger: app.useLogger(app.get(Logger)). pre-otel.main.ts disables the auto-pino instrumentation and registers one with custom logKeys (trace_id, span_id, trace_flags).
Alternatives considered¶
-
Migrate all ~40 EvoLogger call sites to pino. Rejected: touches 40+ files across every module for zero functional gain. EvoLogger records are already trace-tagged via
WinstonInstrumentationand now reach Loki via the filelog receiver. The mass rewrite is pure churn. -
Use winston everywhere (drop pino). Rejected: winston has no stable OTLP log bridge. We would lose the automatic request-lifecycle logging that
nestjs-pinoprovides and would need to build custom Nest middleware to get equivalent HTTP access logs. -
Use pino everywhere (drop EvoLogger). Same outcome as #1 but removes the
@bebkovan/server-coredependency. Possible future state, but not worth the effort today.
Consequences¶
- New code should prefer the Nest-injected pino logger (
constructor(private logger: Logger)fromnestjs-pino). Those records land in Loki via OTLP with full resource attributes. - EvoLogger records reach Loki via a separate path (
filelog/dockerreceiver, taggedsource=docker_filelog). They lackservice.nameresource attributes and must be queried with{source="docker_filelog"}. - Two logger setups means two mental models. The glossary and observability doc explain the distinction.
References¶
libs/shared/src/logger/pino-logger.module.ts— NestLoggerModulelibs/shared/src/basic/pre/pre-otel.main.ts:78-82— PinoInstrumentation registrationlibs/shared/src/basic/base.main.ts— framework logger swapdocs/observability.md— full two-logger explanation