diff --git a/packages/durabletask-js/src/tracing/trace-helper.ts b/packages/durabletask-js/src/tracing/trace-helper.ts index aecbb46..01e596a 100644 --- a/packages/durabletask-js/src/tracing/trace-helper.ts +++ b/packages/durabletask-js/src/tracing/trace-helper.ts @@ -690,9 +690,14 @@ export function setOrchestrationStatusFromActions( span.setAttribute(DurableTaskAttributes.TASK_STATUS, statusName); } - // Match .NET: set span error status when orchestration completes with Failed + // Match .NET: set span error status when orchestration completes with Failed. + // Read error message from failureDetails (where setFailed() stores it), + // falling back to result for backwards compatibility. if (status === pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED) { - const errorMessage = completeAction.getResult()?.getValue() ?? "Orchestration failed"; + const errorMessage = + completeAction.getFailuredetails()?.getErrormessage() || + completeAction.getResult()?.getValue() || + "Orchestration failed"; span.setStatus({ code: otel.SpanStatusCode.ERROR, message: errorMessage }); } else { span.setStatus({ code: otel.SpanStatusCode.OK }); diff --git a/packages/durabletask-js/test/tracing.spec.ts b/packages/durabletask-js/test/tracing.spec.ts index 4e2dfa0..c5d447f 100644 --- a/packages/durabletask-js/test/tracing.spec.ts +++ b/packages/durabletask-js/test/tracing.spec.ts @@ -1124,15 +1124,17 @@ describe("Trace Helper - setOrchestrationStatusFromActions", () => { expect(spans[0].status.code).toBe(otel.SpanStatusCode.OK); }); - it("should set status attribute for failed orchestration and ERROR span status", () => { + it("should set status attribute for failed orchestration and ERROR span status with failureDetails", () => { const tracer = otel.trace.getTracer(TRACER_NAME); const span = tracer.startSpan("orch-status-failed"); + // Match the real runtime path: setFailed() sets failureDetails but NOT result const completeAction = new pb.CompleteOrchestrationAction(); completeAction.setOrchestrationstatus(pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED); - const resultValue = new StringValue(); - resultValue.setValue("User code threw an error"); - completeAction.setResult(resultValue); + const failureDetails = new pb.TaskFailureDetails(); + failureDetails.setErrormessage("User code threw an error"); + failureDetails.setErrortype("Error"); + completeAction.setFailuredetails(failureDetails); const action = new pb.OrchestratorAction(); action.setCompleteorchestration(completeAction); @@ -1146,6 +1148,46 @@ describe("Trace Helper - setOrchestrationStatusFromActions", () => { expect(spans[0].status.message).toBe("User code threw an error"); }); + it("should fall back to result field for failed orchestration error message", () => { + const tracer = otel.trace.getTracer(TRACER_NAME); + const span = tracer.startSpan("orch-status-failed-result-fallback"); + + // Edge case: no failureDetails, but result is set (backwards compatibility) + const completeAction = new pb.CompleteOrchestrationAction(); + completeAction.setOrchestrationstatus(pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED); + const resultValue = new StringValue(); + resultValue.setValue("Legacy error message"); + completeAction.setResult(resultValue); + + const action = new pb.OrchestratorAction(); + action.setCompleteorchestration(completeAction); + + setOrchestrationStatusFromActions(span, [action]); + span.end(); + + const spans = exporter.getFinishedSpans(); + expect(spans[0].status.code).toBe(otel.SpanStatusCode.ERROR); + expect(spans[0].status.message).toBe("Legacy error message"); + }); + + it("should use default error message when neither failureDetails nor result is set", () => { + const tracer = otel.trace.getTracer(TRACER_NAME); + const span = tracer.startSpan("orch-status-failed-no-details"); + + const completeAction = new pb.CompleteOrchestrationAction(); + completeAction.setOrchestrationstatus(pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED); + + const action = new pb.OrchestratorAction(); + action.setCompleteorchestration(completeAction); + + setOrchestrationStatusFromActions(span, [action]); + span.end(); + + const spans = exporter.getFinishedSpans(); + expect(spans[0].status.code).toBe(otel.SpanStatusCode.ERROR); + expect(spans[0].status.message).toBe("Orchestration failed"); + }); + it("should not set status when no completion action present", () => { const tracer = otel.trace.getTracer(TRACER_NAME); const span = tracer.startSpan("orch-status-none");