diff --git a/docker-compose.yml b/docker-compose.yml index 4e301236f..db9a97c41 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,14 +23,14 @@ services: - MS_CLIENT_SECRET=YOUR_MS_CLIENT_SECRET # Replace with your Microsoft Client Secret - AVAILABLE_FIELDS_URL=YOUR_STORAGE_ULR # Replace with the url of the storage location - FDM_APP_URL=YOUR_DOMAIN # Replace with your domain - - VITE_SENTRY_ORG=YOUR_SENTRY_ORG # Replace with your Sentry organization - - VITE_SENTRY_PROJECT=YOUR_SENTRY_PROJECT # Replace with your Sentry project - - VITE_SENTRY_DSN=YOUR_SENTRY_DSN # Replace with your Sentry DSN + - PUBLIC_SENTRY_ORG=YOUR_SENTRY_ORG # Replace with your Sentry organization + - PUBLIC_SENTRY_PROJECT=YOUR_SENTRY_PROJECT # Replace with your Sentry project + - PUBLIC_SENTRY_DSN=YOUR_SENTRY_DSN # Replace with your Sentry DSN - SENTRY_AUTH_TOKEN=YOUR_SENTRY_AUTH_TOKEN # Replace with your Sentry authentication token - - VITE_SENTRY_TRACE_SAMPLE_RATE=1 - - VITE_SENTRY_REPLAY_SAMPLE_RATE=0 - - VITE_SENTRY_REPLAY_SAMPLE_RATE_ON_ERROR=1 - - VITE_SENTRY_PROFILE_SAMPLE_RATE=1 + - PUBLIC_SENTRY_TRACE_SAMPLE_RATE=1 + - PUBLIC_SENTRY_REPLAY_SAMPLE_RATE=0 + - PUBLIC_SENTRY_REPLAY_SAMPLE_RATE_ON_ERROR=1 + - PUBLIC_SENTRY_PROFILE_SAMPLE_RATE=1 depends_on: postgres: condition: service_healthy diff --git a/fdm-app/CHANGELOG.md b/fdm-app/CHANGELOG.md index a3bb8e803..f71704308 100644 --- a/fdm-app/CHANGELOG.md +++ b/fdm-app/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog fdm-app +## 0.28.4 + +### Patch Changes + +- [#522](https://github.com/nmi-agro/fdm/pull/522) [`931b2a6`](https://github.com/nmi-agro/fdm/commit/931b2a6c2067a9b8d8c1a502db32fe672ca1a0ea) Thanks [@SvenVw](https://github.com/SvenVw)! - Fix error logging so server errors are actually captured in Sentry + + Server errors were silently dropped from Sentry in several scenarios, leaving only an uninformative client-side "Unexpected Server Error" event with no stack trace or error code + - `reportError()` now always calls `Sentry.captureException()` when the SDK is initialized (guarded via `Sentry.getClient()`), removing the dependency on `clientConfig` which could silently evaluate to `null` server-side + - `errorId` is now stored in Sentry **tags** (`error_id`) in addition to `extra`, making it searchable — users can report their error code and you can find the exact event with `error_id:XXXX-XXXX` + - `console.error` is now always called in `reportError()`, regardless of whether Sentry is configured + - `"Unexpected Server Error"` is added to `ignoreErrors` on the client — this React Router shadow event is always a duplicate of the real server-side error + - `handleError` in `entry.server.tsx` now uses `reportError()` instead of raw `Sentry.captureException()`, so unhandled errors also get a trackable `errorId` + - Streaming `onError` callbacks now call `reportError()` instead of `console.error()` only + - `VITE_SENTRY_DSN`, `VITE_SENTRY_TRACE_SAMPLE_RATE`, and `VITE_SENTRY_PROFILE_SAMPLE_RATE` renamed to `PUBLIC_SENTRY_*` for consistency with the rest of the app + - Sentry server-side initialization is now conditional on `PUBLIC_SENTRY_DSN` being set; the app starts normally without Sentry configured + ## 0.28.3 ### Patch Changes diff --git a/fdm-app/app/entry.client.tsx b/fdm-app/app/entry.client.tsx index 90fc32f53..ac01b73ef 100644 --- a/fdm-app/app/entry.client.tsx +++ b/fdm-app/app/entry.client.tsx @@ -18,7 +18,10 @@ if (clientConfig.analytics.sentry) { dsn: sentryConfig.dsn, release: import.meta.env.PUBLIC_APP_VERSION, environment: import.meta.env.NODE_ENV, - ignoreErrors: [/BodyStreamBuffer was aborted/], + ignoreErrors: [ + /BodyStreamBuffer was aborted/, + /Unexpected Server Error/, + ], integrations: [ Sentry.reactRouterTracingIntegration(), Sentry.replayIntegration(), diff --git a/fdm-app/app/entry.server.tsx b/fdm-app/app/entry.server.tsx index c403f1512..cc6f4df01 100644 --- a/fdm-app/app/entry.server.tsx +++ b/fdm-app/app/entry.server.tsx @@ -5,7 +5,6 @@ import { createReadableStreamFromReadable } from "@react-router/node" * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ * For more information, see https://remix.run/file-conventions/entry.server */ -import * as Sentry from "@sentry/react-router" import { getMetaTagTransformer, wrapSentryHandleRequest, @@ -18,6 +17,7 @@ import type { HandleErrorFunction, } from "react-router" import { ServerRouter } from "react-router" +import { reportError } from "~/lib/error" import { addSecurityHeaders, getCacheControlHeaders } from "./lib/cache.server" export const streamTimeout = 90000 @@ -115,7 +115,7 @@ function handleBotRequest( // errors encountered during initial shell rendering since they'll // reject and get logged in handleDocumentRequest. if (shellRendered) { - console.error(error) + reportError(error, { scope: "streaming-bot" }) } }, }, @@ -164,7 +164,7 @@ function handleBrowserRequest( // errors encountered during initial shell rendering since they'll // reject and get logged in handleDocumentRequest. if (shellRendered) { - console.error(error) + reportError(error, { scope: "streaming" }) } }, }, @@ -180,7 +180,6 @@ export default wrapSentryHandleRequest(handleRequest) export const handleError: HandleErrorFunction = (error, { request }) => { // React Router may abort some interrupted requests, report those if (!request.signal.aborted) { - Sentry.captureException(error) - console.error(error) + reportError(error, { scope: "unhandled" }) } } diff --git a/fdm-app/app/lib/error.ts b/fdm-app/app/lib/error.ts index 49b45d897..80663f0b5 100644 --- a/fdm-app/app/lib/error.ts +++ b/fdm-app/app/lib/error.ts @@ -2,7 +2,6 @@ import * as Sentry from "@sentry/react-router" import { customAlphabet } from "nanoid" import { data, redirect } from "react-router" import { dataWithError, dataWithWarning } from "remix-toast" -import { clientConfig } from "~/lib/config" const customErrorAlphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ" // No lookalikes (0, 1, I, O, S, Z) const errorIdSize = 8 // Number of characters in ID @@ -19,18 +18,19 @@ export function reportError( .match(/.{1,4}/g) ?.join("-") || createErrorId() // Format as XXXX-XXXX - if (clientConfig.analytics.sentry?.dsn) { + console.error(`Error (code: ${errorId}):`, error, context ?? "") + + if (Sentry.getClient()) { Sentry.captureException(error, { tags: { ...tags, + error_id: errorId, }, extra: { ...context, errorId: errorId, }, }) - } else { - console.error(`Error (code: ${errorId}):`, error, context) } return errorId @@ -67,10 +67,11 @@ export function handleLoaderError(error: unknown) { userMessage = "De gevraagde data kon niet worden gevonden." break // case 500: - default: - userMessage = - "Er is een onverwachte fout opgetreden. Probeer het later opnieuw of neem contact op met Ondersteuning." + default: { + const errorId = reportError(error, { scope: "loader" }) + userMessage = `Er is een onverwachte fout opgetreden. Probeer het later opnieuw of neem contact op met Ondersteuning en meldt de volgende foutcode: ${errorId}.` break + } } return data( { @@ -131,9 +132,7 @@ export function handleLoaderError(error: unknown) { ) } - // All other errors - console.error("Loader Error: ", error) - // Forward error to Sentry + // All other errors — reportError handles logging and Sentry capture const errorId = reportError(error, { scope: "loader", }) @@ -221,11 +220,12 @@ export function handleActionError(error: unknown) { dataStatus = "warning" break // case 500: - default: - userMessage = - "Er is een onverwachte fout opgetreden. Probeer het later opnieuw of neem contact op met Ondersteuning." + default: { + const errorId = reportError(error, { scope: "action" }) + userMessage = `Er is een onverwachte fout opgetreden. Probeer het later opnieuw of neem contact op met Ondersteuning en meldt de volgende foutcode: ${errorId}.` dataStatus = "error" break + } } if (dataStatus === "warning") { return dataWithWarning( @@ -275,8 +275,7 @@ export function handleActionError(error: unknown) { ) } - // All other errors - console.error("Error: ", error) + // All other errors — reportError handles logging and Sentry capture const errorId = reportError(error, { scope: "action", }) diff --git a/fdm-app/instrument.server.mjs b/fdm-app/instrument.server.mjs index 715d468bf..deb00ac6c 100644 --- a/fdm-app/instrument.server.mjs +++ b/fdm-app/instrument.server.mjs @@ -1,24 +1,18 @@ import { nodeProfilingIntegration } from "@sentry/profiling-node" import * as Sentry from "@sentry/react-router" -const requiredEnvVars = [ - "VITE_SENTRY_DSN", - "VITE_SENTRY_TRACE_SAMPLE_RATE", - "VITE_SENTRY_PROFILE_SAMPLE_RATE", -] - -for (const envVar of requiredEnvVars) { - if (!process.env[envVar]) { - throw new Error(`Missing required environment variable: ${envVar}`) - } +if (process.env.PUBLIC_SENTRY_DSN) { + Sentry.init({ + dsn: process.env.PUBLIC_SENTRY_DSN, + integrations: [nodeProfilingIntegration()], + tracesSampleRate: Number( + process.env.PUBLIC_SENTRY_TRACE_SAMPLE_RATE ?? 1, + ), + profilesSampleRate: Number( + process.env.PUBLIC_SENTRY_PROFILE_SAMPLE_RATE ?? 1, + ), + ignoreErrors: [/BodyStreamBuffer was aborted/], + environment: process.env.NODE_ENV ?? "development", + release: process.env.npm_package_version, + }) } - -Sentry.init({ - dsn: String(process.env.VITE_SENTRY_DSN), - integrations: [nodeProfilingIntegration()], - tracesSampleRate: Number(process.env.VITE_SENTRY_TRACE_SAMPLE_RATE), - profilesSampleRate: Number(process.env.VITE_SENTRY_PROFILE_SAMPLE_RATE), - ignoreErrors: [/BodyStreamBuffer was aborted/], - environment: process.env.NODE_ENV ?? "development", - release: process.env.npm_package_version, -}) diff --git a/fdm-app/package.json b/fdm-app/package.json index db6d41c29..1c4217db5 100644 --- a/fdm-app/package.json +++ b/fdm-app/package.json @@ -1,6 +1,6 @@ { "name": "@nmi-agro/fdm-app", - "version": "0.28.3", + "version": "0.28.4", "private": true, "sideEffects": false, "type": "module",