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

feat: Support serializing primitives within Error causes (#158) #159

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

darcyrush
Copy link

@darcyrush darcyrush commented Nov 12, 2024

Allow primitives to be returned as error causes when passed via the ErrorOptions object in an Error object constructor.

test/err.test.js Outdated
Comment on lines 69 to 70
// ["an", "array"],
// { an: "object" },
Copy link
Author

Choose a reason for hiding this comment

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

I'm not sure what the desired output would be for the string message for err. As is, output would be

foo: an,array
foo: [object: Object]

Neither are great, but if acceptable, I can add these back

Copy link
Author

Choose a reason for hiding this comment

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

Open to alternative suggestions

Copy link
Author

@darcyrush darcyrush Nov 28, 2024

Choose a reason for hiding this comment

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

This is still the most challenging part. For example how to output ['nested', [new Error('bar', { cause: 'baz'})]] if this is set as a cause. The same for an object.

err-with-cause is much easier in this regard because it isn't opinionated in how to format the error.

Copy link
Contributor

Choose a reason for hiding this comment

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

Find prior art and do what the prior art does – node.js and browsers all have support for logging error causes and if Pino are to handle non-error causes then it should not deviate from them in how it does so

Copy link
Author

@darcyrush darcyrush Dec 9, 2024

Choose a reason for hiding this comment

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

Well it appears that for this particular serializer (err.js), it is actually quite opinionated on the (message) output style, which is why this question remains unanswered. The only prior art of this style that I am aware of is within this library, for example with;

throw new Error("error a", { cause new Error("error b") })

The message string output is;

error a: error b

So basically; ${error.message}: ${cause.message}. Since it only deals with errors, there is no style for how to handle non error objects.

Since nothing was defined, I investigated an approach which mainly uses JSON.stringify along with handling some edge cases, but the complexity really jumped, and I still wasn't sure how to display nested errors. For example, if the cause is a string, number, object, array, null or symbol, this can be somewhat neatly output;

error a: "string"
error a: 123
error a: {"nested":{"object":"value"}}
error a: [["string"],123]
error a: Symbol(symbol)
error a: null

But what about an error nested within an array or object? The err.js serializer compacts the error format to something like ${error.message}: ${cause.message} so continuing with that style yields something like;

error a: {"object": error b: error c}

It isn't really intuitive since it looks like the object is malformed, and isn't entirely obvious you are even looking at a nested error unless the error messages actually contain the word Error or equivalent within.

This is not how err-with-cause.test.js outputs errors, which is in line with what NodeJS and Browsers output, and as such doesn't really suffer from any of this.

lib/err-with-cause.js Outdated Show resolved Hide resolved
@@ -75,6 +75,22 @@ test('keeps non-error cause', () => {
assert.strictEqual(serialized.cause, 'abc')
})

test('keeps non-error cause from constructor', () => {
Copy link
Member

Choose a reason for hiding this comment

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

I think this test hints at why supporting non-Error objects is probably not a great idea. What should be done if someone threw?:

{
	cause: {
		foo: 'foo',
		cause: {
			bar: 'bar',
			cause: {
				baz: 'baz'
			}
		}
	}
}

Copy link

@ifeltsweet ifeltsweet Nov 27, 2024

Choose a reason for hiding this comment

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

If cause is not an error then it should be serialized as a non error value would be.

Copy link
Member

Choose a reason for hiding this comment

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

But the point of this PR is to serialize non-error objects as error objects, right?

Copy link
Author

@darcyrush darcyrush Nov 27, 2024

Choose a reason for hiding this comment

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

I guess an additional test is needed to vizualize the output, but really the point of this PR was to serialize non-error objects and display them as is, not to serialize them as Error objects specifically.

So if an object with nested objects was passed as a cause, regardless if its nested properties were named cause or something else, the entire object and its nested properties and values would be output.

@voxpelli
Copy link
Contributor

An alternative that I looked into some years ago was to align with what Node.js itself does when it serializes: voxpelli/pony-cause#23

Would be kind of nice to fix this "upstream" in pony-cause as well

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

@mcollina
Copy link
Member

mcollina commented Dec 7, 2024

ping @jsumners

Comment on lines +69 to +72
// ['an', 'array'],
// ['a', ['nested', 'array']],
// { an: 'object' },
// { a: { nested: 'object' } },
Copy link
Member

Choose a reason for hiding this comment

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

Are these commented out because String({ an: 'object' }) returns [object Object]?

Copy link
Author

Choose a reason for hiding this comment

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

Originally, yes. I commented about a WIP approach I have locally in this comment.

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.

5 participants