mirror of
https://github.com/mauricioabh/arbpulse.git
synced 2026-08-06 20:17:43 +00:00
Add Sentry, Pino logs, and OTel spans on WS pipeline (M2).
Capture REST/WS/SSE errors in Sentry, emit structured pino logs with correlation IDs, and trace the book-tick pipeline to Sentry via OpenTelemetry. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import { initOpenTelemetry } from "./otel.js";
|
||||
import { initSentry } from "./sentry.js";
|
||||
|
||||
export function initInstrumentation(): void {
|
||||
initSentry();
|
||||
initOpenTelemetry();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import * as Sentry from "@sentry/node";
|
||||
import { trace, type Span, type Tracer } from "@opentelemetry/api";
|
||||
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
||||
import { SentrySpanProcessor, SentryPropagator } from "@sentry/opentelemetry";
|
||||
|
||||
let tracer: Tracer | null = null;
|
||||
|
||||
/**
|
||||
* OpenTelemetry spans export to Sentry via @sentry/opentelemetry when SENTRY_DSN is set.
|
||||
* Without a DSN, spans are no-ops (safe for local dev and tests).
|
||||
*/
|
||||
export function initOpenTelemetry(): Tracer {
|
||||
if (tracer) return tracer;
|
||||
|
||||
if (!process.env.SENTRY_DSN?.trim()) {
|
||||
tracer = trace.getTracer("arbpulse");
|
||||
return tracer;
|
||||
}
|
||||
|
||||
const provider = new NodeTracerProvider({
|
||||
spanProcessors: [new SentrySpanProcessor()],
|
||||
});
|
||||
provider.register({
|
||||
propagator: new SentryPropagator(),
|
||||
});
|
||||
|
||||
tracer = provider.getTracer("arbpulse");
|
||||
return tracer;
|
||||
}
|
||||
|
||||
export function getTracer(): Tracer {
|
||||
return tracer ?? initOpenTelemetry();
|
||||
}
|
||||
|
||||
export async function withSpan<T>(
|
||||
name: string,
|
||||
attributes: Record<string, string | number | boolean>,
|
||||
fn: (span: Span) => T | Promise<T>,
|
||||
): Promise<T> {
|
||||
const activeTracer = getTracer();
|
||||
return activeTracer.startActiveSpan(name, { attributes }, async (span) => {
|
||||
try {
|
||||
return await fn(span);
|
||||
} catch (err) {
|
||||
span.setStatus({ code: 2, message: String(err) });
|
||||
Sentry.captureException(err);
|
||||
throw err;
|
||||
} finally {
|
||||
span.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as Sentry from "@sentry/node";
|
||||
|
||||
const dsn = process.env.SENTRY_DSN?.trim();
|
||||
|
||||
export function initSentry(): void {
|
||||
if (!dsn) return;
|
||||
|
||||
Sentry.init({
|
||||
dsn,
|
||||
environment: process.env.NODE_ENV ?? "development",
|
||||
tracesSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1,
|
||||
integrations: [Sentry.httpIntegration(), Sentry.expressIntegration()],
|
||||
});
|
||||
|
||||
process.on("unhandledRejection", (reason) => {
|
||||
Sentry.captureException(
|
||||
reason instanceof Error ? reason : new Error(String(reason)),
|
||||
);
|
||||
});
|
||||
|
||||
process.on("uncaughtException", (error) => {
|
||||
Sentry.captureException(error);
|
||||
});
|
||||
}
|
||||
|
||||
export { Sentry };
|
||||
Reference in New Issue
Block a user