fix(levels): useLevelLabels permanently mutates initial cache #684
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Issue
Once a logger instance is created with
useLevelLabels: true
this can never be undone.useLevelLabels: false
will have no effectpino
instances created by child packages, using incompatible semver (and thus a different/dup package) will not be impacted by this, and will continue to operate independentlyOpinion
I can see some benefit in keeping the log records appearing uniformly everywhere. However, I might expect that to be a static method, or otherwise very explicitly documented. Since this is documented as a constructor-level option, I expect it to behave accordingly.
Root Cause
An initial cache of levels (as numbers) is created when the package is required. This is assigned to the instance prototype. When
useLevelLabels
istrue
, genCache is called to update the instance level display labels. This modifies the instance property, stored on the prototype, which points to the cached, initial object.Solution
As I see it, there are two ways to approach this:
Generate the prototype property
My preferred solution would be to prevent ever allowing the defaults to be mutable but wrapping them in a getter function. Instead of assigning
initialLsCache
directly, we would callgetInitialLsCache
to get a copy, and preserve the original.However, this would be executed for every Pino instance created. This library has been optimized for performance, so this is probably not the preferred solution.
Update the instance with a copy
Since this is an uncommon use case, we want to avoid adding extra complexity into the commonly used code paths. Instead, we'll make a shallow copy of the initial cache object when we need to make changes. We can do this in the existing
genLsCache
method.(or use
Object.assign
directly with thereduce
initial value)This code will only be executed in the cases when it is needed. It prioritizes overall performance over cache integrity.