pinojs/pino · error · Error
levelVal is read-only
Error message
levelVal is read-only
What it means
The logger prototype defines levelVal only as a getter over an internal symbol; its setter deliberately throws to protect the internal numeric level used for filtering. Assigning logger.levelVal = n is never valid — use setLevel or the level property instead.
Source
Thrown at lib/proto.js:67
version
} = require('./meta')
const redaction = require('./redaction')
// note: use of class is satirical
// https://github.com/pinojs/pino/pull/433#pullrequestreview-127703127
const constructor = class Pino {}
const prototype = {
constructor,
child,
bindings,
setBindings,
flush,
isLevelEnabled,
version,
get level () { return this[getLevelSym]() },
set level (lvl) { this[setLevelSym](lvl) },
get levelVal () { return this[levelValSym] },
set levelVal (n) { throw Error('levelVal is read-only') },
get msgPrefix () { return this[msgPrefixSym] },
get [Symbol.toStringTag] () { return 'Pino' },
[lsCacheSym]: initialLsCache,
[writeSym]: write,
[asJsonSym]: asJson,
[getLevelSym]: getLevel,
[setLevelSym]: setLevel
}
Object.setPrototypeOf(prototype, EventEmitter.prototype)
// exporting and consuming the prototype object using factory pattern fixes scoping issues with getters when serializing
module.exports = function () {
return Object.create(prototype)
}
const resetChildingsFormatter = bindings => bindings
function child (bindings, options) {View on GitHub (pinned to 5aa62305c5)
Solutions
- Set the level by name instead: logger.level = 'info' or logger.setLevel('info').
- If you need numeric control, define the desired value as a custom level at construction time.
- Exclude levelVal when copying/cloning logger properties.
- Use logger.levelVal only for reads (e.g. comparisons), never writes.
Example fix
// before logger.levelVal = 30 // after logger.level = 'info'
Defensive patterns
Strategy: validation
Validate before calling
if ('levelVal' in changeSet && !('level' in changeSet)) {
throw new Error('set logger.level by name; levelVal is read-only')
}
Object.assign(logger, changeSet) Type guard
function isLevelMutationSafe(patch) {
return !('levelVal' in patch)
} Try / catch
try {
applyLoggerPatch(logger, patch)
} catch (e) {
if (e.message === 'levelVal is read-only') {
logger.setLevel(patch.levelVal)
} else throw e
} Prevention
- Treat levelVal as a read-only numeric view; never include it in writable patches.
- Change levels only via logger.level = 'name' or setLevel().
- Exclude symbol-keyed and internal fields when cloning logger objects.
- Code-review any code that writes to logger properties generically (Object.assign/merge).
When it happens
Trigger: logger.levelVal = 30; Object.assign(logger, { levelVal: 30 }); copying properties from one logger object to another including levelVal.
Common situations: Trying to change level 'by value' after reading numeric level APIs; migrating code that manipulated internal fields; bulk-cloning logger state.
Related errors
- missing bindings for child Pino
- only one of target or targets can be specified
- missing bindings for child Pino
- callback must be a function
- Levels comparison should be one of "ASC", "DESC" or "functio
AI-assisted analysis of pinojs/pino@5aa62305c5 (2026-09-02).
Data as JSON: /api/errors/d07c690922be19eb.
Report an issue: GitHub.