Improve error handling

This commit is contained in:
tdurieux
2026-05-06 16:45:22 +03:00
parent 3613c895c8
commit dcb524c8c1
10 changed files with 72 additions and 12 deletions
+4
View File
@@ -12,6 +12,7 @@ export default class AnonymousError extends CustomError {
value?: unknown;
httpStatus?: number;
cause?: Error;
private explicitUrl?: string;
constructor(
message: string,
@@ -19,15 +20,18 @@ export default class AnonymousError extends CustomError {
httpStatus?: number;
cause?: Error;
object?: unknown;
url?: string;
}
) {
super(message);
this.value = opt?.object;
this.httpStatus = opt?.httpStatus;
this.cause = opt?.cause;
this.explicitUrl = opt?.url;
}
url(): string | undefined {
if (this.explicitUrl) return this.explicitUrl;
if (this.value == null) return undefined;
try {
if (this.value instanceof AnonymizedFile) {
+1 -1
View File
@@ -163,7 +163,7 @@ export default class S3Storage extends StorageBase {
if (!res) {
throw new AnonymousError("file_not_found", {
httpStatus: 404,
object: join(this.repoPath(repoId), path),
url: join(this.repoPath(repoId), path),
});
}
return res as Readable;
+1 -1
View File
@@ -72,7 +72,7 @@ router.get(
.on("error", () => {
handleError(
new AnonymousError("file_not_found", {
object: req.params.repoId,
url: req.originalUrl,
httpStatus: 404,
}),
res
+26 -6
View File
@@ -122,22 +122,42 @@ function printError(error: any, req?: express.Request) {
...serializeError(error),
url: req?.originalUrl,
};
// Use the error's snake_case message as the logger summary so the admin
// Errors page surfaces something meaningful (e.g. "repoId_already_used")
// instead of a generic "anonymous error" wrapper.
const summary = error.message || error.name || "AnonymousError";
// 4xx are expected client errors (not_found, expired, not_connected) —
// route them to warn so the admin Errors page can split server faults
// (5xx) from client misuse (4xx) cleanly.
const status = error.httpStatus;
if (typeof status === "number" && status >= 400 && status < 500) {
logger.warn("anonymous error", payload);
logger.warn(summary, payload);
} else {
logger.error("anonymous error", payload);
logger.error(summary, payload);
}
} else if (error instanceof HTTPError) {
logger.error("http error", {
code: error.code,
message: error.message,
logger.error(error.code || error.name || "HTTPError", {
...serializeError(error),
url: req?.originalUrl,
});
} else {
logger.error("unhandled error", serializeError(error));
// Unhandled errors: use the error class name (SyntaxError, TypeError,
// RangeError, ...) as the summary so the admin page shows
// something far more useful than a generic "unhandled error" label.
const serialized = serializeError(error) as Record<string, unknown>;
if (
typeof serialized.status !== "number" &&
typeof serialized.httpStatus !== "number"
) {
serialized.httpStatus = 500;
}
if (req?.originalUrl) serialized.url = req.originalUrl;
const summary =
(error && typeof error === "object" &&
((error as { name?: string }).name ||
(error as { message?: string }).message)) ||
"UnhandledError";
logger.error(summary, serialized);
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ app.all("*", (req, res) => {
handleError(
new AnonymousError("file_not_found", {
httpStatus: 404,
object: req.originalUrl,
url: req.originalUrl,
}),
res,
req