pinojs/pino · error · Error

default level:${defaultLevel} must be included in custom lev

Error message

default level:${defaultLevel} must be included in custom levels

What it means

assertDefaultLevelFound validates that the configured defaultLevel appears among the allowed level values: the customLevels values, plus (unless useOnlyCustomLevels is set) the default numeric levels, plus Infinity (silent). When defaultLevel is not found in that numeric set, pino throws because the logger would start at a level it cannot compare against.

Source

Thrown at lib/levels.js:183

    customNums
  )
  const values = Object.assign(
    Object.create(Object.prototype, { silent: { value: Infinity } }),
    useOnlyCustomLevels ? null : DEFAULT_LEVELS,
    customLevels
  )
  return { labels, values }
}

function assertDefaultLevelFound (defaultLevel, customLevels, useOnlyCustomLevels) {
  if (typeof defaultLevel === 'number') {
    const values = [].concat(
      Object.keys(customLevels || {}).map(key => customLevels[key]),
      useOnlyCustomLevels ? [] : Object.keys(nums).map(level => +level),
      Infinity
    )
    if (!values.includes(defaultLevel)) {
      throw Error(`default level:${defaultLevel} must be included in custom levels`)
    }
    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) {

View on GitHub (pinned to 5aa62305c5)

Solutions

  1. Add the default level to customLevels (e.g. customLevels: { info: 30, foo: 35 }) when useOnlyCustomLevels is true.
  2. Remove useOnlyCustomLevels: true so default pino levels remain available.
  3. Set defaultLevel explicitly to one of the custom level names.
  4. Verify the defaultLevel value/number matches one of the registered level values.

Example fix

// before
const logger = pino({ customLevels: { trace2: 15 }, useOnlyCustomLevels: true })
// after
const logger = pino({
  customLevels: { trace2: 15, info: 30 },
  defaultLevel: 'info',
  useOnlyCustomLevels: true
})
Defensive patterns

Strategy: validation

Validate before calling

function assertCustomLevelsConfig(opts) {
  const { customLevels = {}, useOnlyCustomLevels, level = 'info', defaultLevel = level } = opts
  const numeric = Object.values(customLevels)
  const ok = useOnlyCustomLevels
    ? numeric.includes(defaultLevel)
    : true
  if (!ok) throw new Error(`defaultLevel ${defaultLevel} missing from customLevels`)
}

Type guard

function hasValidDefaultLevel(opts) {
  const customLevels = opts.customLevels || {}
  const defaultLevel = opts.defaultLevel || opts.level || 'info'
  return !opts.useOnlyCustomLevels || Object.values(customLevels).includes(defaultLevel)
}

Try / catch

try {
  logger = pino(opts)
} catch (e) {
  if (e.message.includes('must be included in custom levels')) {
    throw new Error(`config error: defaultLevel '${opts.defaultLevel}' not in customLevels`, { cause: e })
  }
  throw e
}

Prevention

When it happens

Trigger: pino({ customLevels: { foo: 35 }, useOnlyCustomLevels: true }) — default level 'info' (30) is excluded because only custom levels are allowed; pino({ defaultLevel: 'warn', customLevels: { foo: 35 }, useOnlyCustomLevels: true }) without 'warn' in customLevels.

Common situations: Setting useOnlyCustomLevels: true but forgetting the defaultLevel must itself be a custom level; copying a config snippet with custom levels into a codebase that relies on the default 'info' level.

Related errors


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