/**
* Observability instrumentation (Sentry + OpenTelemetry)
*
* MUST be imported before any other modules to ensure proper instrumentation.
* Initializes Sentry for error tracking and OpenTelemetry for distributed tracing.
*/
import * as Sentry from '@sentry/node';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { Resource } from '@opentelemetry/resources';
import {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} from '@opentelemetry/semantic-conventions';
import { getConfig } from './config/index.js';
import { sanitizePii } from './shared/pii-sanitizer.js';
const config = getConfig();
/**
* Initialize Sentry error tracking
*/
function initSentry(): void {
if (!config.sentryDsn) {
return;
}
Sentry.init({
dsn: config.sentryDsn,
environment: process.env['NODE_ENV'] ?? 'development',
release: config.serverVersion,
// Performance monitoring
tracesSampleRate: config.debugMode ? 1.0 : 0.1,
// PII filtering
beforeSend(event) {
// Sanitize error messages
if (event.message) {
event.message = sanitizePii(event.message);
}
// Sanitize breadcrumbs
if (event.breadcrumbs) {
for (const breadcrumb of event.breadcrumbs) {
if (breadcrumb.message) {
breadcrumb.message = sanitizePii(breadcrumb.message);
}
}
}
return event;
},
// Filter sensitive data
beforeSendTransaction(event) {
// Remove sensitive headers
if (event.request?.headers) {
const sensitiveHeaders = ['authorization', 'cookie', 'x-api-key'];
for (const header of sensitiveHeaders) {
if (header in event.request.headers) {
event.request.headers[header] = '[REDACTED]';
}
}
}
return event;
},
});
console.error(`[instrumentation] Sentry initialized`);
}
/**
* Initialize OpenTelemetry tracing
*/
let otelSdk: NodeSDK | null = null;
function initOpenTelemetry(): void {
if (!config.otelEnabled) {
return;
}
const resource = new Resource({
[ATTR_SERVICE_NAME]: config.serverName,
[ATTR_SERVICE_VERSION]: config.serverVersion,
});
const traceExporter = config.otelEndpoint
? new OTLPTraceExporter({ url: config.otelEndpoint })
: undefined;
otelSdk = new NodeSDK({
resource,
traceExporter,
instrumentations: [
getNodeAutoInstrumentations({
// Disable fs instrumentation (too noisy)
'@opentelemetry/instrumentation-fs': { enabled: false },
}),
],
});
otelSdk.start();
console.error(`[instrumentation] OpenTelemetry initialized`);
}
/**
* Shutdown observability (for graceful exit)
*/
export async function shutdownObservability(): Promise<void> {
const promises: Promise<void>[] = [];
if (otelSdk) {
promises.push(otelSdk.shutdown());
}
promises.push(Sentry.close(2000).then(() => undefined));
await Promise.all(promises);
}
// Initialize on import
initSentry();
initOpenTelemetry();