Repository navigation
Expand file tree
/
Copy patherrorReporter.ts
More file actions
89 lines (77 loc) · 3.24 KB
/
Copy patherrorReporter.ts
File metadata and controls
89 lines (77 loc) · 3.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import type { SeverityLevel } from '@sentry/core';
import { captureException, isObjectLike } from '@sentry/core';
import * as Effect from 'effect';
import type * as Cause from 'effect/Cause';
import type * as EffectErrorReporter from 'effect/ErrorReporter';
import type * as EffectLayer from 'effect/Layer';
import { empty as emptyLayer } from 'effect/Layer';
import type * as LogLevel from 'effect/LogLevel';
// `effect/ErrorReporter` only exists in Effect v4, so it is read off the main entry instead of being imported
// as a subpath. On Effect v3 the lookup yields `undefined` and no reporter is registered. The property is read
// through a separate binding because bundlers fail the build on `Effect.ErrorReporter` when the export is absent.
const effectExports = Effect as Record<string, unknown>;
const ErrorReporter = effectExports.ErrorReporter as typeof EffectErrorReporter | undefined;
const SEVERITY_TO_LEVEL: Record<LogLevel.Severity, SeverityLevel> = {
Fatal: 'fatal',
Error: 'error',
Warn: 'warning',
Info: 'info',
Debug: 'debug',
Trace: 'debug',
};
function getLevel(errorReporter: typeof EffectErrorReporter, error: unknown): SeverityLevel {
// Effect's `getSeverity` falls back to `Info` for unannotated errors, which would file plain failures as
// informational in Sentry. Only an explicit annotation changes the level.
if (isObjectLike(error) && errorReporter.severity in error) {
return SEVERITY_TO_LEVEL[errorReporter.getSeverity(error)];
}
return 'error';
}
function makeSentryErrorReporter(errorReporter: typeof EffectErrorReporter): EffectErrorReporter.ErrorReporter {
const reported = new WeakSet<object>();
return {
[errorReporter.TypeId]: errorReporter.TypeId,
report({ cause }: { readonly cause: Cause.Cause<unknown> }): void {
if (reported.has(cause)) {
return;
}
reported.add(cause);
for (const reason of cause.reasons) {
if (reason._tag === 'Interrupt') {
continue;
}
// The raw error is captured rather than Effect's pretty-printed copy so Sentry sees the original stack,
// `cause` chain and error class.
const error = reason._tag === 'Fail' ? reason.error : reason.defect;
if (isObjectLike(error)) {
if (reported.has(error)) {
continue;
}
reported.add(error);
}
if (errorReporter.isIgnored(error)) {
continue;
}
captureException(error, {
mechanism: { type: 'auto.function.effect.error_reporter', handled: false },
captureContext: {
level: getLevel(errorReporter, error),
extra: isObjectLike(error) ? { ...errorReporter.getAttributes(error) } : undefined,
},
});
}
},
};
}
/**
* Registers a Sentry `ErrorReporter` for `Effect.withErrorReporting`, `ErrorReporter.report` and the
* built-in HTTP and RPC reporting boundaries. Existing reporters are kept.
*
* Effect v3 has no `ErrorReporter` API, so the returned layer is empty there.
*/
export function makeSentryErrorReporterLayer(): EffectLayer.Layer<never, never, never> {
if (!ErrorReporter) {
return emptyLayer;
}
return ErrorReporter.layer([makeSentryErrorReporter(ErrorReporter)], { mergeWithExisting: true });
}