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

Support for aggregate errors #87

Merged
merged 7 commits into from
Jan 19, 2022
Merged

Conversation

sameer-coder
Copy link
Contributor

@sameer-coder sameer-coder commented Jan 4, 2022

Example

const log = require('pino')()
const foo = new Error('foo')
const bar = new Error('bar')
const aggregate = new AggregateError([foo, bar], 'aggregated message')
log.error(aggregate)

Produces the following serialized output:

{
  "level": 50,
  "time": 1641313649352,
  "pid": 1788,
  "hostname": "SAMEER",
  "err": {
    "type": "AggregateError",
    "message": "aggregated message",
    "stack": "AggregateError: aggregated message\n at Object.<anonymous> (pino-std-serializers\\example.js:12:19)\n at Module._compile (node:internal/modules/cjs/loader:1108:14)\n at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10)\n at Module.load (node:internal/modules/cjs/loader:988:32)\n at Function.Module._load (node:internal/modules/cjs/loader:828:14)\n at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)\n at node:internal/main/run_main_module:17:47",
    "aggregateErrors": [
      {
        "type": "Error",
        "message": "foo",
        "stack": "Error: foo\n at Object.<anonymous> (pino-std-serializers\\example.js:10:15)\n at Module._compile (node:internal/modules/cjs/loader:1108:14)\n at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10)\n at Module.load (node:internal/modules/cjs/loader:988:32)\n at Function.Module._load (node:internal/modules/cjs/loader:828:14)\n at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)\n at node:internal/main/run_main_module:17:47"
      },
      {
        "type": "Error",
        "message": "bar",
        "stack": "Error: bar\n at Object.<anonymous> (pino-std-serializers\\example.js:11:15)\n at Module._compile (node:internal/modules/cjs/loader:1108:14)\n at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10)\n at Module.load (node:internal/modules/cjs/loader:988:32)\n at Function.Module._load (node:internal/modules/cjs/loader:828:14)\n at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)\n at node:internal/main/run_main_module:17:47"
      }
    ]
  },
  "msg": "aggregated message"
}

Note: AggregateError is only supported by node 15+

Closes #86

test/err.test.js Outdated Show resolved Hide resolved
lib/err.js Outdated Show resolved Hide resolved
test/err.test.js Outdated Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

lgtm

Copy link
Contributor

@voxpelli voxpelli left a comment

Choose a reason for hiding this comment

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

Some comments, thoughts, questions – but most importantly: A note that this is susceptible to infinite recursion. Only the latter is why I set "Request changes" :)

Do note though: I'm not a maintainer of this module

package.json Outdated Show resolved Hide resolved
lib/err.js Outdated Show resolved Hide resolved
lib/err.js Outdated Show resolved Hide resolved
test/err.test.js Outdated Show resolved Hide resolved
test/err.test.js Outdated Show resolved Hide resolved
lib/err.js Outdated
@@ -50,6 +56,10 @@ function errSerializer (err) {
: err.name
_err.message = messageWithCauses(err)
_err.stack = stackWithCauses(err)
// eslint-disable-next-line no-undef
if (hasAggregateError && err instanceof AggregateError && err.errors && Array.isArray(err.errors)) {
_err.aggregateErrors = err.errors.map(errSerializer)
Copy link
Contributor

Choose a reason for hiding this comment

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

This bypasses the if (!val.hasOwnProperty(seen)) { check and thus is susceptible to infinite recursion if an aggregate error includes itself in the list of errors.

We should add a prevents infinite aggregate recursion test + either add a check right here or in the beginning of errSerializer to ensure that infinite is aborted.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@voxpelli I agree that infinite recursion is possible if the aggregate error includes itself in the list of errors but can you provide a scenario where this could happen? I think creating a new aggregate error requires you to provide an iterable of already existing errors so the list of errors cannot contain a reference to the aggregate error itself.

Copy link
Member

Choose a reason for hiding this comment

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

@sameer-coder I'm kind of lost on why it's impossible to write a unit test that cover this case. Can you add one anyway?

lib/err.js Outdated Show resolved Hide resolved
lib/err.js Outdated Show resolved Hide resolved
test/err.test.js Outdated
t.plan(7)
const foo = new Error('foo')
const agg1 = new AggregateError([foo], 'aggregated message') // eslint-disable-line no-undef
agg1.inner = agg1
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not really sure if this test is written correctly

Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't this be agg1.errors.push(agg1)? Although, I agree with you. I'd like to have a clear explanation of when we think this would actually happen, @voxpelli. An AggregateError must be initialized with an array of existing errors. Thus, it cannot be initialized with itself in the aggregate. Anyone appending to the aggregate to its errors list seems a Bad Idea, and a scenario we should just classify as unsupported.

http://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError

Also, the spec requires at least the array of errors be present when creating a new aggregate -- https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-aggregate-error-constructor

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@voxpelli Did you get a chance to look at this again?

Copy link
Contributor

Choose a reason for hiding this comment

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

Anyone appending to the aggregate to its errors list seems a Bad Idea, and a scenario we should just classify as unsupported.

I agree, but since the consequences are catastrophic, I think we should still prevent it.

Same would apply for Error Causes.

Copy link
Member

Choose a reason for hiding this comment

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

@mcollina do you have any thoughts on this? @davidmarkclements ?

Copy link
Member

Choose a reason for hiding this comment

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

I don't think cycles are a problem. It will be catastrophic but also very easy to debug and fix.
They will need to modify the .errors property manually.

To prevernt this, I would just wrap the function in a try/catch

Copy link
Member

Choose a reason for hiding this comment

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

So, to be clear, we are agreeing to remove the edge case detection in this code?

Copy link
Contributor

Choose a reason for hiding this comment

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

And if someone jumps on eg. huntr.dev and reports this as a potential DDoS vector, then we'll simply say that its not valid?

I'm okay with that 👍

Copy link
Member

Choose a reason for hiding this comment

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

Yes. As stated, it's unsupported.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jsumners @voxpelli I have updated the code

Copy link
Member

@jsumners jsumners left a comment

Choose a reason for hiding this comment

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

Looks good to me.

@mcollina mcollina merged commit 86a6ac9 into pinojs:master Jan 19, 2022
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.

Serialisation of AggregateError
4 participants