pinojs/pino · error · Error

pre-existing level values cannot be used for new levels

Error message

pre-existing level values cannot be used for new levels

What it means

The second collision check in assertNoLevelCollisions: it throws when a customLevels key's numeric VALUE already belongs to an existing level label in the parent. Two different names sharing one numeric value would make level filtering ambiguous, so pino rejects it.

Source

Thrown at lib/levels.js:205

  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
  }

  if (typeof levelComparison === 'string' && Object.values(SORTING_ORDER).includes(levelComparison)) {
    return

View on GitHub (pinned to 5aa62305c5)

Solutions

  1. Pick a numeric value not used by any existing level (check Object.values(logger.levels.values)).
  2. Use values outside the default range, e.g. 25, 35, 45, or above 60, ensuring uniqueness.
  3. Rename/remap the custom level value in your shared config so it does not clash.
  4. Validate the customLevels object programmatically before calling .child().

Example fix

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

Strategy: validation

Validate before calling

function assertUniqueLevelValues(parent, customLevels = {}) {
  const used = new Set(Object.values(parent.levels.values))
  for (const [k, v] of Object.entries(customLevels)) {
    if (used.has(v)) throw new Error(`custom level '${k}' value ${v} already in use`)
  }
}

Type guard

function hasUniqueLevelValues(parent, customLevels) {
  const used = new Set(Object.values(parent.levels.values))
  return Object.values(customLevels || {}).every(v => !used.has(v))
}

Try / catch

try {
  child = logger.child(bindings, { customLevels })
} catch (e) {
  if (e.message === 'pre-existing level values cannot be used for new levels') {
    console.error('Custom level numbers collide with existing levels:', logger.levels.values)
    throw e
  }
  throw e
}

Prevention

When it happens

Trigger: logger.child({}, { customLevels: { notice: 30 } }) where 30 is 'info' in the parent; customLevels: { verbose: 20 } on a parent where 20 is 'debug'.

Common situations: Assigning sequential numbers (10, 20, 30...) to custom levels without realizing defaults already occupy 10–60; merging level tables from two services that use overlapping numeric ranges.

Related errors


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