pinojs/pino · error · Error

levels cannot be overridden

Error message

levels cannot be overridden

What it means

assertNoLevelCollisions runs when creating a child logger with customLevels. It throws 'levels cannot be overridden' when a customLevels key already exists as a level name in the parent's values map — pino forbids redefining an existing level name on a child.

Source

Thrown at lib/levels.js:202

    }
    return
  }

  const labels = Object.assign(
    Object.create(Object.prototype, { silent: { value: Infinity } }),
    useOnlyCustomLevels ? null : DEFAULT_LEVELS,
    customLevels
  )
  if (!(defaultLevel in labels)) {
    throw Error(`default level:${defaultLevel} must be included in custom levels`)
  }
}

function assertNoLevelCollisions (levels, customLevels) {
  const { labels, values } = levels
  for (const k in customLevels) {
    if (k in values) {
      throw Error('levels cannot be overridden')
    }
    if (customLevels[k] in labels) {
      throw Error('pre-existing level values cannot be used for new levels')
    }
  }
}

/**
 * Validates whether `levelComparison` is correct
 *
 * @throws Error
 * @param {SORTING_ORDER | Function} levelComparison - value to validate
 * @returns
 */
function assertLevelComparison (levelComparison) {
  if (typeof levelComparison === 'function') {
    return
  }

View on GitHub (pinned to 5aa62305c5)

Solutions

  1. Rename the custom level to a non-conflicting name (e.g. 'info2' or 'notice').
  2. Remove the conflicting key from the child's customLevels and inherit the parent level.
  3. If the level must differ, create a brand-new root logger instead of a child.
  4. Filter config-supplied custom levels against logger.levels.values before passing them.

Example fix

// before
const child = logger.child({}, { customLevels: { info: 25 } })
// after
const child = logger.child({}, { customLevels: { notice: 25 } })
Defensive patterns

Strategy: validation

Validate before calling

function safeChildCustomLevels(parent, customLevels = {}) {
  const conflicts = Object.keys(customLevels).filter(k => k in parent.levels.values)
  if (conflicts.length) throw new Error(`child customLevels override existing levels: ${conflicts}`)
  return customLevels
}
const child = logger.child({}, { customLevels: safeChildCustomLevels(logger, cfg.customLevels) })

Type guard

function hasNoLevelNameConflicts(parent, customLevels) {
  return Object.keys(customLevels || {}).every(k => !(k in parent.levels.values))
}

Try / catch

try {
  child = logger.child(bindings, { customLevels })
} catch (e) {
  if (e.message === 'levels cannot be overridden') {
    child = logger.child(bindings) // fall back to inherited levels
  } else throw e
}

Prevention

When it happens

Trigger: logger.child({}, { customLevels: { info: 25 } }) — 'info' is already a built-in level; logger.child(bindings, { customLevels: { debug: 15 } }) on a parent that already has debug.

Common situations: Trying to remap a standard level's value for a submodule's child logger; merging user-supplied level config that happens to contain standard level names.

Related errors


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