Skip to content

Commit ac77fe4

Browse files
[codex] Preserve relay trace error causes (#3377)
Co-authored-by: codex <codex@users.noreply.github.com>
1 parent 4fbc4f9 commit ac77fe4

2 files changed

Lines changed: 72 additions & 6 deletions

File tree

packages/shared/src/relayTracing.test.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import { describe, expect, it } from "@effect/vitest";
22
import * as Effect from "effect/Effect";
3+
import * as Layer from "effect/Layer";
34
import * as Option from "effect/Option";
45
import * as Tracer from "effect/Tracer";
6+
import { FetchHttpClient } from "effect/unstable/http";
7+
import { vi } from "vite-plus/test";
58

6-
import { RelayClientTracer, withRelayClientTracing } from "./relayTracing.ts";
9+
import {
10+
makeRelayClientTracingLayer,
11+
RelayClientTracer,
12+
withRelayClientTracing,
13+
} from "./relayTracing.ts";
714

815
function collectingTracer(spans: Array<string>): Tracer.Tracer {
916
return Tracer.make({
@@ -54,4 +61,44 @@ describe("withRelayClientTracing", () => {
5461
expect(userSpans).toEqual(["relay.operation"]);
5562
}),
5663
);
64+
65+
it.effect("preserves nested error causes in exported relay spans", () => {
66+
const fetchFn = vi.fn<typeof fetch>(async () => new Response(null, { status: 202 }));
67+
const httpClientLayer = FetchHttpClient.layer.pipe(
68+
Layer.provide(Layer.succeed(FetchHttpClient.Fetch, fetchFn)),
69+
);
70+
const tracingLayer = makeRelayClientTracingLayer(
71+
{
72+
tracesUrl: "https://api.axiom.test/v1/traces",
73+
tracesDataset: "relay-traces",
74+
tracesToken: "public-ingest-token",
75+
},
76+
{
77+
serviceName: "relay-test",
78+
runtime: "test",
79+
client: "test",
80+
},
81+
).pipe(Layer.provide(httpClientLayer));
82+
const rootCause = new Error("relay socket closed");
83+
const failure = new Error("relay request failed", { cause: rootCause });
84+
const tracedApplication = Layer.effectDiscard(
85+
Effect.fail(failure).pipe(
86+
Effect.withSpan("relay.failed-operation"),
87+
withRelayClientTracing,
88+
Effect.exit,
89+
),
90+
).pipe(Layer.provide(tracingLayer));
91+
92+
return Layer.build(tracedApplication).pipe(
93+
Effect.scoped,
94+
Effect.andThen(
95+
Effect.sync(() => {
96+
expect(fetchFn).toHaveBeenCalledOnce();
97+
const payload = new TextDecoder().decode(fetchFn.mock.calls[0]?.[1]?.body as Uint8Array);
98+
expect(payload).toContain("relay request failed");
99+
expect(payload).toContain("relay socket closed");
100+
}),
101+
),
102+
);
103+
});
57104
});

packages/shared/src/relayTracing.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,16 @@ export const withRelayClientTracing = <A, E, R>(
4141
),
4242
);
4343

44-
function traceSafeError(value: unknown): Error {
44+
function cleanTraceStack(error: Error): string {
45+
const stack = error.stack ?? `${error.name}: ${error.message}`;
46+
const lines = stack.split("\n");
47+
const effectFrameIndex = lines.findIndex(
48+
(line, index) => index > 0 && /(?:Generator\.next|~effect\/Effect)/.test(line),
49+
);
50+
return effectFrameIndex < 0 ? stack : lines.slice(0, effectFrameIndex).join("\n");
51+
}
52+
53+
function traceSafeError(value: unknown, seen = new WeakSet<object>()): Error {
4554
const message =
4655
value instanceof Error
4756
? value.message
@@ -51,12 +60,19 @@ function traceSafeError(value: unknown): Error {
5160
typeof value.message === "string"
5261
? value.message
5362
: String(value);
54-
const error = new Error(message);
63+
64+
let cause: Error | undefined;
65+
if (typeof value === "object" && value !== null && !seen.has(value)) {
66+
seen.add(value);
67+
if ("cause" in value && value.cause !== undefined) {
68+
cause = traceSafeError(value.cause, seen);
69+
}
70+
}
71+
72+
const error = new Error(message, cause ? { cause } : undefined);
5573
if (value instanceof Error) {
5674
error.name = value.name;
57-
if (value.stack !== undefined) {
58-
error.stack = value.stack;
59-
}
75+
error.stack = cleanTraceStack(value);
6076
} else if (
6177
typeof value === "object" &&
6278
value !== null &&
@@ -65,6 +81,9 @@ function traceSafeError(value: unknown): Error {
6581
) {
6682
error.name = value.name;
6783
}
84+
if (cause) {
85+
error.stack = `${error.stack ?? `${error.name}: ${error.message}`}\nCaused by: ${cause.stack ?? `${cause.name}: ${cause.message}`}`;
86+
}
6887
return error;
6988
}
7089

0 commit comments

Comments
 (0)