Add stringifyError that is useful for logging e.g. Something went wrong, ${stringifyError(e)}

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2021-10-20 21:16:35 +02:00
parent b61c50a12f
commit 8c30ae8902
7 changed files with 63 additions and 21 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/errors': patch
---
Add `stringifyError` that is useful for logging e.g. `Something went wrong, ${stringifyError(e)}`
+4 -1
View File
@@ -18,7 +18,7 @@ export class ConflictError extends CustomErrorBase {}
export class CustomErrorBase extends Error {
constructor(message?: string, cause?: Error | unknown);
// (undocumented)
readonly cause?: Error;
readonly cause?: Error | undefined;
}
// @public
@@ -98,4 +98,7 @@ export function serializeError(
includeStack?: boolean;
},
): SerializedError;
// @public
export function stringifyError(error: unknown): string;
```
+4 -16
View File
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { stringifyError } from '../serialization';
import { isError } from './assertion';
/**
@@ -32,25 +33,12 @@ import { isError } from './assertion';
* ```
*/
export class CustomErrorBase extends Error {
readonly cause?: Error;
readonly cause?: Error | undefined;
constructor(message?: string, cause?: Error | unknown) {
let assignedCause: Error | undefined = undefined;
let fullMessage = message;
if (cause !== undefined) {
let causeStr;
if (isError(cause)) {
assignedCause = cause;
causeStr = String(cause);
// Prefer the cause.toString, but if it's not implemented we use a nicer fallback
if (causeStr === '[object Object]') {
causeStr = `${cause.name}: ${cause.message}`;
}
} else {
causeStr = `unknown error '${cause}'`;
}
const causeStr = stringifyError(cause);
if (fullMessage) {
fullMessage += `; caused by ${causeStr}`;
} else {
@@ -63,6 +51,6 @@ export class CustomErrorBase extends Error {
Error.captureStackTrace?.(this, this.constructor);
this.name = this.constructor.name;
this.cause = assignedCause;
this.cause = isError(cause) ? cause : undefined;
}
}
@@ -14,7 +14,8 @@
* limitations under the License.
*/
import { deserializeError, serializeError } from './error';
import { NotModifiedError } from '../errors';
import { deserializeError, serializeError, stringifyError } from './error';
class CustomError extends Error {
readonly customField: any;
@@ -61,4 +62,30 @@ describe('serialization', () => {
expect(withoutStack1.stack).not.toBeDefined();
expect(withoutStack2.stack).not.toBeDefined();
});
it('stringifies all supported forms', () => {
expect(stringifyError({})).toEqual("unknown error '[object Object]'");
expect(
stringifyError({
toString() {
return 'str';
},
}),
).toEqual("unknown error 'str'");
expect(
stringifyError({
name: 'not used',
message: 'not used',
toString() {
return 'str';
},
}),
).toEqual('str');
expect(stringifyError({ name: 'N', message: 'm1' })).toEqual('N: m1');
expect(stringifyError(new NotModifiedError('m2'))).toEqual(
'NotModifiedError: m2',
);
expect(stringifyError(new Error('m3'))).toEqual('Error: m3');
expect(stringifyError(new TypeError('m4'))).toEqual('TypeError: m4');
});
});
@@ -19,6 +19,7 @@ import {
deserializeError as deserializeErrorInternal,
serializeError as serializeErrorInternal,
} from 'serialize-error';
import { isError } from '../errors';
/**
* The serialized form of an Error.
@@ -78,3 +79,19 @@ export function deserializeError<T extends Error = Error>(
}
return result;
}
/**
* Stringifies an error, including its name and message where available.
*
* @param error - The error.
* @public
*/
export function stringifyError(error: unknown): string {
if (isError(error)) {
// Prefer error.toString, but if it's not implemented we use a nicer fallback
const str = String(error);
return str !== '[object Object]' ? str : `${error.name}: ${error.message}`;
}
return `unknown error '${error}'`;
}
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
export { deserializeError, serializeError } from './error';
export { deserializeError, serializeError, stringifyError } from './error';
export type { SerializedError } from './error';
export { parseErrorResponse } from './response';
export type { ErrorResponse } from './response';
@@ -20,7 +20,7 @@ import {
parseEntityRef,
UNSTABLE_EntityStatusItem,
} from '@backstage/catalog-model';
import { SerializedError } from '@backstage/errors';
import { SerializedError, stringifyError } from '@backstage/errors';
import { Knex } from 'knex';
import { uniqBy } from 'lodash';
import { v4 as uuid } from 'uuid';
@@ -49,7 +49,9 @@ export class Stitcher {
try {
await this.stitchOne(entityRef);
} catch (error) {
this.logger.error(`Failed to stitch ${entityRef}, ${error}`);
this.logger.error(
`Failed to stitch ${entityRef}, ${stringifyError(error)}`,
);
}
}
}