Skip to content

Commit

Permalink
feat(logger): add cause to formatted error (#1617)
Browse files Browse the repository at this point in the history
  • Loading branch information
dreamorosi authored Jul 17, 2023
1 parent a57dc11 commit 6a14595
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 0 deletions.
22 changes: 22 additions & 0 deletions packages/logger/src/formatter/LogFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
import { LogFormatterInterface } from '.';
import { LogAttributes, UnformattedAttributes } from '../types';

/**
* Typeguard to monkey patch Error to add a cause property.
*
* This is needed because the `cause` property was added in Node 16.x.
* Since we want to be able to format errors in Node 14.x, we need to
* add this property ourselves. We can remove this once we drop support
* for Node 14.x.
*
* @see 1361
* @see https://nodejs.org/api/errors.html#errors_error_cause
*/
const isErrorWithCause = (
error: Error
): error is Error & { cause: unknown } => {
return 'cause' in error;
};

/**
* This class defines and implements common methods for the formatting of log attributes.
*
Expand Down Expand Up @@ -31,6 +48,11 @@ abstract class LogFormatter implements LogFormatterInterface {
location: this.getCodeLocation(error.stack),
message: error.message,
stack: error.stack,
cause: isErrorWithCause(error)
? error.cause instanceof Error
? this.formatError(error.cause)
: error.cause
: undefined,
};
}

Expand Down
65 changes: 65 additions & 0 deletions packages/logger/tests/unit/formatter/PowertoolLogFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,71 @@ describe('Class: PowertoolLogFormatter', () => {

expect(shouldThrow).toThrowError(expect.any(URIError));
});

test('when an error with cause of type Error is formatted, the cause key is included and formatted', () => {
// Prepare
const formatter = new PowertoolLogFormatter();
class ErrorWithCause extends Error {
public cause?: Error;
public constructor(message: string, options?: { cause: Error }) {
super(message);
this.cause = options?.cause;
}
}

// Act
const formattedURIError = formatter.formatError(
new ErrorWithCause('foo', { cause: new Error('bar') })
);

// Assess
expect(formattedURIError).toEqual({
location: expect.stringMatching(/PowertoolLogFormatter.test.ts:[0-9]+/),
message: 'foo',
name: 'Error',
stack: expect.stringMatching(
/PowertoolLogFormatter.test.ts:[0-9]+:[0-9]+/
),
cause: {
location: expect.stringMatching(
/PowertoolLogFormatter.test.ts:[0-9]+/
),
message: 'bar',
name: 'Error',
stack: expect.stringMatching(
/PowertoolLogFormatter.test.ts:[0-9]+:[0-9]+/
),
},
});
});

test('when an error with cause of type other than Error is formatted, the cause key is included as-is', () => {
// Prepare
const formatter = new PowertoolLogFormatter();
class ErrorWithCause extends Error {
public cause?: unknown;
public constructor(message: string, options?: { cause: unknown }) {
super(message);
this.cause = options?.cause;
}
}

// Act
const formattedURIError = formatter.formatError(
new ErrorWithCause('foo', { cause: 'bar' })
);

// Assess
expect(formattedURIError).toEqual({
location: expect.stringMatching(/PowertoolLogFormatter.test.ts:[0-9]+/),
message: 'foo',
name: 'Error',
stack: expect.stringMatching(
/PowertoolLogFormatter.test.ts:[0-9]+:[0-9]+/
),
cause: 'bar',
});
});
});

describe('Method: formatTimestamp', () => {
Expand Down

0 comments on commit 6a14595

Please sign in to comment.