Skip to content

Commit

Permalink
improve error report
Browse files Browse the repository at this point in the history
  • Loading branch information
patroza committed Oct 18, 2024
1 parent 5b3e01d commit 4a814f5
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 51 deletions.
8 changes: 8 additions & 0 deletions .changeset/tender-deers-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@effect-app/infra-adapters": patch
"effect-app": patch
"@effect-app/infra": patch
"@effect-app/vue": patch
---

improve error report
2 changes: 1 addition & 1 deletion packages/infra-adapters/src/ServiceBus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export function subscribe<RMsg, RErr>(hndlr: MessageHandlers<RMsg, RErr>, sessio
if (Exit.isSuccess(exit)) {
resolve(exit.value)
} else {
reject(Cause.pretty(exit.cause))
reject(Cause.pretty(exit.cause, { renderErrorCause: true }))
}
})
)
Expand Down
25 changes: 4 additions & 21 deletions packages/infra/src/errorReporter.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,15 @@
import { dropUndefined } from "@effect-app/core/utils"
import * as Sentry from "@sentry/node"
import { Cause, Effect, Option } from "effect-app"
import { annotateSpanWithError, CauseException, ErrorReported } from "./errors.js"
import { CauseException, ErrorReported, tryToJson } from "./errors.js"
import { InfraLogger } from "./logger.js"
import { RequestContextContainer } from "./services/RequestContextContainer.js"

const tryToJson = <T>(error: CauseException<T>) => {
try {
return error.toJSON()
} catch {
try {
return error.toString()
} catch (err) {
try {
return `Failed to convert error: ${err}`
} catch {
return `Failed to convert error: unknown failure`
}
}
}
}

export function reportError(
name: string
) {
return (cause: Cause<unknown>, extras?: Record<string, unknown>) =>
Effect.gen(function*() {
yield* annotateSpanWithError(cause, name)
if (Cause.isInterrupted(cause)) {
yield* InfraLogger.logDebug("Interrupted").pipe(Effect.annotateLogs("extras", JSON.stringify(extras ?? {})))
return
Expand All @@ -39,7 +22,7 @@ export function reportError(
.pipe(
Effect.annotateLogs(dropUndefined({
extras,
__cause__: tryToJson(error),
cause: tryToJson(error),
__error_name__: name
})),
Effect.catchAllCause((cause) => InfraLogger.logError("Failed to log error", cause)),
Expand All @@ -61,6 +44,7 @@ function reportSentry(
if (context) scope.setContext("context", context as unknown as Record<string, unknown>)
if (extras) scope.setContext("extras", extras)
scope.setContext("error", tryToJson(error) as any)
scope.setContext("cause", tryToJson(error.originalCause) as any)
Sentry.captureException(error, scope)
}))
}
Expand All @@ -74,13 +58,12 @@ export function logError<E>(
yield* InfraLogger.logDebug("Interrupted").pipe(Effect.annotateLogs(dropUndefined({ extras })))
return
}
const error = new CauseException(cause, name)
yield* InfraLogger
.logWarning("Logging error", cause)
.pipe(
Effect.annotateLogs(dropUndefined({
extras,
__cause__: tryToJson(error),
cause: tryToJson(cause),
__error_name__: name
})),
Effect.catchAllCause((cause) => InfraLogger.logError("Failed to log error", cause)),
Expand Down
2 changes: 1 addition & 1 deletion packages/infra/src/logger/jsonLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const jsonLogger = Logger.make<unknown, void>(
fiber: FiberId.threadName(fiberId),
message,
request: Option.getOrUndefined(c),
cause: cause != null && cause != Cause.empty ? Cause.pretty(cause) : undefined,
cause: cause !== null && cause !== Cause.empty ? Cause.pretty(cause, { renderErrorCause: true }) : undefined,
spans: List.map(spans, (_) => ({ label: _.label, timing: nowMillis - _.startTime })).pipe(List.toArray),
annotations: HashMap.size(annotations) > 0
? [...annotations].reduce((prev, [k, v]) => {
Expand Down
36 changes: 19 additions & 17 deletions packages/prelude/src/client/errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import { TaggedError } from "effect-app/schema"
import { makeFiberFailure } from "effect/Runtime"
import { Cause, Effect, Predicate, S } from "../lib.js"
import { Cause, S } from "../lib.js"

export const tryToJson = (error: { toJSON(): unknown; toString(): string }) => {
try {
return error.toJSON()
} catch {
try {
return error.toString()
} catch (err) {
try {
return `Failed to convert error: ${err}`
} catch {
return `Failed to convert error: unknown failure`
}
}
}
}

// eslint-disable-next-line unused-imports/no-unused-vars
// @ts-expect-error type not used
Expand Down Expand Up @@ -149,30 +165,16 @@ export class CauseException<E> extends Error {
return {
_tag: this._tag,
name: this.name,
message: this.message,
pretty: this.toString(),
cause: this.originalCause.toJSON()
message: this.message
}
}

[Symbol.for("nodejs.util.inspect.custom")]() {
return this.toJSON()
}
override toString() {
return `[${this._tag}] ` + Cause.pretty(this.originalCause)
return `[${this._tag}] ` + Cause.pretty(this.originalCause, { renderErrorCause: true })
}

[ErrorReported] = false
}

export const annotateSpanWithError = (cause: Cause<unknown>, name?: string) =>
Effect.annotateCurrentSpan({
"exception.escaped": true,
"exception.message": "Reported error for " + (name ?? cause._tag),
"exception.stacktrace": Cause.pretty(cause),
"exception.type": Cause.squashWith(
cause,
(_) => Predicate.hasProperty(_, "_tag") ? _._tag : Predicate.hasProperty(_, "name") ? _.name : `${_}`
),
"error.type": cause._tag
})
23 changes: 12 additions & 11 deletions packages/vue/src/errorReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { dropUndefined } from "@effect-app/core/utils"
import * as Sentry from "@sentry/browser"
import { Cause, Effect } from "effect"
import { annotateSpanWithError, CauseException, ErrorReported } from "effect-app/client/errors"
import { Cause, Effect } from "effect-app"
import { CauseException, ErrorReported, tryToJson } from "effect-app/client/errors"

export function reportError(
name: string
) {
return (cause: Cause.Cause<unknown>, extras?: Record<string, unknown>) =>
Effect.gen(function*() {
yield* annotateSpanWithError(cause, name)
if (Cause.isInterrupted(cause)) {
yield* Effect.logDebug("Interrupted").pipe(Effect.annotateLogs("extras", JSON.stringify(extras ?? {})))
return Cause.squash(cause)
Expand All @@ -22,7 +21,7 @@ export function reportError(
.logError("Reporting error", cause)
.pipe(Effect.annotateLogs(dropUndefined({
extras,
__cause__: error.toJSON(),
cause: tryToJson(cause),
__error_name__: name
})))

Expand All @@ -38,7 +37,8 @@ function reportSentry(
return Effect.sync(() => {
const scope = new Sentry.Scope()
if (extras) scope.setContext("extras", extras)
scope.setContext("error", error.toJSON() as any)
scope.setContext("error", tryToJson(error) as any)
scope.setContext("cause", tryToJson(error.originalCause) as any)
Sentry.captureException(error, scope)
})
}
Expand All @@ -52,14 +52,15 @@ export function logError<E>(
yield* Effect.logDebug("Interrupted").pipe(Effect.annotateLogs(dropUndefined({ extras })))
return
}
const error = new CauseException(cause, name)
yield* Effect
.logWarning("Logging error", cause)
.pipe(Effect.annotateLogs(dropUndefined({
extras,
__cause__: error.toJSON(),
__error_name__: name
})))
.pipe(
Effect.annotateLogs(dropUndefined({
extras,
cause: tryToJson(cause),
__error_name__: name
}))
)
})
}

Expand Down

0 comments on commit 4a814f5

Please sign in to comment.