{"record":{"id":"9a02cf67df9101ba","repo":"pinojs/pino","slug":"option-transport-targets-do-not-allow-custom-level","errorCode":null,"errorMessage":"option.transport.targets do not allow custom level formatters","messagePattern":"option\\.transport\\.targets do not allow custom level formatters","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/tools.js","lineNumber":342,"sourceCode":"  return function normalizeArgs (instance, caller, opts = {}, stream) {\n    // support stream as a string\n    if (typeof opts === 'string') {\n      stream = buildSafeSonicBoom({ dest: opts })\n      opts = {}\n    } else if (typeof stream === 'string') {\n      if (opts && opts.transport) {\n        throw Error('only one of option.transport or stream can be specified')\n      }\n      stream = buildSafeSonicBoom({ dest: stream })\n    } else if (opts instanceof SonicBoom || opts.writable || opts._writableState) {\n      stream = opts\n      opts = {}\n    } else if (opts.transport) {\n      if (opts.transport instanceof SonicBoom || opts.transport.writable || opts.transport._writableState) {\n        throw Error('option.transport do not allow stream, please pass to option directly. e.g. pino(transport)')\n      }\n      if (opts.transport.targets && opts.transport.targets.length && opts.formatters && typeof opts.formatters.level === 'function') {\n        throw Error('option.transport.targets do not allow custom level formatters')\n      }\n\n      let customLevels\n      if (opts.customLevels) {\n        customLevels = opts.useOnlyCustomLevels ? opts.customLevels : Object.assign({}, opts.levels, opts.customLevels)\n      }\n      stream = transport({ caller, ...opts.transport, levels: customLevels })\n    }\n    opts = Object.assign({}, defaultOptions, opts)\n    opts.serializers = Object.assign(Object.create(null), defaultOptions.serializers, opts.serializers)\n    opts.formatters = Object.assign({}, defaultOptions.formatters, opts.formatters)\n\n    if (opts.prettyPrint) {\n      throw new Error('prettyPrint option is no longer supported, see the pino-pretty package (https://github.com/pinojs/pino-pretty)')\n    }\n\n    const { enabled, onChild } = opts\n    if (enabled === false) opts.level = 'silent'","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/pinojs/pino/blob/5aa62305c57709ebd6f6b7c328e8830eeb29b297/lib/tools.js#L324-L360","documentation":"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.","triggerScenarios":"Calling pino({ transport: { targets: [{ target: 'pino/file', level: 'info' }, ...] }, formatters: { level: (label, number) => ({ level: number }) } }).","commonSituations":"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.","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."],"exampleFix":"// before\nconst logger = pino({\n  transport: { targets: [{ target: 'pino/file', options: { destination: './a.log' }, level: 'info' }] },\n  formatters: { level: (label, n) => ({ level: n }) }\n})\n// after\nconst logger = pino({\n  transport: { targets: [{ target: 'pino/file', options: { destination: './a.log' }, level: 'info' }] }\n})","handlingStrategy":"validation","validationCode":"function validateTransportFormatters(opts) {\n  const t = opts && opts.transport\n  if (t && Array.isArray(t.targets) && t.targets.length > 0 &&\n      opts.formatters && typeof opts.formatters.level === 'function') {\n    throw new TypeError('Remove formatters.level when using transport.targets')\n  }\n  return true\n}","typeGuard":"null","tryCatchPattern":"try {\n  const logger = pino(opts)\n} catch (err) {\n  if (err.message.includes('transport.targets do not allow custom level formatters')) {\n    const { level: _omit, ...formatters } = opts.formatters || {}\n    logger = pino({ ...opts, formatters })\n  } else throw err\n}","preventionTips":["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"],"tags":["pino","configuration","transport","formatters"],"backgroundTag":"conflicting-logger-options","analyzedSha":"5aa62305c57709ebd6f6b7c328e8830eeb29b297","analyzedAt":"2026-09-02T22:05:21.418Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}