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
- Remove opts.formatters.level when using transport.targets.
- Move any per-target formatting into each target's own options if the target supports it.
- Use a single transport.target instead of targets if the custom level formatter is essential.
- 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
- Treat transport.targets and formatters.level as mutually exclusive
- Do per-target formatting inside each transport's own code/options
- Centralize logger option construction so both options can't be set together
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
- PINO_TRANSPORT_MULTI_TARGETS_LEVEL_FORMATTER
- only one of option.transport or stream can be specified
- option.transport do not allow stream, please pass to option
- Levels comparison should be one of "ASC", "DESC" or "functio
- only one of target or targets can be specified
AI-assisted analysis of pinojs/pino@5aa62305c5 (2026-09-02).
Data as JSON: /api/errors/9a02cf67df9101ba.
Report an issue: GitHub.