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:
2026-05-11 16:27:36 +02:00
parent 131efd5d2f
commit 098fcd0d51
4 changed files with 101 additions and 0 deletions

View File

@@ -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";

View File

@@ -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
});
});

View File

@@ -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;
}

View File

@@ -1,2 +1,3 @@
export { initOtelServerNode, type InitOtelServerNodeOpts } from "./init-server-node";
export { buildResource, type BuildResourceOpts } from "./resource";
export { currentTraceId } from "./current-trace-id";