import * as Sentry from "@sentry/node" import { Cause, Effect, type LogLevel } from "effect-app" import { dropUndefined, LogLevelToSentry } from "effect-app/utils" import { getRC } from "./api/setupRequest.js" import { CauseException, tryToJson, tryToReport } from "./errors.js" import { InfraLogger } from "./logger.js" const tryCauseException = (cause: Cause.Cause, name: string): CauseException => { try { return new CauseException(cause, name) } catch { return new CauseException(Cause.die(new Error("Failed to create CauseException")), name) } } export function reportError(name: string) { return Effect.fnUntraced( function*( cause: Cause.Cause, extras?: Record, level: LogLevel.Severity = "Error" ) { if (Cause.hasInterruptsOnly(cause)) { yield* InfraLogger.logDebug("Interrupted").pipe(Effect.annotateLogs("extras", JSON.stringify(extras ?? {}))) return } const error = tryCauseException(cause, name) yield* reportSentry(error, extras, LogLevelToSentry(level)) yield* InfraLogger .logWithLevel(level, "Reporting error", cause) .pipe( Effect.annotateLogs(dropUndefined({ extras, error: tryToReport(error), cause: tryToJson(cause), __error_name__: name })), Effect.catchCause((cause) => InfraLogger.logWarning("Failed to log error", cause)), Effect.catchCause(() => InfraLogger.logFatal("Failed to log error cause")) ) return error }, (effect) => Effect.tapCause(effect, (cause) => InfraLogger.logError("Failed to report error", cause).pipe( Effect.tapCause(() => InfraLogger.logFatal("Failed to log error cause")) )) ) } function reportSentry( error: CauseException, extras: Record | undefined, level: Sentry.SeverityLevel = "error" ) { return getRC.pipe(Effect.map((context) => { const scope = new Sentry.Scope() scope.setLevel(level) if (context) scope.setContext("context", { ...context }) if (extras) scope.setContext("extras", extras) const squashed = Cause.squash(error.originalCause) scope.setContext("mainError", tryToJson(squashed)) scope.setContext("error", tryToReport(error)) scope.setContext("cause", tryToJson(error.originalCause)) Sentry.captureException(error, scope) })) } export function logError(name: string) { return Effect.fnUntraced( function*(cause: Cause.Cause, extras?: Record) { if (Cause.hasInterruptsOnly(cause)) { yield* InfraLogger.logDebug("Interrupted").pipe(Effect.annotateLogs(dropUndefined({ extras }))) return } yield* InfraLogger .logWarning("Logging error", cause) .pipe(Effect.annotateLogs(dropUndefined({ extras, cause: tryToJson(cause), __error_name__: name }))) }, (effect) => Effect.tapCause(effect, () => InfraLogger.logFatal("Failed to log error cause")) ) } export const reportMessage = Effect.fnUntraced(function*(message: string, extras?: Record) { const context = yield* getRC const scope = new Sentry.Scope() if (context) scope.setContext("context", { ...context }) if (extras) scope.setContext("extras", extras) Sentry.captureMessage(message, scope) console.warn(message, extras) })