pinojs/pino · error · Error

unknown level ${level}

Error message

unknown level ${level}

What it means

setLevel throws this when the given level name is not registered in the logger's levels map (or when a numeric level value has no matching label). Pino resolves levels strictly against its internal labels/values lookup, so any unknown string or out-of-range number is rejected immediately rather than silently ignored.

Source

Thrown at lib/levels.js:83

    case 'fatal':
    case 'error':
    case 'warn':
    case 'info':
    case 'debug':
    case 'trace':
      return true
    default:
      return false
  }
}

function setLevel (level) {
  const { labels, values } = this.levels
  if (typeof level === 'number') {
    if (labels[level] === undefined) throw Error('unknown level value' + level)
    level = labels[level]
  }
  if (values[level] === undefined) throw Error('unknown level ' + level)
  const preLevelVal = this[levelValSym]
  const levelVal = this[levelValSym] = values[level]
  const useOnlyCustomLevelsVal = this[useOnlyCustomLevelsSym]
  const levelComparison = this[levelCompSym]
  const hook = this[hooksSym].logMethod

  for (const key in values) {
    if (levelComparison(values[key], levelVal) === false) {
      this[key] = noop
      continue
    }
    this[key] = isStandardLevel(key, useOnlyCustomLevelsVal) ? levelMethods[key](hook) : genLog(values[key], hook)
  }

  this.emit(
    'level-change',
    level,
    levelVal,

View on GitHub (pinned to 5aa62305c5)

Solutions

  1. Use a level name that exists in this logger's levels (call logger.levels to see valid labels/values).
  2. If using a custom level, pass it in the customLevels option at logger creation before calling setLevel.
  3. If passing a number, ensure it matches a defined level value (e.g. 30 for 'info' in default levels).
  4. Fix config/env values that supply the level string, trimming whitespace and casing (level names are case-sensitive).

Example fix

// before
logger.setLevel('verbose')
// after
const pino = require('pino')
const logger = pino({ customLevels: { verbose: 25 } })
logger.setLevel('verbose')
Defensive patterns

Strategy: validation

Validate before calling

function isValidLevel(logger, level) {
  if (typeof level === 'number') return level in logger.levels.labels
  return typeof level === 'string' && level in logger.levels.values
}
if (!isValidLevel(logger, cfg.level)) throw new Error(`unsupported level: ${cfg.level}`)

Type guard

function isKnownLevel(logger, level) {
  return typeof level === 'number'
    ? level in logger.levels.labels
    : typeof level === 'string' && level in logger.levels.values
}

Try / catch

try {
  logger.setLevel(cfg.level)
} catch (e) {
  if (String(e.message).startsWith('unknown level')) {
    logger.warn(`invalid level ${cfg.level}, falling back to 'info'`)
    logger.setLevel('info')
  } else throw e
}

Prevention

When it happens

Trigger: logger.setLevel('verbose') where 'verbose' was never defined via customLevels; setLevel(99) where no label maps to that numeric value; setLevel on a logger whose customLevels option excluded the name being set.

Common situations: Typos in level names, passing levels from another logging library (winston 'verbose', log4j 'trace' as string), configuring custom levels but then setting a default library level by name, or reading a level from config/env with a stale value.

Related errors


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