Skip to content

createLogger({ level: 'debug' }) silently drops debug records on default pino stream #24

@dio

Description

@dio

Repro

bun -e "import { createLogger } from '@tetratelabs/logging'; const l = createLogger({ level: 'debug' }); l.debug('debug-visible'); l.info('info-visible');"

Output on 0.3.1:

{"level":30,"time":...,"msg":"info-visible"}

Expected: both debug-visible and info-visible lines.

Root cause

src/sink-pino.ts constructs the pino instance with opts?.pino ?? {} and never threads in opts.level. Pino then defaults to its own info floor. The wrapper-level filter in LoggerImpl lets debug calls through, but the underlying pino instance drops them at the sink.

The GCP sink writes via console.log so it is unaffected. Only the default Node pino path is broken — i.e. dev and tests.

Secondary issue

LOG_LEVEL=trace (a valid pino level) is not in our Level union. The cast process.env.LOG_LEVEL as Level ends up as a value missing from levelRanks, and shouldLog evaluates undefined >= N as false for every call — silencing the logger entirely, including errors. Callers porting from pino hit this and assume the library is broken.

Fix plan

  1. Thread opts.level into the pino sink so debug calls reach the default stream. Explicit opts.pino.level keeps winning.
  2. Export parseLevel so consumers can validate process.env.LOG_LEVEL without an unsafe cast.
  3. Accept trace and fatal as aliases in parseLevel (map to debug and error) so pino habits do not produce surprise silence.

Found while reviewing tetratelabs/fraser-auth#170.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions