Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions packages/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import './bootstrap';
import './redis-client';

import { logger } from '@js/utils/logger';
import { Sentry } from '@js/utils/sentry';
import express from 'express';
import fs from 'fs';
import https from 'https';
Expand Down Expand Up @@ -81,16 +82,20 @@ if (process.env.NODE_ENV !== 'test' && certsExist) {
export { serverInstance };

serverInstance.on('error', (error) => {
console.error('Server failed to start:', error);
logger.error(error);
});

process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
process.exit(1);
logger.error(error);
// Flush pending Sentry events before exiting
Sentry.close(2000).then(() => process.exit(1));
});

process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
process.on('unhandledRejection', (reason) => {
logger.error({
message: 'Unhandled Rejection',
error: reason instanceof Error ? reason : new Error(String(reason)),
});
});

process.on('SIGINT', () => {
Expand Down
40 changes: 40 additions & 0 deletions packages/backend/src/js/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { getCurrentSessionId } from '@common/lib/cls/session-id';
import winston, { format, transports } from 'winston';
import LokiTransport from 'winston-loki';

import { isSentryEnabled, Sentry } from './sentry';

const transportsArray: winston.transport[] = [new transports.Console()];
const { GRAFANA_LOKI_HOST, GRAFANA_LOKI_AUTH, GRAFANA_LOKI_USER_ID } = process.env;
const isGrafanaConfigured = Boolean(GRAFANA_LOKI_HOST && GRAFANA_LOKI_AUTH && GRAFANA_LOKI_USER_ID);
Expand Down Expand Up @@ -59,6 +61,17 @@ const createLogger =
...meta,
});
}

if (severity === 'warn' && isSentryEnabled()) {
Sentry.withScope((scope) => {
scope.setExtras(meta);
const requestId = getCurrentRequestId();
const sessionId = getCurrentSessionId();
if (requestId) scope.setTag('requestId', requestId);
if (sessionId) scope.setTag('sessionId', sessionId);
Sentry.captureMessage(message, 'warning');
});
}
};

const formatErrorToString = (error: string | Error) => {
Expand Down Expand Up @@ -105,6 +118,33 @@ function loggerErrorHandler(
...extra,
});
}

if (isSentryEnabled()) {
Sentry.withScope((scope) => {
scope.setExtras(extra);
const requestId = getCurrentRequestId();
const sessionId = getCurrentSessionId();
if (requestId) scope.setTag('requestId', requestId);
if (sessionId) scope.setTag('sessionId', sessionId);

// Extract the Error object for proper stack traces in Sentry
const errorObj =
messageParam instanceof Error
? messageParam
: typeof messageParam === 'object' && messageParam.error
? messageParam.error
: null;

if (errorObj) {
if (typeof messageParam === 'object' && 'message' in messageParam && messageParam.message) {
scope.setExtra('errorContext', messageParam.message);
}
Sentry.captureException(errorObj);
} else {
Sentry.captureMessage(messageResult, 'error');
}
});
}
}

const logger = {
Expand Down
21 changes: 1 addition & 20 deletions packages/backend/src/js/utils/sentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const SENTRY_DSN = process.env.SENTRY_DSN;
* Check if Sentry should be enabled.
* Only enabled in production with valid DSN.
*/
function isSentryEnabled(): boolean {
export function isSentryEnabled(): boolean {
const isProduction = process.env.NODE_ENV === 'production';
const hasDsn = Boolean(SENTRY_DSN);

Expand Down Expand Up @@ -37,25 +37,6 @@ export function initSentry(): void {
// Express integration
Sentry.expressIntegration(),
],
// Filter out noisy errors
ignoreErrors: [
// Network errors that are expected
'ECONNREFUSED',
'ECONNRESET',
'ETIMEDOUT',
// Client disconnects
'EPIPE',
'ENOTFOUND',
],
// Before sending, add extra context
beforeSend(event, hint) {
// Don't send in non-production
if (process.env.NODE_ENV !== 'production') {
console.error('[Sentry Debug]', hint.originalException);
return null;
}
return event;
},
});
}

Expand Down
Loading