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
- Use a level name that exists in this logger's levels (call logger.levels to see valid labels/values).
- If using a custom level, pass it in the customLevels option at logger creation before calling setLevel.
- If passing a number, ensure it matches a defined level value (e.g. 30 for 'info' in default levels).
- 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
- Derive level choices from logger.levels.labels instead of hardcoding strings.
- Whitelist accepted level names at the config boundary.
- Remember level names are case-sensitive strings; trim env-sourced values.
- Define custom levels once at logger creation before any setLevel call.
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
- Levels comparison should be one of "ASC", "DESC" or "functio
- default level:${defaultLevel} must be included in custom lev
- levels cannot be overridden
- pre-existing level values cannot be used for new levels
- stream object needs to implement either StreamEntry or Desti
AI-assisted analysis of pinojs/pino@5aa62305c5 (2026-09-02).
Data as JSON: /api/errors/f0a93ffc0a7dde45.
Report an issue: GitHub.