Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions packages/durabletask-js/src/tracing/trace-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Comment on lines +697 to +700
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: errorMessage });
} else {
span.setStatus({ code: otel.SpanStatusCode.OK });
Expand Down
50 changes: 46 additions & 4 deletions packages/durabletask-js/test/tracing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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");
Expand Down
Loading