Files
triggerdotdev--trigger.dev/apps/webapp/test/utils/tracing.ts
T
2026-07-13 13:32:57 +08:00

90 lines
3.2 KiB
TypeScript

import { context, propagation, trace } from "@opentelemetry/api";
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { InMemorySpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
import {
MeterProvider,
InMemoryMetricExporter,
PeriodicExportingMetricReader,
AggregationTemporality,
} from "@opentelemetry/sdk-metrics";
export function createInMemoryTracing() {
// Webapp's vitest config uses `pool: "forks"` with `--no-file-parallelism`,
// so all test files in a shard share one process. globalThis persists
// across files even though vitest clears the module cache between them.
//
// OTel's `provider.register()` calls trace/context/propagation
// `setGlobal*` — and `setGlobal` no-ops (logs an error, returns false)
// when a global is already set. Two patterns hit that path:
// 1. `~/v3/tracer.server.ts` runs `provider.register()` via its
// `singleton("opentelemetry", setupTelemetry)` — first test in the
// shard to import that path sets the globals to webapp's tracer.
// 2. A subsequent test calls `createInMemoryTracing()` to swap in its
// own in-memory provider. Without disabling first, register() is
// a no-op — the test's provider receives spans via its
// SimpleSpanProcessor (provider-scoped), but `trace.getActiveSpan()`
// (used by code under test, e.g. sentryTraceContext.server.ts)
// reads from the stale global context manager from step 1.
//
// Disable first, then register, so the test's provider always wins
// for both span recording and the global API. After the test, the
// next caller's createInMemoryTracing rotates again — no leakage.
trace.disable();
context.disable();
propagation.disable();
const exporter = new InMemorySpanExporter();
const provider = new NodeTracerProvider({
spanProcessors: [new SimpleSpanProcessor(exporter)],
});
provider.register();
// Use the provider's tracer so spans hit this exporter even when a global
// NodeTracerProvider was already registered (e.g. via tracer.server import chain).
const tracer = provider.getTracer("test-tracer");
return {
exporter,
tracer,
};
}
export function createInMemoryMetrics() {
// Initialize the metric exporter with cumulative temporality for easier testing
const metricExporter = new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE);
// Create a metric reader that exports frequently for testing
const metricReader = new PeriodicExportingMetricReader({
exporter: metricExporter,
exportIntervalMillis: 100, // Export frequently for tests
});
// Initialize the meter provider
const meterProvider = new MeterProvider({
readers: [metricReader],
});
// Retrieve a meter
const meter = meterProvider.getMeter("test-meter");
return {
metricExporter,
metricReader,
meterProvider,
meter,
// Helper to force collection and get metrics
async getMetrics() {
await metricReader.forceFlush();
return metricExporter.getMetrics();
},
// Helper to reset metrics between tests
reset() {
metricExporter.reset();
},
// Helper to shutdown the meter provider
async shutdown() {
await meterProvider.shutdown();
},
};
}