Feature/sentry error logging #5

Merged
Sluijsens merged 11 commits from feature/sentry_error_logging into master 2026-07-25 20:04:18 +02:00
8 changed files with 107 additions and 0 deletions
Showing only changes of commit c90300b9a4 - Show all commits
@@ -99,6 +99,11 @@ jobs:
# als deze niet is ingesteld, wordt Sentry-logging simpelweg overgeslagen # als deze niet is ingesteld, wordt Sentry-logging simpelweg overgeslagen
# (zie src/main.tsx) en blijft alleen console-logging actief. # (zie src/main.tsx) en blijft alleen console-logging actief.
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }} VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
# Build-time tag die bepaalt of dev/test-only UI (zoals de tijdelijke
# SentryTestButton) zichtbaar is; zie src/components/SentryTestButton.tsx.
# Zolang er nog geen aparte productie-build/deploy bestaat, is dit altijd
# gelijk aan DEPLOY_ENVIRONMENT ('test').
VITE_APP_ENV: ${{ env.DEPLOY_ENVIRONMENT }}
- name: Upload build artifact - name: Upload build artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
@@ -480,3 +480,15 @@
**Context**: Post-workflow follow-up (feature already marked Complete); continues the `feature/sentry_error_logging` branch with a gap-check against Sentry's own official setup guide. Build, lint and tests re-verified successfully after the change. **Context**: Post-workflow follow-up (feature already marked Complete); continues the `feature/sentry_error_logging` branch with a gap-check against Sentry's own official setup guide. Build, lint and tests re-verified successfully after the change.
--- ---
## Post-Completion Follow-up: Temporary Sentry Test Button Added (Dev/Test Only)
**Timestamp**: 2026-07-25T13:41:00Z
**User Input**: "Ik heb hier een code snippet met een intentionele fout om sentry te kunnen testen. Kan je die ergens toevoegen zodat we tijdelijk een test hebben of misschien iets slims bedenken zodat het alleen in development- en testomgevingen zichtbaar is" (a `Sentry.logger.info` + `Sentry.metrics.count` + throw snippet for a "Break the world" button).
**AI Response**: "Added `src/components/SentryTestButton.tsx`, mounted globally via `RootLayout.tsx`. Extracted the click logic into an exported `handleSentryTestErrorClick` function for reliable unit testing (React error boundaries don't catch event-handler errors, so the throw is intentionally left uncaught for Sentry's global handler to pick up). Added `enableLogs: true` to `Sentry.init()` in `main.tsx`, required for `Sentry.logger.*` calls. Introduced a new build-time `VITE_APP_ENV` variable (typed in `vite-env.d.ts`, set to `DEPLOY_ENVIRONMENT` i.e. `test` in the `Build` step of `continuous_integration.yaml`) so the button is only visible during local development (`import.meta.env.DEV`) or when explicitly built for the test environment — hidden by default, including in any future production build. Added `SentryTestButton.test.tsx` (2 tests) and updated `monitoring-setup.md` with a new subsection documenting this temporary tool and noting it should be removed once Sentry has been confirmed to receive test errors/logs/metrics end-to-end."
**Context**: Post-workflow follow-up (feature already marked Complete); continues the `feature/sentry_error_logging` branch. `pnpm run build`, `pnpm run lint` (0 errors, pre-existing-style warnings only) and `pnpm run test` (16/16 tests) all succeeded. Changes not yet committed/pushed.
---
@@ -27,6 +27,13 @@ Both the console and Sentry are now active (original Question 3 resolved as a co
**Log level strategy**: only errors are logged (no verbose/info-level client logging) — this is a static site with no meaningful "business events" beyond page views, which are covered by analytics (see Dashboards below), not logging. **Log level strategy**: only errors are logged (no verbose/info-level client logging) — this is a static site with no meaningful "business events" beyond page views, which are covered by analytics (see Dashboards below), not logging.
### Temporary manual test tool: `SentryTestButton`
- `src/components/SentryTestButton.tsx` renders a "Break the world" button, mounted globally via `RootLayout.tsx`, used to manually verify that errors, logs (`Sentry.logger.info`), and metrics (`Sentry.metrics.count`) actually arrive in Sentry end-to-end.
- **Visibility**: only shown during local development (`pnpm dev`, via Vite's `import.meta.env.DEV`) and in the test environment (via the new build-time `VITE_APP_ENV` variable, set to `test` by `continuous_integration.yaml`'s `Build` step). It is hidden by default (including in any future production build) unless one of those conditions is explicitly true.
- `Sentry.logger.*` requires `enableLogs: true` in `Sentry.init()` (`src/main.tsx`) — added specifically to support this test button (and any future structured logging).
- The thrown error is **intentionally uncaught**: React error boundaries do not catch errors thrown from event handlers (only render/lifecycle errors), so this relies on Sentry's own global `window.onerror` handler, exactly like the official Sentry test snippet.
- This is a temporary verification tool, not a permanent feature — remove `SentryTestButton` (and its usage in `RootLayout.tsx`) once Sentry has been confirmed to receive test errors/logs/metrics end-to-end.
## Dashboards ## Dashboards
### Website analytics ### Website analytics
+2
View File
@@ -1,6 +1,7 @@
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { Nav } from './Nav'; import { Nav } from './Nav';
import { Footer } from './Footer'; import { Footer } from './Footer';
import { SentryTestButton } from './SentryTestButton';
export function RootLayout({ children }: { children: ReactNode }) { export function RootLayout({ children }: { children: ReactNode }) {
return ( return (
@@ -8,6 +9,7 @@ export function RootLayout({ children }: { children: ReactNode }) {
<Nav /> <Nav />
{children} {children}
<Footer /> <Footer />
<SentryTestButton />
</div> </div>
); );
} }
+47
View File
@@ -0,0 +1,47 @@
import * as Sentry from '@sentry/react';
/**
* Alleen zichtbaar tijdens lokale development (`pnpm dev`) en in de testomgeving
* (waar de build met `VITE_APP_ENV=test` wordt gebouwd, zie continuous_integration.yaml).
* In een productiebuild (VITE_APP_ENV ontbreekt of is 'production') is deze knop
* altijd verborgen, ook als iemand vergeet de variabele expliciet te zetten.
*/
const isVisible = import.meta.env.DEV || import.meta.env.VITE_APP_ENV === 'test';
/**
* Losstaand van het component zodat dit direct (zonder DOM/React event-dispatch)
* getest kan worden. React vangt fouten uit event handlers namelijk NIET op via
* een ErrorBoundary (die vangt alleen render-/lifecycle-fouten). De fout hieronder
* blijft dus bewust een onafgevangen (uncaught) fout, die door Sentry's eigen
* globale `window.onerror`-handler wordt opgepikt precies zoals bedoeld voor
* deze test-knop.
*/
export function handleSentryTestErrorClick(): void {
// Send a log before throwing the error
Sentry.logger.info('User triggered test error', {
action: 'test_error_button_click',
});
// Send a test metric before throwing the error
Sentry.metrics.count('test_counter', 1);
throw new Error('This is your first error!');
}
/**
* Tijdelijke testknop om te verifiëren dat Sentry errors, logs en metrics
* daadwerkelijk binnenkomen.
*/
export function SentryTestButton() {
if (!isVisible) {
return null;
}
return (
<button
type="button"
onClick={handleSentryTestErrorClick}
className="fixed bottom-4 right-4 z-50 rounded bg-red-600 px-4 py-2 text-sm font-semibold text-white shadow-lg hover:bg-red-700"
>
Break the world
</button>
);
}
@@ -0,0 +1,30 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import * as Sentry from '@sentry/react';
import { SentryTestButton, handleSentryTestErrorClick } from '../SentryTestButton';
vi.mock('@sentry/react', () => ({
captureException: vi.fn(),
logger: { info: vi.fn() },
metrics: { count: vi.fn() },
}));
describe('SentryTestButton', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('is visible in the current (development/test) build', () => {
render(<SentryTestButton />);
expect(screen.getByRole('button', { name: 'Break the world' })).toBeInTheDocument();
});
it('sends a log and a metric to Sentry, then throws an intentional test error', () => {
expect(() => handleSentryTestErrorClick()).toThrow('This is your first error!');
expect(Sentry.logger.info).toHaveBeenCalledWith('User triggered test error', {
action: 'test_error_button_click',
});
expect(Sentry.metrics.count).toHaveBeenCalledWith('test_counter', 1);
});
});
+2
View File
@@ -15,6 +15,8 @@ if (sentryDsn) {
integrations: [Sentry.tanstackRouterBrowserTracingIntegration(router)], integrations: [Sentry.tanstackRouterBrowserTracingIntegration(router)],
// Low-traffic marketing site: capture all traces (lower this if traffic grows significantly). // Low-traffic marketing site: capture all traces (lower this if traffic grows significantly).
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
// Required for Sentry.logger.* calls (e.g. the temporary SentryTestButton) to actually be sent.
enableLogs: true,
}); });
} }
+2
View File
@@ -3,6 +3,8 @@
interface ImportMetaEnv { interface ImportMetaEnv {
/** Sentry DSN (Data Source Name) for client-side error reporting. Not a secret — safe to expose in the client bundle. */ /** Sentry DSN (Data Source Name) for client-side error reporting. Not a secret — safe to expose in the client bundle. */
readonly VITE_SENTRY_DSN?: string; readonly VITE_SENTRY_DSN?: string;
/** Build-time deployment environment tag ('test' | 'production' | undefined). Used to hide dev/test-only UI (e.g. SentryTestButton) from production builds. */
readonly VITE_APP_ENV?: string;
} }
interface ImportMeta { interface ImportMeta {