diff --git a/.gitignore b/.gitignore index 9aa2ade..c83f729 100644 --- a/.gitignore +++ b/.gitignore @@ -156,4 +156,4 @@ packages/**/version.ts !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json -*.code-workspace \ No newline at end of file +*.code-workspace diff --git a/packages/durabletask-js/src/tracing/constants.ts b/packages/durabletask-js/src/tracing/constants.ts index c892934..a320dcf 100644 --- a/packages/durabletask-js/src/tracing/constants.ts +++ b/packages/durabletask-js/src/tracing/constants.ts @@ -78,11 +78,11 @@ export function createSpanName(type: string, name: string, version?: string): st /** * Creates a timer span name following the Durable Task naming convention. * - * Format: "timer:{orchName}" + * Format: "orchestration:{orchName}:timer" * * @param orchestrationName - The name of the parent orchestration. * @returns The formatted timer span name. */ export function createTimerSpanName(orchestrationName: string): string { - return `${TaskType.TIMER}:${orchestrationName}`; + return `${TaskType.ORCHESTRATION}:${orchestrationName}:${TaskType.TIMER}`; } diff --git a/packages/durabletask-js/src/tracing/index.ts b/packages/durabletask-js/src/tracing/index.ts index d3df6ad..0574072 100644 --- a/packages/durabletask-js/src/tracing/index.ts +++ b/packages/durabletask-js/src/tracing/index.ts @@ -10,6 +10,7 @@ export { createPbTraceContext, extractTraceparentFromSpan, createParentContextFromPb, + createPbTraceContextFromSpan, } from "./trace-context-utils"; // Internal-only exports (not re-exported from package index.ts): diff --git a/packages/durabletask-js/src/tracing/trace-context-utils.ts b/packages/durabletask-js/src/tracing/trace-context-utils.ts index 87001e7..44a955b 100644 --- a/packages/durabletask-js/src/tracing/trace-context-utils.ts +++ b/packages/durabletask-js/src/tracing/trace-context-utils.ts @@ -130,6 +130,36 @@ export function extractTraceparentFromSpan(span: Span | undefined | null): { tra return { traceparent, tracestate }; } +/** + * Creates a protobuf TraceContext directly from a Span, avoiding the + * format→parse roundtrip of extractTraceparentFromSpan + createPbTraceContext. + * Returns undefined if the span context is not valid. + */ +export function createPbTraceContextFromSpan(span: Span | undefined | null): pb.TraceContext | undefined { + const otel = getOtelApi(); + if (!otel || !span) return undefined; + + const spanContext = span.spanContext(); + if (!otel.isSpanContextValid(spanContext)) { + return undefined; + } + + const traceparent = createTraceparent(spanContext.traceId, spanContext.spanId, spanContext.traceFlags); + + const ctx = new pb.TraceContext(); + ctx.setTraceparent(traceparent); + ctx.setSpanid(spanContext.spanId); + + const tracestate = spanContext.traceState?.serialize(); + if (tracestate) { + const sv = new StringValue(); + sv.setValue(tracestate); + ctx.setTracestate(sv); + } + + return ctx; +} + /** * Creates an OTEL Context with a remote parent span from a protobuf TraceContext. * Returns undefined if OTEL is not available or the pbTraceContext is not provided. diff --git a/packages/durabletask-js/src/tracing/trace-helper.ts b/packages/durabletask-js/src/tracing/trace-helper.ts index e88dc1b..46d0ee4 100644 --- a/packages/durabletask-js/src/tracing/trace-helper.ts +++ b/packages/durabletask-js/src/tracing/trace-helper.ts @@ -7,20 +7,39 @@ import { Timestamp } from "google-protobuf/google/protobuf/timestamp_pb"; import { TRACER_NAME, DurableTaskAttributes, TaskType, createSpanName, createTimerSpanName } from "./constants"; import { getOtelApi, - extractTraceparentFromSpan, - createPbTraceContext, + createPbTraceContextFromSpan, createParentContextFromPb, } from "./trace-context-utils"; import type { Span, Tracer } from "@opentelemetry/api"; +// Cached tracer instance to avoid repeated lookups. The tracer is created once +// and reused for the lifetime of the process. This is safe because the OTEL JS +// SDK returns a proxy tracer from `trace.getTracer()` that dynamically delegates +// to the current global tracer provider, so provider swaps (e.g., in tests via +// `setGlobalTracerProvider`) are handled transparently. +let _cachedTracer: Tracer | undefined; + +/** + * Returns the OTEL API and tracer, or undefined if OTEL is not available. + * Caches the tracer instance for efficiency. + */ +function getTracingContext(): { otel: typeof import("@opentelemetry/api"); tracer: Tracer } | undefined { + const otel = getOtelApi(); + if (!otel) return undefined; + + if (!_cachedTracer) { + _cachedTracer = otel.trace.getTracer(TRACER_NAME); + } + + return { otel, tracer: _cachedTracer }; +} + /** * Gets the Durable Task tracer from the OpenTelemetry API. * Returns undefined if OpenTelemetry is not installed. */ export function getTracer(): Tracer | undefined { - const otel = getOtelApi(); - if (!otel) return undefined; - return otel.trace.getTracer(TRACER_NAME); + return getTracingContext()?.tracer; } /** @@ -50,19 +69,18 @@ export interface OrchestrationSpanInfo { * @returns The span (or undefined if OTEL is not available). Caller must end it. */ export function startSpanForNewOrchestration(req: pb.CreateInstanceRequest): Span | undefined { - const otel = getOtelApi(); - const tracer = getTracer(); - if (!otel || !tracer) return undefined; + const ctx = getTracingContext(); + if (!ctx) return undefined; const name = req.getName(); const version = req.getVersion()?.getValue(); const instanceId = req.getInstanceid(); const spanName = createSpanName(TaskType.CREATE_ORCHESTRATION, name, version); - const span = tracer.startSpan(spanName, { - kind: otel.SpanKind.PRODUCER, + const span = ctx.tracer.startSpan(spanName, { + kind: ctx.otel.SpanKind.PRODUCER, attributes: { - [DurableTaskAttributes.TYPE]: TaskType.CREATE_ORCHESTRATION, + [DurableTaskAttributes.TYPE]: TaskType.ORCHESTRATION, [DurableTaskAttributes.TASK_NAME]: name, [DurableTaskAttributes.TASK_INSTANCE_ID]: instanceId, ...(version ? { [DurableTaskAttributes.TASK_VERSION]: version } : {}), @@ -70,9 +88,8 @@ export function startSpanForNewOrchestration(req: pb.CreateInstanceRequest): Spa }); // Inject trace context into the proto request - const traceInfo = extractTraceparentFromSpan(span); - if (traceInfo) { - const pbCtx = createPbTraceContext(traceInfo.traceparent, traceInfo.tracestate); + const pbCtx = createPbTraceContextFromSpan(span); + if (pbCtx) { req.setParenttracecontext(pbCtx); } @@ -93,9 +110,8 @@ export function startSpanForOrchestrationExecution( orchestrationTraceContext: pb.OrchestrationTraceContext | undefined, instanceId: string, ): { span: Span; spanInfo: OrchestrationSpanInfo } | undefined { - const otel = getOtelApi(); - const tracer = getTracer(); - if (!otel || !tracer) return undefined; + const ctx = getTracingContext(); + if (!ctx) return undefined; const name = executionStartedEvent.getName(); const version = executionStartedEvent.getVersion()?.getValue(); @@ -118,10 +134,10 @@ export function startSpanForOrchestrationExecution( // first-execution time for storage in OrchestrationTraceContext. const persistedStartTime = isReplay ? existingStartTime! : spanStartTime; - const span = tracer.startSpan( + const span = ctx.tracer.startSpan( spanName, { - kind: otel.SpanKind.SERVER, + kind: ctx.otel.SpanKind.SERVER, startTime: spanStartTime, attributes: { [DurableTaskAttributes.TYPE]: TaskType.ORCHESTRATION, @@ -168,21 +184,20 @@ export function startSpanForSchedulingTask( action: pb.ScheduleTaskAction, taskId: number, ): void { - const otel = getOtelApi(); - const tracer = getTracer(); - if (!otel || !tracer) return; + const ctx = getTracingContext(); + if (!ctx) return; const name = action.getName(); const version = action.getVersion()?.getValue(); const spanName = createSpanName(TaskType.ACTIVITY, name, version); // Create a context with the orchestration span as parent - const parentContext = otel.trace.setSpan(otel.context.active(), orchestrationSpan); + const parentContext = ctx.otel.trace.setSpan(ctx.otel.context.active(), orchestrationSpan); - const span = tracer.startSpan( + const span = ctx.tracer.startSpan( spanName, { - kind: otel.SpanKind.CLIENT, + kind: ctx.otel.SpanKind.CLIENT, attributes: { [DurableTaskAttributes.TYPE]: TaskType.ACTIVITY, [DurableTaskAttributes.TASK_NAME]: name, @@ -194,9 +209,8 @@ export function startSpanForSchedulingTask( ); // Inject trace context into the action - const traceInfo = extractTraceparentFromSpan(span); - if (traceInfo) { - const pbCtx = createPbTraceContext(traceInfo.traceparent, traceInfo.tracestate); + const pbCtx = createPbTraceContextFromSpan(span); + if (pbCtx) { action.setParenttracecontext(pbCtx); } @@ -211,9 +225,8 @@ export function startSpanForSchedulingTask( * @returns The span (or undefined if OTEL is not available). Caller must end it. */ export function startSpanForTaskExecution(req: pb.ActivityRequest): Span | undefined { - const otel = getOtelApi(); - const tracer = getTracer(); - if (!otel || !tracer) return undefined; + const ctx = getTracingContext(); + if (!ctx) return undefined; const name = req.getName(); const spanName = createSpanName(TaskType.ACTIVITY, name); @@ -223,10 +236,10 @@ export function startSpanForTaskExecution(req: pb.ActivityRequest): Span | undef const instanceId = req.getOrchestrationinstance()?.getInstanceid() ?? ""; - const span = tracer.startSpan( + const span = ctx.tracer.startSpan( spanName, { - kind: otel.SpanKind.SERVER, + kind: ctx.otel.SpanKind.SERVER, attributes: { [DurableTaskAttributes.TYPE]: TaskType.ACTIVITY, [DurableTaskAttributes.TASK_NAME]: name, @@ -253,23 +266,22 @@ export function startSpanForSchedulingSubOrchestration( action: pb.CreateSubOrchestrationAction, taskId: number, ): void { - const otel = getOtelApi(); - const tracer = getTracer(); - if (!otel || !tracer) return; + const ctx = getTracingContext(); + if (!ctx) return; const name = action.getName(); const version = action.getVersion()?.getValue(); const instanceId = action.getInstanceid(); - const spanName = createSpanName(TaskType.CREATE_ORCHESTRATION, name, version); + const spanName = createSpanName(TaskType.ORCHESTRATION, name, version); - const parentContext = otel.trace.setSpan(otel.context.active(), orchestrationSpan); + const parentContext = ctx.otel.trace.setSpan(ctx.otel.context.active(), orchestrationSpan); - const span = tracer.startSpan( + const span = ctx.tracer.startSpan( spanName, { - kind: otel.SpanKind.CLIENT, + kind: ctx.otel.SpanKind.CLIENT, attributes: { - [DurableTaskAttributes.TYPE]: TaskType.CREATE_ORCHESTRATION, + [DurableTaskAttributes.TYPE]: TaskType.ORCHESTRATION, [DurableTaskAttributes.TASK_NAME]: name, [DurableTaskAttributes.TASK_INSTANCE_ID]: instanceId, [DurableTaskAttributes.TASK_TASK_ID]: taskId, @@ -280,9 +292,8 @@ export function startSpanForSchedulingSubOrchestration( ); // Inject trace context into the action - const traceInfo = extractTraceparentFromSpan(span); - if (traceInfo) { - const pbCtx = createPbTraceContext(traceInfo.traceparent, traceInfo.tracestate); + const pbCtx = createPbTraceContextFromSpan(span); + if (pbCtx) { action.setParenttracecontext(pbCtx); } @@ -304,17 +315,16 @@ export function emitSpanForTimer( fireAt: Date, timerId: number, ): void { - const otel = getOtelApi(); - const tracer = getTracer(); - if (!otel || !tracer) return; + const ctx = getTracingContext(); + if (!ctx) return; const spanName = createTimerSpanName(orchestrationName); - const parentContext = otel.trace.setSpan(otel.context.active(), orchestrationSpan); + const parentContext = ctx.otel.trace.setSpan(ctx.otel.context.active(), orchestrationSpan); - const span = tracer.startSpan( + const span = ctx.tracer.startSpan( spanName, { - kind: otel.SpanKind.INTERNAL, + kind: ctx.otel.SpanKind.INTERNAL, attributes: { [DurableTaskAttributes.TYPE]: TaskType.TIMER, [DurableTaskAttributes.TASK_TASK_ID]: timerId, @@ -339,19 +349,18 @@ export function emitSpanForEventSent( eventName: string, targetInstanceId?: string, ): void { - const otel = getOtelApi(); - const tracer = getTracer(); - if (!otel || !tracer) return; + const ctx = getTracingContext(); + if (!ctx) return; const spanName = createSpanName(TaskType.ORCHESTRATION_EVENT, eventName); - const parentContext = otel.trace.setSpan(otel.context.active(), orchestrationSpan); + const parentContext = ctx.otel.trace.setSpan(ctx.otel.context.active(), orchestrationSpan); - const span = tracer.startSpan( + const span = ctx.tracer.startSpan( spanName, { - kind: otel.SpanKind.PRODUCER, + kind: ctx.otel.SpanKind.PRODUCER, attributes: { - [DurableTaskAttributes.TYPE]: TaskType.ORCHESTRATION_EVENT, + [DurableTaskAttributes.TYPE]: TaskType.EVENT, [DurableTaskAttributes.TASK_NAME]: eventName, ...(targetInstanceId ? { [DurableTaskAttributes.EVENT_TARGET_INSTANCE_ID]: targetInstanceId } : {}), }, @@ -370,16 +379,15 @@ export function emitSpanForEventSent( * @returns The span (or undefined if OTEL is not available). Caller must end it. */ export function startSpanForEventRaisedFromClient(eventName: string, instanceId: string): Span | undefined { - const otel = getOtelApi(); - const tracer = getTracer(); - if (!otel || !tracer) return undefined; + const ctx = getTracingContext(); + if (!ctx) return undefined; const spanName = createSpanName(TaskType.ORCHESTRATION_EVENT, eventName); - const span = tracer.startSpan(spanName, { - kind: otel.SpanKind.PRODUCER, + const span = ctx.tracer.startSpan(spanName, { + kind: ctx.otel.SpanKind.PRODUCER, attributes: { - [DurableTaskAttributes.TYPE]: TaskType.ORCHESTRATION_EVENT, + [DurableTaskAttributes.TYPE]: TaskType.EVENT, [DurableTaskAttributes.TASK_NAME]: eventName, [DurableTaskAttributes.EVENT_TARGET_INSTANCE_ID]: instanceId, }, diff --git a/packages/durabletask-js/test/tracing.spec.ts b/packages/durabletask-js/test/tracing.spec.ts index a02efbd..58f2d55 100644 --- a/packages/durabletask-js/test/tracing.spec.ts +++ b/packages/durabletask-js/test/tracing.spec.ts @@ -14,6 +14,7 @@ import { import { createPbTraceContext, + createPbTraceContextFromSpan, getOtelApi, extractTraceparentFromSpan, createParentContextFromPb, @@ -111,7 +112,7 @@ describe("createSpanName", () => { describe("createTimerSpanName", () => { it("should create a timer span name", () => { - expect(createTimerSpanName("MyOrch")).toBe("timer:MyOrch"); + expect(createTimerSpanName("MyOrch")).toBe("orchestration:MyOrch:timer"); }); }); @@ -270,7 +271,7 @@ describe("Trace Helper - startSpanForNewOrchestration", () => { expect(spans.length).toBe(1); expect(spans[0].name).toBe("create_orchestration:MyOrchestration"); expect(spans[0].kind).toBe(otel.SpanKind.PRODUCER); - expect(spans[0].attributes[DurableTaskAttributes.TYPE]).toBe(TaskType.CREATE_ORCHESTRATION); + expect(spans[0].attributes[DurableTaskAttributes.TYPE]).toBe(TaskType.ORCHESTRATION); expect(spans[0].attributes[DurableTaskAttributes.TASK_NAME]).toBe("MyOrchestration"); expect(spans[0].attributes[DurableTaskAttributes.TASK_INSTANCE_ID]).toBe("test-instance-123"); }); @@ -588,7 +589,7 @@ describe("Trace Helper - processActionsForTracing", () => { parentSpan.end(); const spans = exporter.getFinishedSpans(); - const childSpan = spans.find((s: any) => s.name === "create_orchestration:SubOrch"); + const childSpan = spans.find((s: any) => s.name === "orchestration:SubOrch"); expect(childSpan).toBeDefined(); expect(childSpan!.kind).toBe(otel.SpanKind.CLIENT); }); @@ -611,7 +612,7 @@ describe("Trace Helper - processActionsForTracing", () => { parentSpan.end(); const spans = exporter.getFinishedSpans(); - const timerSpan = spans.find((s: any) => s.name === "timer:MyOrchestration"); + const timerSpan = spans.find((s: any) => s.name === "orchestration:MyOrchestration:timer"); expect(timerSpan).toBeDefined(); expect(timerSpan!.kind).toBe(otel.SpanKind.INTERNAL); }); @@ -637,7 +638,7 @@ describe("Trace Helper - processActionsForTracing", () => { const eventSpan = spans.find((s: any) => s.name === "orchestration_event:ApprovalEvent"); expect(eventSpan).toBeDefined(); expect(eventSpan!.kind).toBe(otel.SpanKind.PRODUCER); - expect(eventSpan!.attributes[DurableTaskAttributes.TYPE]).toBe(TaskType.ORCHESTRATION_EVENT); + expect(eventSpan!.attributes[DurableTaskAttributes.TYPE]).toBe(TaskType.EVENT); expect(eventSpan!.attributes[DurableTaskAttributes.TASK_NAME]).toBe("ApprovalEvent"); expect(eventSpan!.attributes[DurableTaskAttributes.EVENT_TARGET_INSTANCE_ID]).toBe("target-instance-1"); }); @@ -799,7 +800,7 @@ describe("Trace Helper - emitSpanForTimer", () => { parentSpan.end(); const spans = exporter.getFinishedSpans(); - const timerSpan = spans.find((s: any) => s.name === "timer:TimerOrch"); + const timerSpan = spans.find((s: any) => s.name === "orchestration:TimerOrch:timer"); expect(timerSpan).toBeDefined(); expect(timerSpan!.kind).toBe(otel.SpanKind.INTERNAL); expect(timerSpan!.attributes[DurableTaskAttributes.TYPE]).toBe(TaskType.TIMER); @@ -820,7 +821,7 @@ describe("Trace Helper - emitSpanForEventSent", () => { const eventSpan = spans.find((s: any) => s.name === "orchestration_event:ApprovalEvent"); expect(eventSpan).toBeDefined(); expect(eventSpan!.kind).toBe(otel.SpanKind.PRODUCER); - expect(eventSpan!.attributes[DurableTaskAttributes.TYPE]).toBe(TaskType.ORCHESTRATION_EVENT); + expect(eventSpan!.attributes[DurableTaskAttributes.TYPE]).toBe(TaskType.EVENT); expect(eventSpan!.attributes[DurableTaskAttributes.TASK_NAME]).toBe("ApprovalEvent"); expect(eventSpan!.attributes[DurableTaskAttributes.EVENT_TARGET_INSTANCE_ID]).toBe("target-instance-1"); }); @@ -849,12 +850,120 @@ describe("Trace Helper - startSpanForEventRaisedFromClient", () => { expect(spans.length).toBe(1); expect(spans[0].name).toBe("orchestration_event:MyEvent"); expect(spans[0].kind).toBe(otel.SpanKind.PRODUCER); - expect(spans[0].attributes[DurableTaskAttributes.TYPE]).toBe(TaskType.ORCHESTRATION_EVENT); + expect(spans[0].attributes[DurableTaskAttributes.TYPE]).toBe(TaskType.EVENT); expect(spans[0].attributes[DurableTaskAttributes.TASK_NAME]).toBe("MyEvent"); expect(spans[0].attributes[DurableTaskAttributes.EVENT_TARGET_INSTANCE_ID]).toBe("target-instance-42"); }); }); +describe("createPbTraceContextFromSpan", () => { + it("should return undefined when OTEL reports an invalid span context", () => { + // Create a span with an explicitly invalid (all-zeros) span context + const invalidSpanContext: otel.SpanContext = { + traceId: "00000000000000000000000000000000", + spanId: "0000000000000000", + traceFlags: otel.TraceFlags.NONE, + }; + const nonRecordingSpan = otel.trace.wrapSpanContext(invalidSpanContext); + + const result = createPbTraceContextFromSpan(nonRecordingSpan); + expect(result).toBeUndefined(); + }); + + it("should correctly format traceparent with hex-padded flags (flags=0)", () => { + const tracer = otel.trace.getTracer(TRACER_NAME); + // Start a span; the default traceFlags from the SDK is SAMPLED (0x01), + // but we verify the hex padding by checking the formatted string. + const span = tracer.startSpan("pb-from-span-flags-test"); + const spanCtx = span.spanContext(); + + const result = createPbTraceContextFromSpan(span); + expect(result).toBeDefined(); + expect(result).toBeInstanceOf(pb.TraceContext); + + const traceparent = result!.getTraceparent(); + // Verify full W3C traceparent format + expect(traceparent).toMatch(/^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/); + // Verify components match the span context + expect(traceparent).toContain(spanCtx.traceId); + expect(traceparent).toContain(spanCtx.spanId); + // Verify flags are zero-padded to 2 hex chars + const flagsHex = (spanCtx.traceFlags & 0xff).toString(16).padStart(2, "0"); + expect(traceparent).toBe(`00-${spanCtx.traceId}-${spanCtx.spanId}-${flagsHex}`); + + span.end(); + }); + + it("should set spanId on the protobuf trace context", () => { + const tracer = otel.trace.getTracer(TRACER_NAME); + const span = tracer.startSpan("pb-from-span-spanid-test"); + const spanCtx = span.spanContext(); + + const result = createPbTraceContextFromSpan(span); + expect(result).toBeDefined(); + expect(result!.getSpanid()).toBe(spanCtx.spanId); + + span.end(); + }); + + it("should include tracestate when present on the span context", () => { + // Create a span context with a tracestate + const traceState = otel.createTraceState("vendor1=value1,vendor2=value2"); + const parentSpanContext: otel.SpanContext = { + traceId: "aaaabbbbccccdddd1111222233334444", + spanId: "1122334455667788", + traceFlags: otel.TraceFlags.SAMPLED, + isRemote: true, + traceState, + }; + const parentContext = otel.trace.setSpanContext(otel.ROOT_CONTEXT, parentSpanContext); + const tracer = otel.trace.getTracer(TRACER_NAME); + const childSpan = tracer.startSpan("pb-from-span-tracestate-test", {}, parentContext); + + const result = createPbTraceContextFromSpan(childSpan); + expect(result).toBeDefined(); + + // The child span inherits the parent traceId but gets a new spanId. + // Tracestate should propagate from the parent. + const tracestateValue = result!.getTracestate()?.getValue(); + expect(tracestateValue).toBeDefined(); + expect(tracestateValue).toContain("vendor1=value1"); + expect(tracestateValue).toContain("vendor2=value2"); + + childSpan.end(); + }); + + it("should exclude tracestate when not present on the span context", () => { + const tracer = otel.trace.getTracer(TRACER_NAME); + const span = tracer.startSpan("pb-from-span-no-tracestate-test"); + + const result = createPbTraceContextFromSpan(span); + expect(result).toBeDefined(); + // No tracestate was set, so the protobuf field should be absent + expect(result!.getTracestate()).toBeUndefined(); + + span.end(); + }); + + it("should produce a result consistent with createPbTraceContext + extractTraceparentFromSpan", () => { + const tracer = otel.trace.getTracer(TRACER_NAME); + const span = tracer.startSpan("pb-from-span-roundtrip-test"); + + // Method under test: direct path + const direct = createPbTraceContextFromSpan(span); + + // Reference path: extract then create + const extracted = extractTraceparentFromSpan(span); + const reference = createPbTraceContext(extracted!.traceparent, extracted!.tracestate); + + expect(direct).toBeDefined(); + expect(direct!.getTraceparent()).toBe(reference.getTraceparent()); + expect(direct!.getSpanid()).toBe(reference.getSpanid()); + + span.end(); + }); +}); + describe("Trace Helper - setSpanError with unknown types", () => { it("should handle string error", () => { const tracer = otel.trace.getTracer(TRACER_NAME); diff --git a/packages/durabletask-js/test/worker-tracing.spec.ts b/packages/durabletask-js/test/worker-tracing.spec.ts index 8982df6..ba42e04 100644 --- a/packages/durabletask-js/test/worker-tracing.spec.ts +++ b/packages/durabletask-js/test/worker-tracing.spec.ts @@ -337,7 +337,7 @@ describe("Worker Tracing - Scheduled Actions Trace Context Injection", () => { // Verify the scheduling span was created const spans = exporter.getFinishedSpans(); const subOrchScheduleSpan = spans.find( - (s) => s.attributes[DurableTaskAttributes.TYPE] === TaskType.CREATE_ORCHESTRATION, + (s) => s.attributes[DurableTaskAttributes.TYPE] === TaskType.ORCHESTRATION && s.kind === otel.SpanKind.CLIENT, ); expect(subOrchScheduleSpan).toBeDefined(); expect(subOrchScheduleSpan!.kind).toBe(otel.SpanKind.CLIENT);