Skip to content

Pino log sending via @opentelemetry/instrumentation-pino does not work with @adonisjs/logger@7.x due to IITM named export limitation #74

@jbsn94

Description

@jbsn94

Description

After upgrading from AdonisJS v6 to v7, OTEL logs are no longer exported to our collector (SigNoz), while traces and metrics continue to work normally. This affects both production apps using Node.js 24.

The root cause is that @adonisjs/logger@7.1.0 imports pino as a named export in its bundled file (logger-Btq_FVdY.js):

import { pino, multistream, destination, ... } from "pino";

The @opentelemetry/instrumentation-pino patches the pino module via import-in-the-middle (IITM):

if (isESM) {
    if (module.pino) module.pino = patchedPino;
    module.default = patchedPino;
}

However, IITM has a known, unfixed limitation where reassignments to named exports are not visible to importing modules (nodejs/import-in-the-middle#38, open since Nov 2023). This means the patched pino function never reaches @adonisjs/logger, so the OTelPinoStream (responsible for log sending) is never added to the logger's stream.

Why traces and metrics are unaffected

Signal Mechanism Relies on IITM?
Traces instrumentation-http uses Node.js native diagnostic_channel No
Metrics Collected automatically by Node.js runtime instrumentations No
Logs instrumentation-pino patches pino module via IITM Yes

Versions

Package Version
@adonisjs/otel 1.2.1
@adonisjs/logger 7.1.0
@adonisjs/core 7.0.1
import-in-the-middle 3.0.0
@opentelemetry/instrumentation-pino 0.58.0
@opentelemetry/auto-instrumentations-node 0.70.1
pino 10.3.1
Node.js 24

Approaches tried and discarded

1. pino-opentelemetry-transport as a pino transport target

Pino transports run in worker threads. The pino-opentelemetry-transport (via otlp-logger) creates a new LoggerProvider in the worker thread and calls logs.setGlobalLoggerProvider(), which overwrites the LoggerProvider configured by @adonisjs/otel in the main thread, breaking traces.

2. Explicit instrumentation-pino configuration

Setting disableLogSending: false explicitly doesn't help since the underlying IITM named export limitation prevents the patch from reaching the logger.

3. Downgrading @adonisjs/otel version

The IITM limitation (#38) affects all IITM versions (v2 and v3), so no version of @adonisjs/otel avoids this.

Suggested fix

The otel_provider could bypass IITM entirely by directly patching the pino logger instance after the app boots. Since the provider has access to the AdonisJS container, it can get the already-created pino instance and manually add the OTelPinoStream via pino.multistream:

// In otel_provider.js — during ready() hook
import { logs } from '@opentelemetry/api-logs'
import pino from 'pino'

const logger = app.container.make('logger')
const pinoInstance = logger.pino
const origStream = pinoInstance[pino.symbols.streamSym]

const otelStream = new OTelPinoStream({
  messageKey: pinoInstance[pino.symbols.messageKeySym],
  levels: pinoInstance.levels,
  // ...
})

pinoInstance[pino.symbols.streamSym] = pino.multistream([
  { level: 0, stream: origStream },
  { level: 0, stream: otelStream },
], { levels: pinoInstance.levels.values })

This would be transparent to users — no changes to config/logger.ts needed.

Alternatively, a simpler fix could be made in @adonisjs/logger: change the bundled import from import { pino } from "pino" (named) to import pino from "pino" (default), which IITM can patch correctly.

Reproduction

  1. Create an AdonisJS v7 app with @adonisjs/otel configured with destinations.otlp({ signals: ['traces', 'logs', 'metrics'] })
  2. Set OTEL_ENABLED=true and point to an OTLP collector
  3. Make HTTP requests — traces and metrics appear in the collector, but no logs

Related issues

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions