mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-27 05:36:28 -05:00
Add Sentry
This commit is contained in:
@@ -43,3 +43,10 @@ VITE_SHOW_LUTI_NAV_ITEM=false
|
||||
VITE_VAPID_PUBLIC_KEY=
|
||||
VAPID_PRIVATE_KEY=
|
||||
VAPID_EMAIL=
|
||||
|
||||
// Sentry build-time config (used for sourcemap upload)
|
||||
SENTRY_ORG=
|
||||
SENTRY_PROJECT=
|
||||
SENTRY_AUTH_TOKEN=
|
||||
|
||||
VITE_SENTRY_DSN=
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as Sentry from "@sentry/react-router";
|
||||
import "@formatjs/intl-durationformat/polyfill.js";
|
||||
import i18next from "i18next";
|
||||
import { hydrateRoot } from "react-dom/client";
|
||||
@@ -7,8 +8,35 @@ import { i18nLoader } from "./modules/i18n/loader";
|
||||
import { logger } from "./utils/logger";
|
||||
import { getSessionId } from "./utils/session-id";
|
||||
|
||||
const tracing = Sentry.reactRouterTracingIntegration({
|
||||
useInstrumentationAPI: true,
|
||||
});
|
||||
|
||||
Sentry.init({
|
||||
dsn: import.meta.env.VITE_SENTRY_DSN,
|
||||
sendDefaultPii: false,
|
||||
integrations: [tracing],
|
||||
enableLogs: true,
|
||||
tracesSampleRate: 0.1,
|
||||
tracePropagationTargets: [/^\//],
|
||||
});
|
||||
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = (input, init) => {
|
||||
const url =
|
||||
typeof input === "string"
|
||||
? input
|
||||
: input instanceof URL
|
||||
? input.href
|
||||
: input.url;
|
||||
const isSameOrigin =
|
||||
url.startsWith("/") ||
|
||||
new URL(url, window.location.origin).origin === window.location.origin;
|
||||
|
||||
if (!isSameOrigin) {
|
||||
return originalFetch(input, init);
|
||||
}
|
||||
|
||||
const sessionId = getSessionId();
|
||||
const headers = new Headers(init?.headers);
|
||||
headers.set("Sendou-Session-Id", sessionId);
|
||||
@@ -27,7 +55,14 @@ i18nLoader()
|
||||
hydrateRoot(
|
||||
document,
|
||||
<I18nextProvider i18n={i18next}>
|
||||
<HydratedRouter />
|
||||
<HydratedRouter
|
||||
unstable_instrumentations={[tracing.clientInstrumentation]}
|
||||
onError={(error) => {
|
||||
if (error && error instanceof Error) {
|
||||
Sentry.captureException(error);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</I18nextProvider>,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as Sentry from "@sentry/react-router";
|
||||
import "@formatjs/intl-durationformat/polyfill.js";
|
||||
import { PassThrough } from "node:stream";
|
||||
import { createReadableStreamFromReadable } from "@react-router/node";
|
||||
@@ -6,7 +7,11 @@ import { isbot } from "isbot";
|
||||
import cron from "node-cron";
|
||||
import { renderToPipeableStream } from "react-dom/server";
|
||||
import { I18nextProvider, initReactI18next } from "react-i18next";
|
||||
import { type EntryContext, ServerRouter } from "react-router";
|
||||
import {
|
||||
type EntryContext,
|
||||
type HandleErrorFunction,
|
||||
ServerRouter,
|
||||
} from "react-router";
|
||||
import { config } from "~/modules/i18n/config"; // your i18n configuration file
|
||||
import { i18next } from "~/modules/i18n/i18next.server";
|
||||
import { resources } from "./modules/i18n/resources.server";
|
||||
@@ -21,7 +26,7 @@ import { logger } from "./utils/logger";
|
||||
// Reject/cancel all pending promises after 5 seconds
|
||||
export const streamTimeout = 5000;
|
||||
|
||||
export default async function handleRequest(
|
||||
async function handleRequest(
|
||||
request: Request,
|
||||
responseStatusCode: number,
|
||||
responseHeaders: Headers,
|
||||
@@ -64,7 +69,7 @@ export default async function handleRequest(
|
||||
}),
|
||||
);
|
||||
|
||||
pipe(body);
|
||||
pipe(Sentry.getMetaTagTransformer(body));
|
||||
},
|
||||
onShellError(error: unknown) {
|
||||
reject(error);
|
||||
@@ -122,6 +127,13 @@ process.on("unhandledRejection", (reason: string, p: Promise<any>) => {
|
||||
});
|
||||
|
||||
// wrapper so we get request id shown in the server logs
|
||||
export function handleError(error: unknown) {
|
||||
export const handleError: HandleErrorFunction = (error, { request }) => {
|
||||
if (!request.signal.aborted) {
|
||||
Sentry.captureException(error);
|
||||
}
|
||||
logger.error(error);
|
||||
}
|
||||
};
|
||||
export default Sentry.wrapSentryHandleRequest(handleRequest);
|
||||
export const unstable_instrumentations = [
|
||||
Sentry.createSentryServerInstrumentation(),
|
||||
];
|
||||
|
||||
23
instrument.server.mjs
Normal file
23
instrument.server.mjs
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as Sentry from "@sentry/react-router";
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.VITE_SENTRY_DSN,
|
||||
sendDefaultPii: false,
|
||||
enableLogs: true,
|
||||
tracesSampleRate: 0.1, // Capture 10% of the transactions
|
||||
|
||||
// Set up performance monitoring
|
||||
beforeSend(event) {
|
||||
// Filter out 404s from error reporting
|
||||
if (event.exception) {
|
||||
const error = event.exception.values?.[0];
|
||||
if (
|
||||
error?.type === "NotFoundException" ||
|
||||
error?.value?.includes("404")
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return event;
|
||||
},
|
||||
});
|
||||
2
knip.ts
2
knip.ts
@@ -12,8 +12,6 @@ const config = {
|
||||
"public/sw-2.js",
|
||||
"ley.config.cjs",
|
||||
"ley-driver.cjs",
|
||||
"vitest.browser.config.ts",
|
||||
"app/browser-test-setup.ts",
|
||||
],
|
||||
compilers: {
|
||||
css: (text: string, path: string) => {
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
"deploy": "pnpm install --frozen-lockfile && pnpm run build",
|
||||
"build": "react-router build",
|
||||
"dev": "cross-env DB_PATH=db.sqlite3 pnpm run migrate up && pnpm run setup && react-router dev --host",
|
||||
"dev:sentry": "NODE_OPTIONS='--import ./instrument.server.mjs' pnpm dev",
|
||||
"dev:prod": "cross-env DB_PATH=db-prod.sqlite3 VITE_PROD_MODE=true react-router dev --host",
|
||||
"start": "pnpm run migrate up && react-router-serve build/server/index.js",
|
||||
"start": "NODE_ENV=production NODE_OPTIONS='--import ./instrument.server.mjs' react-router-serve ./build/server/index.js",
|
||||
"migrate": "ley",
|
||||
"migrate:prod": "cross-env DB_PATH=db-prod.sqlite3 pnpm run migrate up",
|
||||
"check-translation-jsons": "node --experimental-strip-types scripts/check-translation-jsons.ts",
|
||||
@@ -51,6 +52,7 @@
|
||||
"@react-router/node": "7.14.2",
|
||||
"@react-router/serve": "7.14.2",
|
||||
"@remix-run/form-data-parser": "0.16.0",
|
||||
"@sentry/react-router": "^10.52.0",
|
||||
"@tldraw/tldraw": "3.12.1",
|
||||
"@zumer/snapdom": "2.9.0",
|
||||
"aws-sdk": "2.1693.0",
|
||||
|
||||
1067
pnpm-lock.yaml
generated
1067
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
import type { Config } from "@react-router/dev/config";
|
||||
import { sentryOnBuildEnd } from "@sentry/react-router";
|
||||
|
||||
export default {
|
||||
// Upfront cost vs. lazy loading trade-off
|
||||
@@ -8,4 +9,11 @@ export default {
|
||||
future: {
|
||||
v8_middleware: true,
|
||||
},
|
||||
buildEnd: async ({ viteConfig, reactRouterConfig, buildManifest }) => {
|
||||
await sentryOnBuildEnd({
|
||||
viteConfig: viteConfig,
|
||||
reactRouterConfig: reactRouterConfig,
|
||||
buildManifest: buildManifest,
|
||||
});
|
||||
},
|
||||
} satisfies Config;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { reactRouter } from "@react-router/dev/vite";
|
||||
import { sentryReactRouter } from "@sentry/react-router";
|
||||
import { defineConfig, loadEnv } from "vite";
|
||||
import babel from "vite-plugin-babel";
|
||||
import { configDefaults } from "vitest/config";
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), "");
|
||||
export default defineConfig((config) => {
|
||||
const env = loadEnv(config.mode, process.cwd(), "");
|
||||
return {
|
||||
server: {
|
||||
port: Number(env.PORT) || 5173,
|
||||
@@ -40,26 +40,19 @@ export default defineConfig(({ mode }) => {
|
||||
plugins: [["babel-plugin-react-compiler", {}]],
|
||||
},
|
||||
}),
|
||||
sentryReactRouter(
|
||||
{
|
||||
org: process.env.SENTRY_ORG,
|
||||
project: process.env.SENTRY_PROJECT,
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||
telemetry: false,
|
||||
},
|
||||
config,
|
||||
),
|
||||
],
|
||||
|
||||
test: {
|
||||
projects: [
|
||||
{
|
||||
extends: true,
|
||||
test: {
|
||||
name: "unit",
|
||||
include: ["**/*.test.{ts,tsx}"],
|
||||
exclude: [
|
||||
...configDefaults.exclude,
|
||||
"e2e/**",
|
||||
"**/*.browser.test.{ts,tsx}",
|
||||
],
|
||||
setupFiles: ["./app/test-setup.ts"],
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: "./vitest.browser.config.ts",
|
||||
},
|
||||
],
|
||||
projects: ["./vitest.unit.config.ts", "./vitest.browser.config.ts"],
|
||||
},
|
||||
define: {
|
||||
__GIT_COMMIT__: JSON.stringify(process.env.RENDER_GIT_COMMIT ?? ""),
|
||||
@@ -75,5 +68,8 @@ export default defineConfig(({ mode }) => {
|
||||
resolve: {
|
||||
tsconfigPaths: true,
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: ["@sentry/react-router"],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
17
vitest.unit.config.ts
Normal file
17
vitest.unit.config.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { configDefaults, defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
name: "unit",
|
||||
include: ["**/*.test.{ts,tsx}"],
|
||||
exclude: [
|
||||
...configDefaults.exclude,
|
||||
"e2e/**",
|
||||
"**/*.browser.test.{ts,tsx}",
|
||||
],
|
||||
setupFiles: ["./app/test-setup.ts"],
|
||||
},
|
||||
resolve: {
|
||||
tsconfigPaths: true,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user