pinojs/pino · error · Error

option.transport.targets do not allow custom level formatter

Error message

option.transport.targets do not allow custom level formatters

What it means

When using transport.targets (multiple transports), Pino forbids opts.formatters.level (a custom level formatter function). The level formatter is applied per-target inside the transport pipeline, so a global custom level formatter would conflict with how targets serialize log levels.

Source

Thrown at lib/tools.js:342

  return function normalizeArgs (instance, caller, opts = {}, stream) {
    // support stream as a string
    if (typeof opts === 'string') {
      stream = buildSafeSonicBoom({ dest: opts })
      opts = {}
    } else if (typeof stream === 'string') {
      if (opts && opts.transport) {
        throw Error('only one of option.transport or stream can be specified')
      }
      stream = buildSafeSonicBoom({ dest: stream })
    } else if (opts instanceof SonicBoom || opts.writable || opts._writableState) {
      stream = opts
      opts = {}
    } else if (opts.transport) {
      if (opts.transport instanceof SonicBoom || opts.transport.writable || opts.transport._writableState) {
        throw Error('option.transport do not allow stream, please pass to option directly. e.g. pino(transport)')
      }
      if (opts.transport.targets && opts.transport.targets.length && opts.formatters && typeof opts.formatters.level === 'function') {
        throw Error('option.transport.targets do not allow custom level formatters')
      }

      let customLevels
      if (opts.customLevels) {
        customLevels = opts.useOnlyCustomLevels ? opts.customLevels : Object.assign({}, opts.levels, opts.customLevels)
      }
      stream = transport({ caller, ...opts.transport, levels: customLevels })
    }
    opts = Object.assign({}, defaultOptions, opts)
    opts.serializers = Object.assign(Object.create(null), defaultOptions.serializers, opts.serializers)
    opts.formatters = Object.assign({}, defaultOptions.formatters, opts.formatters)

    if (opts.prettyPrint) {
      throw new Error('prettyPrint option is no longer supported, see the pino-pretty package (https://github.com/pinojs/pino-pretty)')
    }

    const { enabled, onChild } = opts
    if (enabled === false) opts.level = 'silent'

View on GitHub (pinned to 5aa62305c5)

Solutions

  1. Remove opts.formatters.level when using transport.targets.
  2. Move any per-target formatting into each target's own options if the target supports it.
  3. Use a single transport.target instead of targets if the custom level formatter is essential.
  4. Apply the level formatting inside the transport worker code rather than in the main thread.

Example fix

// before
const logger = pino({
  transport: { targets: [{ target: 'pino/file', options: { destination: './a.log' }, level: 'info' }] },
  formatters: { level: (label, n) => ({ level: n }) }
})
// after
const logger = pino({
  transport: { targets: [{ target: 'pino/file', options: { destination: './a.log' }, level: 'info' }] }
})
Defensive patterns

Strategy: validation

Validate before calling

function validateTransportFormatters(opts) {
  const t = opts && opts.transport
  if (t && Array.isArray(t.targets) && t.targets.length > 0 &&
      opts.formatters && typeof opts.formatters.level === 'function') {
    throw new TypeError('Remove formatters.level when using transport.targets')
  }
  return true
}

Type guard

null

Try / catch

try {
  const logger = pino(opts)
} catch (err) {
  if (err.message.includes('transport.targets do not allow custom level formatters')) {
    const { level: _omit, ...formatters } = opts.formatters || {}
    logger = pino({ ...opts, formatters })
  } else throw err
}

Prevention

When it happens

Trigger: Calling pino({ transport: { targets: [{ target: 'pino/file', level: 'info' }, ...] }, formatters: { level: (label, number) => ({ level: number }) } }).

Common situations: Combining a multi-target transport config copied from docs with a formatters block carried over from an older single-logger setup; teams adding a level formatter (e.g. to emit numeric levels) after adopting transport targets.

Related errors


AI-assisted analysis of pinojs/pino@5aa62305c5 (2026-09-02). Data as JSON: /api/errors/9a02cf67df9101ba. Report an issue: GitHub.