From 42065c2eaaa14591137c1d31c18841f97e3b1c36 Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 28 Jul 2025 22:11:21 +0200 Subject: [PATCH 1/7] enhance Cause.pretty with additional error fields. --- packages/effect/src/Inspectable.ts | 51 +++++++++++++++++++++++++-- packages/effect/src/internal/cause.ts | 10 +++++- packages/effect/test/Cause.test.ts | 23 ++++++++++-- 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/packages/effect/src/Inspectable.ts b/packages/effect/src/Inspectable.ts index 5f397c66357..fec95a4ec67 100644 --- a/packages/effect/src/Inspectable.ts +++ b/packages/effect/src/Inspectable.ts @@ -102,13 +102,58 @@ export const toStringUnknown = (u: unknown, whitespace: number | string | undefi } } +function stringifyWithDepth( + input: any, + depth?: number, + replacer?: (this: any, key: string, value: any) => any, + whitespace?: string | number +): string { + if (depth === undefined) { + return JSON.stringify(input, replacer, whitespace) + } + if (!input) { + return input + } + + const objectsAlreadySerialized = [input], + objDepth = [input] + + return JSON.stringify(input, function(key, value) { + if (replacer) { + value = replacer.call(this, key, value) + } + if (key) { + if (typeof value === "object") { + if (objectsAlreadySerialized.indexOf(value) !== -1) { + return undefined + } + + objectsAlreadySerialized.push(value) + } + + if (objDepth.indexOf(this) === -1) { + objDepth.push(this) + } else {while (objDepth[objDepth.length - 1] !== this) { + objDepth.pop() + }} + + if (objDepth.length > depth) { + return undefined + } + } + + return value + }, whitespace) +} + /** * @since 2.0.0 */ -export const stringifyCircular = (obj: unknown, whitespace?: number | string | undefined): string => { +export const stringifyCircular = (obj: unknown, whitespace?: number | string | undefined, depth?: number): string => { let cache: Array = [] - const retVal = JSON.stringify( + const retVal = stringifyWithDepth( obj, + depth, (_key, value) => typeof value === "object" && value !== null ? cache.includes(value) @@ -116,6 +161,8 @@ export const stringifyCircular = (obj: unknown, whitespace?: number | string | u : cache.push(value) && (redactableState.fiberRefs !== undefined && isRedactable(value) ? value[symbolRedactable](redactableState.fiberRefs) : value) + : typeof value === "bigint" + ? value.toString() : value, whitespace ) diff --git a/packages/effect/src/internal/cause.ts b/packages/effect/src/internal/cause.ts index 881ef5b6d73..847893138e0 100644 --- a/packages/effect/src/internal/cause.ts +++ b/packages/effect/src/internal/cause.ts @@ -879,7 +879,15 @@ export const pretty = (cause: Cause.Cause, options?: { if (options?.renderErrorCause !== true || e.cause === undefined) { return e.stack } - return `${e.stack} {\n${renderErrorCause(e.cause as PrettyError, " ")}\n}` + const { cause, message: _, name: __, stack, ...rest } = e + const json = stringifyCircular(toJSON(rest), 2, 2) + return !cause && !!Object.keys(rest).length ? + stack : + `${stack} {${ + json.replace(/^[\t ]*"[^:\n\r]+(? { if (typeof window === "undefined") { // eslint-disable-next-line @typescript-eslint/no-require-imports const { inspect } = require("node:util") - assertInclude(inspect(ex), "Cause.test.ts:39") // <= reference to the line above + assertInclude(inspect(ex), "Cause.test.ts:40") // <= reference to the line above } }) }) @@ -1064,8 +1065,9 @@ describe("Cause", () => { describe("Die", () => { it("with span", () => { + const span = Effect.runSync(Effect.makeSpan("[myspan]")) const exit: any = Effect.die(new Error("my message", { cause: "my cause" })).pipe( - Effect.withSpan("[myspan]"), + Effect.provideService(Tracer.ParentSpan, span), Effect.exit, Effect.runSync ) @@ -1074,6 +1076,21 @@ describe("Cause", () => { deepStrictEqual(simplifyStackTrace(pretty), [ `Error: my message`, "at [myspan]", + "span: {", + "name: \"[myspan]\",", + "parent: {},", + "context: {},", + `startTime: "${span.startTime}",`, + "kind: \"internal\",", + "_tag: \"Span\",", + `spanId: "${span.spanId}",`, + `traceId: "${span.traceId}",`, + "sampled: true,", + "status: {},", + "attributes: {},", + "events: [],", + "links: []", + "},", "[cause]: Error: my cause" ]) }) From 45b41ec7a3a192f5fefe39d07521cb1219a449f3 Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 28 Jul 2025 23:15:44 +0200 Subject: [PATCH 2/7] add changeset --- .changeset/petite-doodles-help.md | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .changeset/petite-doodles-help.md diff --git a/.changeset/petite-doodles-help.md b/.changeset/petite-doodles-help.md new file mode 100644 index 00000000000..91f28a94710 --- /dev/null +++ b/.changeset/petite-doodles-help.md @@ -0,0 +1,49 @@ +--- +"effect": patch +--- + +improve: Enhance Cause.pretty output with additional error fields, similar to classic throw of Error. + +code: + +``` +class MyError extends Error { + testValue = 1 +} +console.log(Cause.pretty(Cause.die(new MyError("my message")), { renderErrorCause: true })) +``` + +before: + +``` +Error: my message + at /pj/effect/effect/packages/effect/test/Cause.test.ts:1081:51 + at file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:155:11 + at file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:752:26 + at file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:1897:20 + at new Promise () + at runWithTimeout (file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:1863:10) + at runTest (file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:1574:12) + at processTicksAndRejections (node:internal/process/task_queues:105:5) + at async Promise.all (index 0) + at runSuite (file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:1718:7) +``` + +after: + +``` +"Error: my message + at /pj/effect/effect/packages/effect/test/Cause.test.ts:1081:51 + at file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:155:11 + at file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:752:26 + at file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:1897:20 + at new Promise () + at runWithTimeout (file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:1863:10) + at runTest (file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:1574:12) + at processTicksAndRejections (node:internal/process/task_queues:105:5) + at async Promise.all (index 0) + at runSuite (file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:1718:7) +{ + testValue: 1 +} +``` From d3723f4de0baf88a38a9aa9747e3e31a43c529c4 Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 28 Jul 2025 23:15:58 +0200 Subject: [PATCH 3/7] fix --- packages/effect/test/Cause.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/effect/test/Cause.test.ts b/packages/effect/test/Cause.test.ts index e17afb8c25a..18b2fa1248a 100644 --- a/packages/effect/test/Cause.test.ts +++ b/packages/effect/test/Cause.test.ts @@ -1080,7 +1080,7 @@ describe("Cause", () => { "name: \"[myspan]\",", "parent: {},", "context: {},", - `startTime: "${span.startTime}",`, + `startTime: "${(span as any).startTime}",`, "kind: \"internal\",", "_tag: \"Span\",", `spanId: "${span.spanId}",`, From afc47830eb797f5307f4d31a42299012a57b8ab9 Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 28 Jul 2025 23:40:33 +0200 Subject: [PATCH 4/7] render nice --- packages/effect/src/internal/cause.ts | 43 +++++++++++++++++---------- packages/effect/test/Cause.test.ts | 10 +++++-- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/packages/effect/src/internal/cause.ts b/packages/effect/src/internal/cause.ts index 847893138e0..1576db030b1 100644 --- a/packages/effect/src/internal/cause.ts +++ b/packages/effect/src/internal/cause.ts @@ -876,30 +876,43 @@ export const pretty = (cause: Cause.Cause, options?: { return "All fibers interrupted without errors." } return prettyErrors(cause).map(function(e) { - if (options?.renderErrorCause !== true || e.cause === undefined) { + if (options?.renderErrorCause !== true) { return e.stack } - const { cause, message: _, name: __, stack, ...rest } = e - const json = stringifyCircular(toJSON(rest), 2, 2) - return !cause && !!Object.keys(rest).length ? - stack : - `${stack} {${ - json.replace(/^[\t ]*"[^:\n\r]+(? { - const lines = cause.stack!.split("\n") - let stack = `${prefix}[cause]: ${lines[0]}` +const renderErrorCause = (e: Cause.PrettyError, prefix: string) => { + const lines = e.stack!.split("\n") + let stack = `${prefix}${prefix === "" ? "" : "[cause]: "}${lines[0]}` for (let i = 1, len = lines.length; i < len; i++) { stack += `\n${prefix}${lines[i]}` } - if (cause.cause) { - stack += ` {\n${renderErrorCause(cause.cause as PrettyError, `${prefix} `)}\n${prefix}}` + const { cause, message: _, name: __, stack: ___, ...rest } = e + const hasRest = Object.keys(rest).filter((_) => (rest as any)[_] !== undefined).length > 0 + if (hasRest || cause) { + stack += ` {` + } + if (hasRest) { + const json = stringifyCircular(toJSON(rest), 2, 2) + const bareJson = json.replace(/^[\t ]*"[^:\n\r]+(? { Effect.runSync ) const cause = exit.cause - const pretty = Cause.pretty(cause, { renderErrorCause: true }) - deepStrictEqual(simplifyStackTrace(pretty), [ + class MyError extends Error { + testValue = 1 + } + const pretty = simplifyStackTrace(Cause.pretty(cause, { renderErrorCause: true })) + // const pretty = Cause.pretty(cause, { renderErrorCause: true }) + // const pretty = Cause.pretty(Cause.die(new MyError("my message")), { renderErrorCause: true }) + deepStrictEqual(pretty, [ `Error: my message`, "at [myspan]", "span: {", @@ -1090,7 +1095,6 @@ describe("Cause", () => { "attributes: {},", "events: [],", "links: []", - "},", "[cause]: Error: my cause" ]) }) From 35f4fa73021c11557ce5c6ed6a77a96823ae186d Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 28 Jul 2025 23:41:08 +0200 Subject: [PATCH 5/7] cleanup --- .changeset/petite-doodles-help.md | 5 ++--- packages/effect/test/Cause.test.ts | 5 ----- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.changeset/petite-doodles-help.md b/.changeset/petite-doodles-help.md index 91f28a94710..c9c24f62b19 100644 --- a/.changeset/petite-doodles-help.md +++ b/.changeset/petite-doodles-help.md @@ -42,8 +42,7 @@ after: at runTest (file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:1574:12) at processTicksAndRejections (node:internal/process/task_queues:105:5) at async Promise.all (index 0) - at runSuite (file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:1718:7) -{ + at runSuite (file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:1718:7) { testValue: 1 -} + } ``` diff --git a/packages/effect/test/Cause.test.ts b/packages/effect/test/Cause.test.ts index 699b89c279f..8d8d0bba467 100644 --- a/packages/effect/test/Cause.test.ts +++ b/packages/effect/test/Cause.test.ts @@ -1072,12 +1072,7 @@ describe("Cause", () => { Effect.runSync ) const cause = exit.cause - class MyError extends Error { - testValue = 1 - } const pretty = simplifyStackTrace(Cause.pretty(cause, { renderErrorCause: true })) - // const pretty = Cause.pretty(cause, { renderErrorCause: true }) - // const pretty = Cause.pretty(Cause.die(new MyError("my message")), { renderErrorCause: true }) deepStrictEqual(pretty, [ `Error: my message`, "at [myspan]", From e220fea8241125a53fc6e8737266a1fc6054e025 Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 28 Jul 2025 23:42:29 +0200 Subject: [PATCH 6/7] update changeset --- .changeset/petite-doodles-help.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/petite-doodles-help.md b/.changeset/petite-doodles-help.md index c9c24f62b19..9c458c62ca4 100644 --- a/.changeset/petite-doodles-help.md +++ b/.changeset/petite-doodles-help.md @@ -17,7 +17,7 @@ before: ``` Error: my message - at /pj/effect/effect/packages/effect/test/Cause.test.ts:1081:51 + at /pj/effect/effect/packages/effect/test/Cause.test.ts:1079:51 at file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:155:11 at file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:752:26 at file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:1897:20 @@ -32,8 +32,8 @@ Error: my message after: ``` -"Error: my message - at /pj/effect/effect/packages/effect/test/Cause.test.ts:1081:51 +Error: my message + at /pj/effect/effect/packages/effect/test/Cause.test.ts:1079:51 at file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:155:11 at file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:752:26 at file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:1897:20 From 6c4ef024ef0d9ab737d00369d8f93142f8ade887 Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 28 Jul 2025 23:48:12 +0200 Subject: [PATCH 7/7] update changeset to include bigint support --- .changeset/petite-doodles-help.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.changeset/petite-doodles-help.md b/.changeset/petite-doodles-help.md index 9c458c62ca4..5bb8bdfbe31 100644 --- a/.changeset/petite-doodles-help.md +++ b/.changeset/petite-doodles-help.md @@ -3,12 +3,13 @@ --- improve: Enhance Cause.pretty output with additional error fields, similar to classic throw of Error. +also allows for printing `bigint` values as string. code: ``` class MyError extends Error { - testValue = 1 + testValue = BigInt(1) } console.log(Cause.pretty(Cause.die(new MyError("my message")), { renderErrorCause: true })) ``` @@ -43,6 +44,6 @@ Error: my message at processTicksAndRejections (node:internal/process/task_queues:105:5) at async Promise.all (index 0) at runSuite (file:///pj/effect/effect/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:1718:7) { - testValue: 1 + testValue: "1" } ```