Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(metadata precedence) - fix metadata override issues #1989

Conversation

maverick1872
Copy link
Member

@maverick1872 maverick1872 commented Dec 15, 2021

Purpose

Fixes #2029 #1665

This PR intends to fix the issue regarding child loggers and the current implementation allowing them to override metadata from a parent. This PR is a reincarnation of #1598 now that we have some activity around the project again. I have pulled the latest changes in from master at this point but I will have to set aside a little time to go through these changes and re-verify them and the expected behavior.

Per the original PR I believe I had addressed all the feedback I had received from @DABH but I could be wrong. Holiday vacation starts for me this weekend so I'll add it to me to-do list in the near term to go back through all of this and re-verify they're still the most appropriate changes. In the meantime I'd love any feedback anyone wants to give on this implementation and I'll gladly keep it in mind as I'm doing my re-review!

Original PR Description

When creating a child logger with overrides for defaultMeta properties they do not actually override. Is due to the fact that the child logger is overriding the write function which is inappropriate. It should actually be overriding how the default metadata is being added to the log statement. I will have test cases incoming to prove functionality. All existing test cases are passing.

This should address issue #1596

@maverick1872
Copy link
Member Author

@fearphage @wbt - Pinging for awareness (hope you don't mind) as I've been informed you're currently the active maintainers. Please let me know if you need anything from me!

lib/winston/create-logger.js Outdated Show resolved Hide resolved
lib/winston/logger.js Outdated Show resolved Hide resolved
@wbt
Copy link
Contributor

wbt commented Dec 16, 2021

@fearphage @wbt - Pinging for awareness (hope you don't mind) as I've been informed you're currently the active maintainers. Please let me know if you need anything from me!

Thanks for getting back into this!

I think 'active maintainers' is a bit of an overstatement, at least speaking for myself. I'm likely able to chip in here and there, with initial motivation on getting out a release with a fix for a memory-leak problem I'd found & fixed a year and a half ago; I've already been able to help with releasing some important changes merged into winston-transport a while back. My JavaScript expertise level suffices for the applications I work on primarily, but a general-purpose logging library like Winston and PRs like this justifiably make more extensive use of lower-level language functions than is typical for my other current work. (My background goes down to the level of 'programming' via hardware configuration, so it's not completely out of scope, just not a current active focus.) I don't want to merge code into such a highly used library without a pretty high degree of confidence that it's not going to break a funky edge case (as examples across the long tail of rare occurrences can likely be found in practice with this library) or introduce a possible vulnerability.

Especially after Log4Shell, I'm especially sensitive around the potential security implications of introducing new paths by which data could be transferred into a more privileged setting. Allowing a log object which may be ultimately constructed on the basis of attacker-controllable inputs to arbitrarily override metadata fields which might've previously only been settable in more trusted contexts and which may control how the message is routed and handled at least raises flags that IMO require closer review.

The PR is appreciated, in solving what seems to be an issue worth solving. The proposed diff appears to include at least some changes where I don't understand how they contribute to solving the issue, but that might say more about the limits of my understanding than the specific changes themselves. (Some cases could also be the latter, just pending that re-review you've noted is on your to-do list.) I've added a few comments/questions but am not convinced that I presently have the unfunded bandwidth and/or expertise that a solid review of this PR as it stands would likely require. However, I hope this doesn't discourage you from still doing that re-review and improving the PR as you see fit, in the event that someone else is able to review it!

Also, if you send a private email to @DABH and @indexzero and can pass muster with them, you might be able to merge commits yourself if you have adequate confidence in the content. Also, if you know of organizations looking to make significant contributions toward the maintenance of widely-used open-source logging software (especially after recent news), please feel free to make an introduction!

@DABH
Copy link
Contributor

DABH commented Dec 23, 2021

Looks like @wbt left at least a few minor comments to address, but I will also try to help with a more thorough review once those are addressed. Also, I have invited @maverick1872 to be a maintainer -- more hands the better :) Looking forward to 3.4!

@maverick1872 maverick1872 force-pushed the bugfix/child-logger-metadata-override-fix branch from bcf5f1a to 5df5786 Compare January 30, 2022 20:30
@maverick1872 maverick1872 force-pushed the bugfix/child-logger-metadata-override-fix branch from e18e1bb to 237534a Compare January 30, 2022 22:01
@maverick1872
Copy link
Member Author

@wbt @fearphage @DABH Would it be possible for me to get some reviews on this implementation as it currently stands. Hoping to get it moved forward and get a fix out for the community.

Copy link
Contributor

@DABH DABH left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments and questions, but this is mostly looking good to me :) Thank you, @maverick1872 , for all of your work on this! Very excited to get your work into the hands of the community at last!

const logger = this;
const clonedParentMetadata = JSON.parse(JSON.stringify(this.defaultMeta))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really the fastest way to clone an object in JS in 2022? :O

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also can we do ; at ends of lines?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly I'm not entirely sure. Versions of node we currently support (>12) do not have a proper language native implementation for deep cloning objects. So as far as I can determine the only solutions available to us at this current juncture are the following:

  • naive stringify & reparse (my current implementation)
    • not the most performant but should function consistently
  • walking the object in it's entirety to clone without persisting object references
    • prone to implementation errors introducing additional edge cases
  • external dependency such as lodash.close/deepclone
    • don't like the idea of introducing additional external dependencies

I feel like none of these are good solutions. Recently there was an introduction of structuredClone but it's only bundled with Node v17.x.x. We could alternatively pull a pollyfill for the functionality but I'm not sure if that's something we want to do or not.

Alternatively if anyone knows of a better way to clone to deeply clone objects I'd love to learn as this has been a reoccurring pain point for myself.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we're using a safe-stringify function in this library in general to avoid issues with circular references etc.; that might be a good idea here.

Copy link
Member Author

@maverick1872 maverick1872 Feb 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we're using a safe-stringify function in this library in general to avoid issues with circular references etc.; that might be a good idea here.

I was unaware of this and typically I personally like to see when a resultant object has a circular reference myself so I default to letting it throw the TypeError. But in the context of a library such as Winston it probably is better to handle them gracefully. Will update.

Also can we do ; at ends of lines?

Since I forgot to address this directly; Sounds like we should make an addition to the linter config 😆


logger.write(infoClone);
}
defaultMeta: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forget most of the design decisions here. Should this be called defaultMeta, or is meta more appropriate? I don't recall, maybe I will remember more or learn more as I read through 😬 Some docs around recommended usage for meta stuff would be useful (if we don't already have that), although I see a bunch of tests down below so maybe those are pretty self-documenting :)

Copy link
Member Author

@maverick1872 maverick1872 Feb 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is a great question. The short answer is that it's consistent with every other usage regarding configuring default metadata.

I want to preface the following with it's possible that I'm way off base in my understanding of how defaultMeta is supported overall. As far as I can tell there is no explicit definition of a defaultMeta property on our Logger instance anywhere. Although we reference it in so many places. As such I believe that this is a "convention" based implementation and not necessarily a 1st class implementation. If my presumptions here are accurate, it's extremely important that this convention is not broken.

P.S. It is possible that there's a configuration that I'm unaware of that's leveraged in the dynamic Object.defineProperty calls that would make this non convention based; but I haven't seen anything as of yet.

@@ -288,6 +270,10 @@ class Logger extends Transform {
info[LEVEL] = info.level;
}

if (!info[MESSAGE]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for? This looks unrelated to child loggers / metadata -- is this fixing some other issue, or is it indeed related? If it's related, a comment might be helpful explaining why?

Copy link
Member Author

@maverick1872 maverick1872 Feb 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this to have been an issue with the SPLAT implementation and associated hotpath optimizations. If I am remembering correctly this stemmed from this comment on my previous PR. Additional discussion was had in #1596 way back when. If we want to remove this addition from this addition I'm happy to (so long as all my tests that I've written are still passing. 😃

Edit: I've verified all my tests work without this implementation. Happy to remove. Alternatively I can try and find some time to add in some additional SPLAT tests to re-verify my understanding of this addition; although that may take some time. About to be fairly busy in the coming weeks.

@@ -647,7 +633,9 @@ class Logger extends Transform {

_addDefaultMeta(msg) {
if (this.defaultMeta) {
Object.assign(msg, this.defaultMeta);
// The msg must be cloned as it is being mutated, but any metadata provided with the msg takes precedence over default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, so Object.assign(msg, this.defaultMeta); doesn't mutate msg? But Object.assign(msg, this.defaultMeta, msgClone); does? This comment confused me a bit... and then I'm also confused why msg is getting merged with msgClone. Is it just to ensure msgClone's metadata is used instead of defaultMeta? But then wouldn't there be some simpler way to write that rather than using Object.assign?

Copy link
Member Author

@maverick1872 maverick1872 Feb 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is a mutating operation. The issue with this mutation is that we lost the ability to apply the metadata defined in the appropriate order of precedence.

Considerations:

  • We don't want to mutate the logger's default metadata so we can't use it as the target.
  • We have to mutate the msg itself as it's what is used in the write method; therefore it must be the target of this operation
  • Order of precedence is the default metadata, then the msg's configured metadata

Due to the above, and the fact that we HAVE to mutate msg given the current dependency of other code paths (such as write), we have to have a means to reapply any msg specific metadata that overrides default metadata. Without this the default metadata will always take precedence.

lib/winston/profiler.js Show resolved Hide resolved
test/unit/winston/logger.test.js Outdated Show resolved Hide resolved
test/unit/winston/logger.test.js Outdated Show resolved Hide resolved
test/unit/winston/logger.test.js Outdated Show resolved Hide resolved
@maverick1872
Copy link
Member Author

@DABH @wbt @fearphage Do any of you have any more feedback? I'd like to get this pushed over the finish line in the near future if possible and I'm not so inclined to merge without another maintainers approval.

Copy link
Contributor

@wbt wbt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have enough in-depth knowledge of this part of the code-base to do a more detailed review within the time-budget available.

lib/winston/profiler.js Show resolved Hide resolved
@tyuv
Copy link

tyuv commented Mar 23, 2022

@maverick1872 is it possible to separate out and merge the small _addDefaultMeta addition in profiler.js to fix #1665?
Could be very helpful and looks like it can be approved much more quickly

@maverick1872
Copy link
Member Author

@maverick1872 is it possible to separate out and merge the small _addDefaultMeta addition in profiler.js to fix #1665? Could be very helpful and looks like it can be approved much more quickly

Unfortunately that implementation is directly tied to the fixes for default metadata. I'm inclined to go ahead and merge this as I feel it's in the WinstonJS community's best interest. I'm hoping to spend some time this week sorting out how I would cut a new release prior to merging this so I can do it all back to back. Plus that should give the other maintainers some time to block this if they see fit for some reason.

@wbt
Copy link
Contributor

wbt commented Mar 24, 2022

I can also help with cutting a release if need be, especially if you update the changelog as that's the biggest part of the job.

@maverick1872
Copy link
Member Author

I can also help with cutting a release if need be, especially if you update the changelog as that's the biggest part of the job.

Ended up getting sick past couple days so I'll get the changelog updated early this week; if not today - I'd love some insight if possible though as to our process for that. Do we have it documented anywhere?

@wbt
Copy link
Contributor

wbt commented Mar 29, 2022

That's a good question. I've been mostly following along with the vbump script that @indexzero wrote several years ago, but the main steps I've been doing are as follows:

  1. Complete merging in any PRs that should be part of the release.
  2. Update the changelog. Check to make sure you've caught everything using GitHub's compare tool (example URL for next release is here. It's nice to thank the contributors here. It's nice to organize this by which changes would merit which level of semver bump, and especially call out any breaking changes (major-version-number) concisely at the start.
  3. Update the version number in package.json, bumping as appropriate for semver based on the most significant position change trigger from the changelog you just wrote/reviewed. Do not miss this step!
  4. Make sure your local master branch is up to date.
  5. Make sure all the lint checks and tests pass, beyond what the CI might've told you.
  6. On the Releases tab in the GitHub UI, click 'Draft a new release' in the upper right corner.
  7. Under the 'Choose a tag' dropdown, type the name of the new version starting with a v (e.g. v3.7.0) and don't forget to click the 'Create new tag on publish' option below (this step is annoyingly easy to miss):
    image
  8. Paste the same version number, with or without the v (with is probably better) in the release title box.
  9. Paste the contents of the changelog for this release in the 'Describe this release' box.
  10. Click "Publish release."
  11. Back on the command line, npm publish and complete npm 2FA as needed.
  12. Keep a closer-than-usual eye on issues in the hours and days that follow, prepared to quickly revert/address anything that might be associated with that release.

A more professional version of this would probably use a release branch off master to make sure no other maintainers merge a PR into master between the loading of a compare view for changelog preparation and completion of the process, but we're such a small team that the extra steps are probably not needed. After release, you can also verify with the compare view between the new and prior release tags to see when the latest change was and verify it was before you started the process.

@maverick1872
Copy link
Member Author

That's a good question. I've been mostly following along with the vbump script that @indexzero wrote several years ago, but the main steps I've been doing are as follows:

I'll add a readme to the project for this if you're alright with that. More this is documented the better.

@wbt
Copy link
Contributor

wbt commented Mar 29, 2022

It probably doesn't need to go in the public readme which gets automatic visibility to users and potential users, but some other doc could be a good idea.

@maverick1872 maverick1872 merged commit ba93eae into winstonjs:master Mar 31, 2022
@maverick1872 maverick1872 deleted the bugfix/child-logger-metadata-override-fix branch March 31, 2022 00:38
wbt added a commit that referenced this pull request Apr 4, 2022
wbt added a commit that referenced this pull request Apr 4, 2022
* Revert "fix(metadata precedence) - fix metadata override issues (#1989)"

This reverts commit ba93eae.

* Update changelog

* Update version number
@@ -647,7 +634,9 @@ class Logger extends Transform {

_addDefaultMeta(msg) {
if (this.defaultMeta) {
Object.assign(msg, this.defaultMeta);
// The msg must be cloned as it is being mutated, but any metadata provided with the msg takes precedence over default
const msgClone = JSON.parse(jsonStringify(msg));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super late to the party, but because of this change there is no possibility of logging instances of error objects in any way. All the previous suggestions of adding custom formatters in order to extract separate parts of the error object are not applicable anymore. By the time any formatter is called, the error object was already stringified and parsed back, thus losing message, stack, etc. props. As of now, a user of this library should choose: be able to log errors or use defaultMeta logger property.

Copy link
Member Author

@maverick1872 maverick1872 Apr 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks to be the case. I was under the impression at the time that the ErrorFormatter addressed the use case sufficiently. Obviously I was wrong in that respect. Hence why we did a patch release to "revert" the regression.

I'm going to be working through these scenarios this week ideally to fix the regression contained within the metadata fixes. Any test cases you have would be hugely appreciated!!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any side-effects if I follow this approach?

const msgClone = JSON.parse(jsonStringify(msg)); Object.assign(msg, this.defaultMeta, msgClone);

cause I am facing the issue where defaultMeta is overriding my msg data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG]: Inconsistent metadata overrides (master issue)
6 participants