Skip to content

Commit

Permalink
include Error.cause stack in log output
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart committed Jul 7, 2024
1 parent 9f3e895 commit 5afefbd
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilly-berries-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

include Error.cause stack in log output
5 changes: 5 additions & 0 deletions .changeset/clever-parents-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

add renderErrorCause option to Cause.pretty
5 changes: 5 additions & 0 deletions .changeset/fast-dodos-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

set stackTraceLimit to 1 in PrettyError to address performance issues
4 changes: 3 additions & 1 deletion packages/effect/src/Cause.ts
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,9 @@ export const isUnknownException: (u: unknown) => u is UnknownException = core.is
* @since 2.0.0
* @category rendering
*/
export const pretty: <E>(cause: Cause<E>) => string = internal.pretty
export const pretty: <E>(cause: Cause<E>, options?: {
readonly renderErrorCause?: boolean | undefined
}) => string = internal.pretty

/**
* @since 3.2.0
Expand Down
31 changes: 26 additions & 5 deletions packages/effect/src/internal/cause.ts
Original file line number Diff line number Diff line change
Expand Up @@ -970,25 +970,46 @@ export const reduceWithContext = dual<
// -----------------------------------------------------------------------------

/** @internal */
export const pretty = <E>(cause: Cause.Cause<E>): string => {
export const pretty = <E>(cause: Cause.Cause<E>, options?: {
readonly renderErrorCause?: boolean | undefined
}): string => {
if (isInterruptedOnly(cause)) {
return "All fibers interrupted without errors."
}
return prettyErrors<E>(cause).map((e) => e.stack).join("\n")
return prettyErrors<E>(cause).map(function(e) {
if (options?.renderErrorCause !== true || e.cause === undefined) {
return e.stack
}
return `${e.stack} {\n${renderErrorCause(e.cause as PrettyError, " ")}\n}`
}).join("\n")
}

const renderErrorCause = (cause: PrettyError, prefix: string) => {
const lines = cause.stack!.split("\n")
let stack = `${prefix}[cause]: ${lines[0]}`
for (let i = 1, len = lines.length; i < len; i++) {
stack += `\n${prefix}${lines[i]}`
}
return stack
}

class PrettyError extends globalThis.Error implements Cause.PrettyError {
span: undefined | Span = undefined
constructor(originalError: unknown) {
const originalErrorIsObject = typeof originalError === "object" && originalError !== null
const prevLimit = Error.stackTraceLimit
Error.stackTraceLimit = 0
super(prettyErrorMessage(originalError))
Error.stackTraceLimit = 1
super(prettyErrorMessage(originalError), {
cause: originalErrorIsObject && "cause" in originalError && typeof originalError.cause !== "undefined"
? new PrettyError(originalError.cause)
: undefined
})
if (this.message === "") {
this.message = "An error has occurred"
}
Error.stackTraceLimit = prevLimit
this.name = originalError instanceof Error ? originalError.name : "Error"
if (typeof originalError === "object" && originalError !== null) {
if (originalErrorIsObject) {
if (spanSymbol in originalError) {
this.span = originalError[spanSymbol] as Span
}
Expand Down
19 changes: 8 additions & 11 deletions packages/effect/src/internal/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export const stringLogger: Logger.Logger<unknown, string> = makeLogger(

if (cause != null && cause._tag !== "Empty") {
output = output + " cause="
output = appendQuoted(Cause.pretty(cause), output)
output = appendQuoted(Cause.pretty(cause, { renderErrorCause: true }), output)
}

if (List.isCons(spans)) {
Expand Down Expand Up @@ -255,7 +255,7 @@ export const logfmtLogger = makeLogger<unknown, string>(

if (cause != null && cause._tag !== "Empty") {
output = output + " cause="
output = appendQuotedLogfmt(Cause.pretty(cause), output)
output = appendQuotedLogfmt(Cause.pretty(cause, { renderErrorCause: true }), output)
}

if (List.isCons(spans)) {
Expand Down Expand Up @@ -324,7 +324,7 @@ export const structuredLogger = makeLogger<unknown, {
message: messageArr.length === 1 ? structuredMessage(messageArr[0]) : messageArr.map(structuredMessage),
logLevel: logLevel.label,
timestamp: date.toISOString(),
cause: Cause.isEmpty(cause) ? undefined : Cause.pretty(cause),
cause: Cause.isEmpty(cause) ? undefined : Cause.pretty(cause, { renderErrorCause: true }),
annotations: annotationsObj,
spans: spansObj,
fiberId: _fiberId.threadName(fiberId)
Expand Down Expand Up @@ -463,14 +463,11 @@ export const prettyLogger = (options?: {
const params: Array<unknown> = []

if (!Cause.isEmpty(cause)) {
const errors = Cause.prettyErrors(cause)
for (let i = 0; i < errors.length; i++) {
if (isBrowser) {
console.error(errors[i].stack)
} else {
currentMessage += "\n%s"
params.push(errors[i].stack)
}
if (isBrowser) {
console.error(Cause.pretty(cause, { renderErrorCause: true }))
} else {
currentMessage += "\n%s"
params.push(Cause.pretty(cause, { renderErrorCause: true }))
}
}

Expand Down
10 changes: 10 additions & 0 deletions packages/effect/test/Effect/cause-rendering.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,14 @@ error
message
at`))
}))

it.effect("pretty includes error.cause with renderErrorCause: true", () =>
Effect.gen(function*() {
const cause = yield* Effect.fail(new Error("parent", { cause: new Error("child") })).pipe(
Effect.sandbox,
Effect.flip
)
const pretty = Cause.pretty(cause, { renderErrorCause: true })
assert.include(pretty, "[cause]: Error: child")
}))
})

0 comments on commit 5afefbd

Please sign in to comment.