feat(frontend): show error ID for internal server errors
This commit is contained in:
parent
83b62b4845
commit
a0ba712632
8 changed files with 1554 additions and 25 deletions
|
@ -1,7 +1,18 @@
|
|||
# Example .env file--DO NOT EDIT, copy to .env or .env.local then edit
|
||||
|
||||
# The language the frontend will use. Valid languages are listed in src/lib/i18n/index.ts.
|
||||
PUBLIC_LANGUAGE=en
|
||||
# The public base URL, i.e. the one users will see. Used for building links.
|
||||
PUBLIC_BASE_URL=https://pronouns.cc
|
||||
# The base URL for the URL shortener service. Used for building short links.
|
||||
PUBLIC_SHORT_URL=https://prns.cc
|
||||
# The base public URL for the API. This is (almost) always the public base URL + /api.
|
||||
PUBLIC_API_BASE=https://pronouns.cc/api
|
||||
# The base *private* URL for the API's rate limiter proxy. The frontend will rewrite API URLs to use this.
|
||||
# In development, you can set this to the same value as $PRIVATE_INTERNAL_API_HOST, but be aware that this will disable rate limiting.
|
||||
PRIVATE_API_HOST=http://localhost:5003/api
|
||||
# The base private URL for the API, which bypasses the rate limiter. Used for /api/internal paths and unauthenticated GET requests.
|
||||
PRIVATE_INTERNAL_API_HOST=http://localhost:6000/api
|
||||
|
||||
# The Sentry URL to use. Optional.
|
||||
PRIVATE_SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
"packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c",
|
||||
"dependencies": {
|
||||
"@fontsource/firago": "^5.1.0",
|
||||
"@sentry/sveltekit": "^8.52.0",
|
||||
"base64-arraybuffer": "^1.0.2",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"luxon": "^3.5.0",
|
||||
|
|
File diff suppressed because it is too large
Load diff
3
Foxnouns.Frontend/src/app.d.ts
vendored
3
Foxnouns.Frontend/src/app.d.ts
vendored
|
@ -9,7 +9,8 @@ declare global {
|
|||
message: string;
|
||||
status: number;
|
||||
code: ErrorCode;
|
||||
id: string;
|
||||
errors?: Array<{ key: string; errors: ValidationError[] }>;
|
||||
error_id?: string;
|
||||
}
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import ApiError, { ErrorCode } from "$api/error";
|
||||
import { PRIVATE_API_HOST, PRIVATE_INTERNAL_API_HOST } from "$env/static/private";
|
||||
import { env } from "$env/dynamic/private";
|
||||
import { PUBLIC_API_BASE } from "$env/static/public";
|
||||
import log from "$lib/log";
|
||||
import type { HandleFetch, HandleServerError } from "@sveltejs/kit";
|
||||
import * as Sentry from "@sentry/sveltekit";
|
||||
|
||||
export const handleFetch: HandleFetch = async ({ request, fetch }) => {
|
||||
if (request.url.startsWith(`${PUBLIC_API_BASE}/internal`)) {
|
||||
|
@ -14,12 +16,17 @@ export const handleFetch: HandleFetch = async ({ request, fetch }) => {
|
|||
return await fetch(request);
|
||||
};
|
||||
|
||||
Sentry.init({
|
||||
dsn: env.PRIVATE_SENTRY_DSN,
|
||||
});
|
||||
|
||||
export const handleError: HandleServerError = async ({ error, status, message }) => {
|
||||
const id = crypto.randomUUID();
|
||||
// as far as i know, sentry IDs are just UUIDs with the dashes removed. use those here as well
|
||||
let id = crypto.randomUUID().replaceAll("-", "");
|
||||
|
||||
if (error instanceof ApiError) {
|
||||
return {
|
||||
id,
|
||||
error_id: id,
|
||||
status: error.raw?.status || status,
|
||||
message: error.raw?.message || "Unknown error",
|
||||
code: error.code,
|
||||
|
@ -27,10 +34,18 @@ export const handleError: HandleServerError = async ({ error, status, message })
|
|||
}
|
||||
|
||||
if (status >= 400 && status <= 499) {
|
||||
return { id, status, message, code: ErrorCode.GenericApiError };
|
||||
return { error_id: id, status, message, code: ErrorCode.GenericApiError };
|
||||
}
|
||||
|
||||
// client errors and backend API errors just clog up sentry, so we don't send those.
|
||||
id = Sentry.captureException(error, {
|
||||
mechanism: {
|
||||
type: "sveltekit",
|
||||
handled: false,
|
||||
},
|
||||
});
|
||||
|
||||
log.error("[%s] error in handler:", id, error);
|
||||
|
||||
return { id, status, message, code: ErrorCode.InternalServerError };
|
||||
return { error_id: id, status, message, code: ErrorCode.InternalServerError };
|
||||
};
|
||||
|
|
|
@ -14,6 +14,7 @@ export default class ApiError {
|
|||
|
||||
toObject(): RawApiError {
|
||||
return {
|
||||
error_id: this.raw?.error_id,
|
||||
status: this.raw?.status || 500,
|
||||
code: this.code,
|
||||
message: this.raw?.message || "Internal server error",
|
||||
|
@ -23,6 +24,7 @@ export default class ApiError {
|
|||
}
|
||||
|
||||
export type RawApiError = {
|
||||
error_id?: string;
|
||||
status: number;
|
||||
message: string;
|
||||
code: ErrorCode;
|
||||
|
|
|
@ -18,6 +18,14 @@
|
|||
</svelte:element>
|
||||
{/if}
|
||||
<p>{errorDescription($t, error.code)}</p>
|
||||
{#if error.error_id}
|
||||
<p>
|
||||
{$t("error.error-id")}
|
||||
<code>
|
||||
{error.error_id}
|
||||
</code>
|
||||
</p>
|
||||
{/if}
|
||||
{#if error.errors}
|
||||
<details>
|
||||
<summary>{$t("error.extra-info-header")}</summary>
|
||||
|
|
|
@ -110,7 +110,8 @@
|
|||
"back-to-prev-page-button": "Go back to the previous page",
|
||||
"400-description": "Something went wrong with your request. This error should never land you on this page, so it's probably a bug.",
|
||||
"500-description": "Something went wrong on the server. Please try again later.",
|
||||
"unknown-status-description": "Something went wrong, but we're not sure what. Please try again."
|
||||
"unknown-status-description": "Something went wrong, but we're not sure what. Please try again.",
|
||||
"error-id": "If you report this error to the developers, please give them this ID:"
|
||||
},
|
||||
"settings": {
|
||||
"general-information-tab": "General information",
|
||||
|
|
Loading…
Reference in a new issue