Skip to content

Commit 6a14595

Browse files
authored
feat(logger): add cause to formatted error (#1617)
1 parent a57dc11 commit 6a14595

File tree

2 files changed

+87
-0
lines changed

2 files changed

+87
-0
lines changed

Diff for: packages/logger/src/formatter/LogFormatter.ts

+22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
import { LogFormatterInterface } from '.';
22
import { LogAttributes, UnformattedAttributes } from '../types';
33

4+
/**
5+
* Typeguard to monkey patch Error to add a cause property.
6+
*
7+
* This is needed because the `cause` property was added in Node 16.x.
8+
* Since we want to be able to format errors in Node 14.x, we need to
9+
* add this property ourselves. We can remove this once we drop support
10+
* for Node 14.x.
11+
*
12+
* @see 1361
13+
* @see https://nodejs.org/api/errors.html#errors_error_cause
14+
*/
15+
const isErrorWithCause = (
16+
error: Error
17+
): error is Error & { cause: unknown } => {
18+
return 'cause' in error;
19+
};
20+
421
/**
522
* This class defines and implements common methods for the formatting of log attributes.
623
*
@@ -31,6 +48,11 @@ abstract class LogFormatter implements LogFormatterInterface {
3148
location: this.getCodeLocation(error.stack),
3249
message: error.message,
3350
stack: error.stack,
51+
cause: isErrorWithCause(error)
52+
? error.cause instanceof Error
53+
? this.formatError(error.cause)
54+
: error.cause
55+
: undefined,
3456
};
3557
}
3658

Diff for: packages/logger/tests/unit/formatter/PowertoolLogFormatter.test.ts

+65
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,71 @@ describe('Class: PowertoolLogFormatter', () => {
307307

308308
expect(shouldThrow).toThrowError(expect.any(URIError));
309309
});
310+
311+
test('when an error with cause of type Error is formatted, the cause key is included and formatted', () => {
312+
// Prepare
313+
const formatter = new PowertoolLogFormatter();
314+
class ErrorWithCause extends Error {
315+
public cause?: Error;
316+
public constructor(message: string, options?: { cause: Error }) {
317+
super(message);
318+
this.cause = options?.cause;
319+
}
320+
}
321+
322+
// Act
323+
const formattedURIError = formatter.formatError(
324+
new ErrorWithCause('foo', { cause: new Error('bar') })
325+
);
326+
327+
// Assess
328+
expect(formattedURIError).toEqual({
329+
location: expect.stringMatching(/PowertoolLogFormatter.test.ts:[0-9]+/),
330+
message: 'foo',
331+
name: 'Error',
332+
stack: expect.stringMatching(
333+
/PowertoolLogFormatter.test.ts:[0-9]+:[0-9]+/
334+
),
335+
cause: {
336+
location: expect.stringMatching(
337+
/PowertoolLogFormatter.test.ts:[0-9]+/
338+
),
339+
message: 'bar',
340+
name: 'Error',
341+
stack: expect.stringMatching(
342+
/PowertoolLogFormatter.test.ts:[0-9]+:[0-9]+/
343+
),
344+
},
345+
});
346+
});
347+
348+
test('when an error with cause of type other than Error is formatted, the cause key is included as-is', () => {
349+
// Prepare
350+
const formatter = new PowertoolLogFormatter();
351+
class ErrorWithCause extends Error {
352+
public cause?: unknown;
353+
public constructor(message: string, options?: { cause: unknown }) {
354+
super(message);
355+
this.cause = options?.cause;
356+
}
357+
}
358+
359+
// Act
360+
const formattedURIError = formatter.formatError(
361+
new ErrorWithCause('foo', { cause: 'bar' })
362+
);
363+
364+
// Assess
365+
expect(formattedURIError).toEqual({
366+
location: expect.stringMatching(/PowertoolLogFormatter.test.ts:[0-9]+/),
367+
message: 'foo',
368+
name: 'Error',
369+
stack: expect.stringMatching(
370+
/PowertoolLogFormatter.test.ts:[0-9]+:[0-9]+/
371+
),
372+
cause: 'bar',
373+
});
374+
});
310375
});
311376

312377
describe('Method: formatTimestamp', () => {

0 commit comments

Comments
 (0)