Files
flyer-crawler.projectium.com/src/services/sentry.server.ts
Torben Sorensen 11aeac5edd
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 1m10s
whoa - so much - new features (UPC,etc) - Sentry for app logging! so much more !
2026-01-11 19:07:02 -08:00

162 lines
4.7 KiB
TypeScript

// src/services/sentry.server.ts
/**
* Sentry SDK initialization for error tracking.
* Implements ADR-015: Application Performance Monitoring and Error Tracking.
*
* This module configures @sentry/node to send errors to our self-hosted
* Bugsink instance, which is Sentry-compatible.
*
* IMPORTANT: This module should be imported and initialized at the very top
* of server.ts, before any other imports, to ensure all errors are captured.
*
* Note: Uses Sentry SDK v8+ API which differs significantly from v7.
*/
import * as Sentry from '@sentry/node';
import type { Request, Response, NextFunction, ErrorRequestHandler } from 'express';
import { config, isSentryConfigured, isProduction, isTest } from '../config/env';
import { logger } from './logger.server';
/**
* Initializes the Sentry SDK with the configured DSN.
* Should be called once at application startup.
*/
export function initSentry(): void {
if (!isSentryConfigured) {
logger.info('[Sentry] Error tracking disabled (SENTRY_DSN not configured)');
return;
}
// Don't initialize Sentry in test environment
if (isTest) {
logger.debug('[Sentry] Skipping initialization in test environment');
return;
}
Sentry.init({
dsn: config.sentry.dsn,
environment: config.sentry.environment || config.server.nodeEnv,
debug: config.sentry.debug,
// Performance monitoring - disabled for now to keep it simple
tracesSampleRate: 0,
// Before sending an event, add additional context
beforeSend(event, hint) {
// In development, log errors to console as well
if (!isProduction && hint.originalException) {
logger.error(
{ err: hint.originalException, sentryEventId: event.event_id },
'[Sentry] Capturing error',
);
}
return event;
},
});
logger.info(
{ environment: config.sentry.environment || config.server.nodeEnv },
'[Sentry] Error tracking initialized',
);
}
/**
* Creates Sentry middleware for Express.
* Returns the request handler and error handler middleware.
*
* In Sentry SDK v8+, the old Handlers.requestHandler and Handlers.errorHandler
* have been replaced. Request context is now captured automatically via the
* Express integration. We provide a custom error handler that filters errors.
*/
export function getSentryMiddleware(): {
requestHandler: (req: Request, res: Response, next: NextFunction) => void;
errorHandler: ErrorRequestHandler;
} {
if (!isSentryConfigured || isTest) {
// Return no-op middleware when Sentry is not configured
return {
requestHandler: (_req: Request, _res: Response, next: NextFunction) => next(),
errorHandler: (_err: Error, _req: Request, _res: Response, next: NextFunction) => next(_err),
};
}
return {
// In SDK v8+, request context is captured automatically.
// This middleware is a placeholder for compatibility.
requestHandler: (_req: Request, _res: Response, next: NextFunction) => next(),
// Custom error handler that captures errors to Sentry
errorHandler: (err: Error, _req: Request, _res: Response, next: NextFunction) => {
// Only send 5xx errors to Sentry by default
const statusCode =
(err as Error & { statusCode?: number }).statusCode ||
(err as Error & { status?: number }).status ||
500;
if (statusCode >= 500) {
Sentry.captureException(err);
}
// Pass the error to the next error handler
next(err);
},
};
}
/**
* Captures an exception and sends it to Sentry.
* Use this for errors that are caught and handled gracefully.
*/
export function captureException(error: Error, context?: Record<string, unknown>): string | null {
if (!isSentryConfigured || isTest) {
return null;
}
if (context) {
Sentry.setContext('additional', context);
}
return Sentry.captureException(error);
}
/**
* Captures a message and sends it to Sentry.
* Use this for non-exception events that should be tracked.
*/
export function captureMessage(
message: string,
level: Sentry.SeverityLevel = 'info',
): string | null {
if (!isSentryConfigured || isTest) {
return null;
}
return Sentry.captureMessage(message, level);
}
/**
* Sets the user context for all subsequent events.
* Call this after user authentication.
*/
export function setUser(user: { id: string; email?: string; username?: string } | null): void {
if (!isSentryConfigured || isTest) {
return;
}
Sentry.setUser(user);
}
/**
* Adds a breadcrumb to the current scope.
* Breadcrumbs are logged actions that led up to an error.
*/
export function addBreadcrumb(breadcrumb: Sentry.Breadcrumb): void {
if (!isSentryConfigured || isTest) {
return;
}
Sentry.addBreadcrumb(breadcrumb);
}
// Re-export Sentry for advanced usage
export { Sentry };