diff --git a/.changeset/bright-falcon-dance.md b/.changeset/bright-falcon-dance.md new file mode 100644 index 000000000..b1515e28f --- /dev/null +++ b/.changeset/bright-falcon-dance.md @@ -0,0 +1,5 @@ +--- +"@exactly/server": patch +--- + +🥅 fingerprint service errors by name and message diff --git a/server/index.ts b/server/index.ts index 4dc9c5aec..5311fe916 100644 --- a/server/index.ts +++ b/server/index.ts @@ -19,6 +19,7 @@ import persona from "./hooks/persona"; import androidFingerprints from "./utils/android/fingerprints"; import appOrigin from "./utils/appOrigin"; import { closeAndFlush as closeSegment } from "./utils/segment"; +import ServiceError from "./utils/ServiceError"; import type { UnofficialStatusCode } from "hono/utils/http-status"; @@ -273,36 +274,11 @@ frontend.use( app.route("/", frontend); app.onError((error, c) => { - let fingerprint: string[] | undefined; - if (error instanceof Error) { - const message = error.message - .split("Error:") - .reduce((result, part) => (result ? `${result}Error:${part}` : part.trimStart()), ""); - const status = message.slice(0, 3); - const hasStatus = /^\d{3}$/.test(status); - const hasBodyFormat = message.length === 3 || message[3] === " "; - const body = hasBodyFormat && message.length > 3 ? message.slice(4).trim() : undefined; - if (hasStatus && hasBodyFormat) fingerprint = ["{{ default }}", status]; - if (hasStatus && hasBodyFormat && body) { - try { - const json = JSON.parse(body) as { code?: unknown; error?: unknown; message?: unknown }; - fingerprint = [ - "{{ default }}", - status, - ...("code" in json - ? [String(json.code)] - : typeof json.message === "string" - ? [json.message] - : typeof json.error === "string" - ? [json.error] - : [body]), - ]; - } catch { - fingerprint = ["{{ default }}", status, body]; - } - } - } - captureException(error, { level: "error", tags: { unhandled: true }, fingerprint }); + captureException(error, { + level: "error", + tags: { unhandled: true }, + fingerprint: error instanceof ServiceError ? ["{{ default }}", error.name, error.message] : undefined, + }); return c.json({ code: "unexpected error", legacy: "unexpected error" }, 555 as UnofficialStatusCode); }); diff --git a/server/instrument.cjs b/server/instrument.cjs index 544128c38..73a3c2691 100644 --- a/server/instrument.cjs +++ b/server/instrument.cjs @@ -28,8 +28,16 @@ init({ ], beforeSend: (event, hint) => { const exception = event.exception?.values?.[0]; + if (!exception) return event; + const error = hint.originalException; + if ( + error instanceof Error && + typeof (/** @type {{ status?: unknown }} */ (error).status) === "number" && + !(event.fingerprint && event.fingerprint.length > 1) + ) { + event.fingerprint = ["{{ default }}", error.name, error.message]; + } if ( - exception && event.fingerprint?.[0] === "{{ default }}" && (exception.type === "ContractFunctionExecutionError" || exception.type === "ContractFunctionRevertedError" || @@ -37,11 +45,11 @@ init({ ) { /** @typedef {{ cause?: unknown; data?: { errorName?: string }; reason?: string; signature?: string }} RevertError */ for ( - let error = /** @type {RevertError | undefined} */ (hint.originalException); - error; - error = /** @type {RevertError | undefined} */ (error.cause) + let revert = /** @type {RevertError | undefined} */ (hint.originalException); + revert; + revert = /** @type {RevertError | undefined} */ (revert.cause) ) { - const reason = error.data?.errorName ?? error.reason ?? error.signature; + const reason = revert.data?.errorName ?? revert.reason ?? revert.signature; if (reason) { exception.type = reason; break;