Skip to content

Commit 056d402

Browse files
committed
feat(stdlib): make AppError compatible with Sentry
By setting the `Error#message`, users don't need separate handling of AppErrors when capturing them with Sentry. In combination with the extra error data integration, the `info` prop is captured as well.
1 parent 2134362 commit 056d402

3 files changed

Lines changed: 49 additions & 15 deletions

File tree

‎packages/server/src/middleware/error.js‎

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,24 +38,17 @@ export function errorHandler(opts) {
3838
err = AppError.serverError({}, err);
3939
}
4040

41-
if (err.status >= 500) {
42-
if (_compasSentryExport) {
43-
if (err === origErr) {
44-
// An AppError.serverError was thrown.
45-
_compasSentryExport.captureException(
46-
new Error(err.key, {
47-
cause: err,
48-
}),
49-
);
50-
} else {
51-
// Something else was thrown.
52-
_compasSentryExport.captureException(origErr);
53-
}
54-
}
41+
const isUnexpectedError = err.status >= 500;
5542

43+
if (isUnexpectedError) {
44+
// Upgrade to error logging, developer probably has alerting on error logs.
5645
log = ctx.log.error;
5746
}
5847

48+
if (_compasSentryExport && isUnexpectedError) {
49+
_compasSentryExport.captureException(origErr);
50+
}
51+
5952
ctx.status = err.status;
6053

6154
const formatted = AppError.format(err);
@@ -66,8 +59,14 @@ export function errorHandler(opts) {
6659

6760
if (onAppError === defaultOnAppError) {
6861
if (isProduction()) {
62+
// Delete generic internals
6963
delete formatted.stack;
7064
delete formatted.cause;
65+
66+
if (isUnexpectedError) {
67+
// Remove any possible internal details. In explicit 400's for example, info is useful to the caller.
68+
delete formatted.info;
69+
}
7170
}
7271

7372
ctx.body = formatted;

‎packages/stdlib/src/error.js‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,16 @@ export class AppError extends Error {
1818
* @param {Error} [cause]
1919
*/
2020
constructor(key, status, info, cause) {
21-
super();
21+
let errMessage = info?.message ?? info?.type ?? "";
22+
if (typeof errMessage !== "string") {
23+
errMessage = "";
24+
}
25+
26+
if (errMessage) {
27+
errMessage = `: ${errMessage}`;
28+
}
29+
30+
super(`AppError: ${key}${errMessage}`);
2231

2332
this.key = key;
2433
this.status = status;

‎packages/stdlib/src/error.test.js‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ test("stdlib/error", (t) => {
1111
const e = new AppError(500, 500);
1212

1313
t.equal(AppError.instanceOf(e), true);
14+
t.equal(e.message, "AppError: error.server.internal");
1415
t.equal(e.key, "error.server.internal");
1516
t.equal(e.info.appErrorConstructParams.key, 500);
1617
t.equal(e.cause.key, 500);
@@ -20,18 +21,43 @@ test("stdlib/error", (t) => {
2021
const e = new AppError("test.error", "500");
2122

2223
t.equal(AppError.instanceOf(e), true);
24+
t.equal(e.message, "AppError: error.server.internal");
2325
t.equal(e.key, "error.server.internal");
2426
t.equal(e.info.appErrorConstructParams.key, "test.error");
2527
t.equal(e.info.appErrorConstructParams.status, "500");
2628
t.equal(e.cause.key, "test.error");
2729
t.equal(e.cause.status, "500");
2830
});
2931

32+
t.test("AppError sets Error#message based on info object", (t) => {
33+
t.equal(new AppError("foo", 200, {}).message, "AppError: foo");
34+
t.equal(
35+
new AppError("foo", 200, {
36+
message: "message prop",
37+
}).message,
38+
"AppError: foo: message prop",
39+
);
40+
t.equal(
41+
new AppError("foo", 200, {
42+
type: "type prop",
43+
}).message,
44+
"AppError: foo: type prop",
45+
);
46+
t.equal(
47+
new AppError("foo", 200, {
48+
type: "type prop",
49+
message: "prefers message",
50+
}).message,
51+
"AppError: foo: prefers message",
52+
);
53+
});
54+
3055
t.test("AppError#format with stack", (t) => {
3156
try {
3257
throw AppError.validationError("test.error", {});
3358
} catch (e) {
3459
t.ok(AppError.instanceOf(e));
60+
t.equal(e.message, "AppError: test.error");
3561

3662
const formatted = AppError.format(e);
3763
t.equal(formatted.key, "test.error");

0 commit comments

Comments
 (0)