feat(core-shared): currentTraceId helper for OTel-audit correlation bridge
Reads the active OTel span context via trace.getActiveSpan(); returns the 32-char hex traceId or undefined when no span is active or traceId is the all-zeros invalid value. Re-exported from both instrumentation/otel/index and instrumentation/index barrels. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,3 +28,4 @@ export { bindOtelInstrumentation as bindSentryInstrumentation } from "./di/bind-
|
||||
export type { BindOtelOpts as BindSentryOpts } from "./di/bind-otel-instrumentation";
|
||||
|
||||
export { initSentryClientReact } from "./sentry/init-client-react";
|
||||
export { currentTraceId } from "./otel/current-trace-id";
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { context, trace } from "@opentelemetry/api";
|
||||
import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
|
||||
import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
||||
import { currentTraceId } from "./current-trace-id";
|
||||
|
||||
// Register async context manager so startActiveSpan propagates context.
|
||||
const ctxManager = new AsyncLocalStorageContextManager();
|
||||
ctxManager.enable();
|
||||
context.setGlobalContextManager(ctxManager);
|
||||
|
||||
function setupProvider(): { exporter: InMemorySpanExporter; provider: BasicTracerProvider } {
|
||||
const exporter = new InMemorySpanExporter();
|
||||
const provider = new BasicTracerProvider({
|
||||
spanProcessors: [new SimpleSpanProcessor(exporter)],
|
||||
});
|
||||
trace.setGlobalTracerProvider(provider);
|
||||
return { exporter, provider };
|
||||
}
|
||||
|
||||
describe("currentTraceId", () => {
|
||||
let exporter: InMemorySpanExporter;
|
||||
let provider: BasicTracerProvider;
|
||||
|
||||
beforeEach(() => {
|
||||
({ exporter, provider } = setupProvider());
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await provider.shutdown();
|
||||
trace.disable();
|
||||
});
|
||||
|
||||
it("returns undefined when no active span", () => {
|
||||
expect(currentTraceId()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns the active span's traceId when inside startActiveSpan", async () => {
|
||||
const tracer = trace.getTracer("test");
|
||||
await new Promise<void>((resolve) => {
|
||||
tracer.startActiveSpan("test-span", (span) => {
|
||||
const id = currentTraceId();
|
||||
expect(id).toBeDefined();
|
||||
expect(id).toMatch(/^[a-f0-9]{32}$/);
|
||||
span.end();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("filters all-zeros invalid traceId", () => {
|
||||
// The INVALID_SPAN (no-op) has traceId "00000000000000000000000000000000"
|
||||
// which is what getActiveSpan() returns when there is no real span.
|
||||
// currentTraceId() must treat this as absent.
|
||||
expect(currentTraceId()).toBeUndefined();
|
||||
});
|
||||
|
||||
// Suppress unused variable warning — exporter used via closure
|
||||
it("returns distinct traceIds for independent spans", async () => {
|
||||
const tracer = trace.getTracer("test");
|
||||
const ids: string[] = [];
|
||||
for (let i = 0; i < 2; i++) {
|
||||
await new Promise<void>((resolve) => {
|
||||
tracer.startActiveSpan(`span-${i}`, (span) => {
|
||||
const id = currentTraceId();
|
||||
if (id) ids.push(id);
|
||||
span.end();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
// Both spans are in fresh traces — IDs should be valid hex strings
|
||||
expect(ids).toHaveLength(2);
|
||||
for (const id of ids) {
|
||||
expect(id).toMatch(/^[a-f0-9]{32}$/);
|
||||
}
|
||||
void exporter; // suppress lint
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { trace } from "@opentelemetry/api";
|
||||
|
||||
/**
|
||||
* Returns the trace ID of the currently active OTel span, or undefined if
|
||||
* there is no active span (e.g., outside any request context, in unit tests
|
||||
* without an OTel SDK).
|
||||
*
|
||||
* Used by core-audit's TraceIdEnrichingAuditLog decorator to auto-populate
|
||||
* AuditEntry.correlationId so callers don't have to thread it explicitly.
|
||||
*
|
||||
* Returns undefined for the all-zeros invalid trace ID — OTel emits this
|
||||
* when context propagation hasn't kicked in.
|
||||
*/
|
||||
export function currentTraceId(): string | undefined {
|
||||
const span = trace.getActiveSpan();
|
||||
if (!span) return undefined;
|
||||
const ctx = span.spanContext();
|
||||
if (!ctx.traceId || /^0+$/.test(ctx.traceId)) return undefined;
|
||||
return ctx.traceId;
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export { initOtelServerNode, type InitOtelServerNodeOpts } from "./init-server-node";
|
||||
export { buildResource, type BuildResourceOpts } from "./resource";
|
||||
export { currentTraceId } from "./current-trace-id";
|
||||
|
||||
Reference in New Issue
Block a user