better-effect 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +599 -30
- package/dist/adapters/iti.d.mts +2 -3
- package/dist/adapters/iti.d.mts.map +1 -1
- package/dist/adapters/iti.mjs +2 -2
- package/dist/bun.d.mts +95 -0
- package/dist/bun.d.mts.map +1 -0
- package/dist/bun.mjs +177 -0
- package/dist/bun.mjs.map +1 -0
- package/dist/{context-BUEf1qjL.mjs → context-DSGSqcJj.mjs} +14 -3
- package/dist/context-DSGSqcJj.mjs.map +1 -0
- package/dist/current-request-Bc1YHL0w.mjs +21 -0
- package/dist/current-request-Bc1YHL0w.mjs.map +1 -0
- package/dist/current-request-Dm-QQ3Xr.d.mts +22 -0
- package/dist/current-request-Dm-QQ3Xr.d.mts.map +1 -0
- package/dist/{effect-CK4YZaX0.mjs → effect-W0IQXY8P.mjs} +21 -2
- package/dist/effect-W0IQXY8P.mjs.map +1 -0
- package/dist/{errors-BKcsQ0ip.d.mts → errors-CpHMzPiP.d.mts} +6 -2
- package/dist/errors-CpHMzPiP.d.mts.map +1 -0
- package/dist/hono.d.mts +65 -27
- package/dist/hono.d.mts.map +1 -1
- package/dist/hono.mjs +177 -53
- package/dist/hono.mjs.map +1 -1
- package/dist/{index-CX6iAoCB.d.mts → index-3Bkl-sxS.d.mts} +7 -2
- package/dist/{index-CX6iAoCB.d.mts.map → index-3Bkl-sxS.d.mts.map} +1 -1
- package/dist/{index-B1nH4luq.d.mts → index-C2ed9Ilq.d.mts} +32 -21
- package/dist/index-C2ed9Ilq.d.mts.map +1 -0
- package/dist/{index-GYJ4qm9K.d.mts → index-ClNndn2-.d.mts} +93 -62
- package/dist/index-ClNndn2-.d.mts.map +1 -0
- package/dist/{index-BMdU_Z72.d.mts → index-Cy1VsU-u.d.mts} +2 -3
- package/dist/index-Cy1VsU-u.d.mts.map +1 -0
- package/dist/index.d.mts +6 -7
- package/dist/index.mjs +8 -8
- package/dist/{internal-identity-CbXoZIfw.mjs → internal-identity-BuHBtP0k.mjs} +2 -2
- package/dist/{internal-identity-CbXoZIfw.mjs.map → internal-identity-BuHBtP0k.mjs.map} +1 -1
- package/dist/layer-2kjHVv4N.mjs +343 -0
- package/dist/layer-2kjHVv4N.mjs.map +1 -0
- package/dist/{outcome-CB43bnFW.d.mts → layer-C2OaqTmB.d.mts} +683 -138
- package/dist/layer-C2OaqTmB.d.mts.map +1 -0
- package/dist/next.d.mts +192 -0
- package/dist/next.d.mts.map +1 -0
- package/dist/next.mjs +185 -0
- package/dist/next.mjs.map +1 -0
- package/dist/{node-context-DWsCUBRa.d.mts → node-context-1YEjrU9g.d.mts} +2 -2
- package/dist/{node-context-DWsCUBRa.d.mts.map → node-context-1YEjrU9g.d.mts.map} +1 -1
- package/dist/{node-context-lggRF4bR.mjs → node-context-g3bCcY9t.mjs} +2 -2
- package/dist/{node-context-lggRF4bR.mjs.map → node-context-g3bCcY9t.mjs.map} +1 -1
- package/dist/node.d.mts +11 -6
- package/dist/node.d.mts.map +1 -1
- package/dist/node.mjs +156 -10
- package/dist/node.mjs.map +1 -1
- package/dist/opentelemetry.d.mts +112 -0
- package/dist/opentelemetry.d.mts.map +1 -0
- package/dist/opentelemetry.mjs +871 -0
- package/dist/opentelemetry.mjs.map +1 -0
- package/dist/{registration-drnZk0SY.mjs → registration-Bwe2GbVu.mjs} +8 -2
- package/dist/registration-Bwe2GbVu.mjs.map +1 -0
- package/dist/runtime/explicit.d.mts +2 -2
- package/dist/runtime/explicit.mjs +1 -1
- package/dist/runtime/node.d.mts +3 -3
- package/dist/runtime/node.mjs +2 -2
- package/dist/runtime-qpcVwaz_.mjs +1680 -0
- package/dist/runtime-qpcVwaz_.mjs.map +1 -0
- package/dist/service-CO9VJQYa.mjs +74 -0
- package/dist/service-CO9VJQYa.mjs.map +1 -0
- package/dist/signal-5Rg9bUUe.mjs +52 -0
- package/dist/signal-5Rg9bUUe.mjs.map +1 -0
- package/dist/{standard-services-GBZ32tll.mjs → standard-services-B-iMM_W7.mjs} +39 -19
- package/dist/standard-services-B-iMM_W7.mjs.map +1 -0
- package/dist/standard-services.d.mts +4 -3
- package/dist/standard-services.mjs +3 -2
- package/dist/testing.d.mts +25 -6
- package/dist/testing.d.mts.map +1 -1
- package/dist/testing.mjs +51 -7
- package/dist/testing.mjs.map +1 -1
- package/dist/types-BFTqCCcw.d.mts +81 -0
- package/dist/types-BFTqCCcw.d.mts.map +1 -0
- package/dist/web-effect-B9hp9jIe.mjs +380 -0
- package/dist/web-effect-B9hp9jIe.mjs.map +1 -0
- package/dist/web.d.mts +56 -0
- package/dist/web.d.mts.map +1 -0
- package/dist/web.mjs +2 -0
- package/package.json +36 -11
- package/dist/context-BUEf1qjL.mjs.map +0 -1
- package/dist/effect-CK4YZaX0.mjs.map +0 -1
- package/dist/errors-BKcsQ0ip.d.mts.map +0 -1
- package/dist/index-B1nH4luq.d.mts.map +0 -1
- package/dist/index-BMdU_Z72.d.mts.map +0 -1
- package/dist/index-GYJ4qm9K.d.mts.map +0 -1
- package/dist/index-heuaRmXR.d.mts +0 -374
- package/dist/index-heuaRmXR.d.mts.map +0 -1
- package/dist/layer-D56fRdLt.mjs +0 -269
- package/dist/layer-D56fRdLt.mjs.map +0 -1
- package/dist/outcome-CB43bnFW.d.mts.map +0 -1
- package/dist/program-metadata-DhQCCirO.mjs +0 -260
- package/dist/program-metadata-DhQCCirO.mjs.map +0 -1
- package/dist/registration-drnZk0SY.mjs.map +0 -1
- package/dist/runtime-CBMrX8Wl.mjs +0 -776
- package/dist/runtime-CBMrX8Wl.mjs.map +0 -1
- package/dist/signal-D2UTcaaG.mjs +0 -105
- package/dist/signal-D2UTcaaG.mjs.map +0 -1
- package/dist/standard-services-GBZ32tll.mjs.map +0 -1
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
import { ROOT_CONTEXT, SpanStatusCode, context, trace } from "@opentelemetry/api";
|
|
2
|
+
//#region src/opentelemetry/lifecycle.ts
|
|
3
|
+
const readMonotonicTime$1 = () => {
|
|
4
|
+
try {
|
|
5
|
+
return performance.now();
|
|
6
|
+
} catch {
|
|
7
|
+
return Date.now();
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
const eventName$1 = (phase) => `better-effect.lifecycle.${phase}`;
|
|
11
|
+
const callSpan$2 = (operation) => {
|
|
12
|
+
try {
|
|
13
|
+
Promise.resolve(operation()).catch(() => {});
|
|
14
|
+
} catch {}
|
|
15
|
+
};
|
|
16
|
+
const finishSpan$1 = (span, status, details) => {
|
|
17
|
+
callSpan$2(() => span.setStatus({ code: status === "failure" ? SpanStatusCode.ERROR : SpanStatusCode.OK }));
|
|
18
|
+
if (details !== void 0) {
|
|
19
|
+
const message = details.message;
|
|
20
|
+
if (message !== void 0) callSpan$2(() => span.recordException(message));
|
|
21
|
+
if (Object.keys(details.attributes).length > 0) callSpan$2(() => span.addEvent("better-effect.failure", details.attributes));
|
|
22
|
+
}
|
|
23
|
+
callSpan$2(() => span.end());
|
|
24
|
+
};
|
|
25
|
+
const failureCause = (event) => {
|
|
26
|
+
if (event.error !== void 0) return event.error;
|
|
27
|
+
return event.outcome.status === "failure" ? event.outcome.cause : void 0;
|
|
28
|
+
};
|
|
29
|
+
const eventStatus = (event) => "error" in event && event.error !== void 0 ? "failure" : event.outcome.status;
|
|
30
|
+
/** Internal lifecycle state machine for the OpenTelemetry Runtime observer. */
|
|
31
|
+
var LifecycleTelemetry = class {
|
|
32
|
+
options;
|
|
33
|
+
states = /* @__PURE__ */ new Map();
|
|
34
|
+
disposed = false;
|
|
35
|
+
constructor(options) {
|
|
36
|
+
this.options = options;
|
|
37
|
+
}
|
|
38
|
+
onStart(event) {
|
|
39
|
+
if (this.disposed || this.options.mode === "off" || this.states.has(event.lifecycleId)) return;
|
|
40
|
+
const state = {
|
|
41
|
+
lifecycleId: event.lifecycleId,
|
|
42
|
+
startedAt: readMonotonicTime$1(),
|
|
43
|
+
parentContext: this.options.getParentContext(),
|
|
44
|
+
starting: true,
|
|
45
|
+
carrier: void 0,
|
|
46
|
+
activationSpan: void 0,
|
|
47
|
+
activationEnded: false,
|
|
48
|
+
activationReported: false,
|
|
49
|
+
pendingActivationEnd: void 0
|
|
50
|
+
};
|
|
51
|
+
this.states.set(event.lifecycleId, state);
|
|
52
|
+
const span = this.options.startSpan(this.options.mode === "events" ? "better-effect.lifecycle" : eventName$1("activate"), this.options.makeAttributes(event, "activate", void 0, void 0), state.parentContext);
|
|
53
|
+
if (!this.states.has(event.lifecycleId)) {
|
|
54
|
+
if (span !== void 0) callSpan$2(() => span.end());
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
state.starting = false;
|
|
58
|
+
if (this.options.mode === "events") {
|
|
59
|
+
state.carrier = span;
|
|
60
|
+
if (span !== void 0) callSpan$2(() => span.addEvent(eventName$1("activate"), this.options.makeAttributes(event, "activate", void 0, void 0)));
|
|
61
|
+
} else state.activationSpan = span;
|
|
62
|
+
const pendingEnd = state.pendingActivationEnd;
|
|
63
|
+
if (pendingEnd !== void 0) {
|
|
64
|
+
state.pendingActivationEnd = void 0;
|
|
65
|
+
this.onEnd(pendingEnd);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
onEnd(event) {
|
|
69
|
+
if (this.options.mode === "off") return;
|
|
70
|
+
const state = this.states.get(event.lifecycleId);
|
|
71
|
+
if (state === void 0 || state.activationReported) return;
|
|
72
|
+
const durationMs = Math.max(0, readMonotonicTime$1() - state.startedAt);
|
|
73
|
+
const status = eventStatus(event);
|
|
74
|
+
if (this.options.mode === "events") {
|
|
75
|
+
const span = state.carrier;
|
|
76
|
+
if (span === void 0) {
|
|
77
|
+
if (state.starting) {
|
|
78
|
+
state.pendingActivationEnd = event;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
state.activationReported = true;
|
|
82
|
+
if (status === "failure") this.states.delete(event.lifecycleId);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
state.activationReported = true;
|
|
86
|
+
state.activationEnded = true;
|
|
87
|
+
if (status === "failure") this.states.delete(event.lifecycleId);
|
|
88
|
+
callSpan$2(() => span.addEvent(eventName$1("activate"), this.options.makeAttributes(event, "activate", status, durationMs)));
|
|
89
|
+
if (status === "failure" && this.options.recordFailures) {
|
|
90
|
+
const details = this.options.getFailureDetails(failureCause(event));
|
|
91
|
+
if (details !== void 0) finishDetails(span, details);
|
|
92
|
+
}
|
|
93
|
+
if (status === "failure") {
|
|
94
|
+
callSpan$2(() => span.setStatus({ code: SpanStatusCode.ERROR }));
|
|
95
|
+
callSpan$2(() => span.end());
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const span = state.activationSpan;
|
|
100
|
+
if (span === void 0) {
|
|
101
|
+
if (state.starting) {
|
|
102
|
+
state.pendingActivationEnd = event;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
state.activationReported = true;
|
|
106
|
+
if (status === "failure") this.states.delete(event.lifecycleId);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
state.activationReported = true;
|
|
110
|
+
if (status === "failure") this.states.delete(event.lifecycleId);
|
|
111
|
+
state.activationEnded = true;
|
|
112
|
+
finishSpan$1(span, status, status === "failure" && this.options.recordFailures ? this.options.getFailureDetails(failureCause(event)) : void 0);
|
|
113
|
+
}
|
|
114
|
+
onRelease(event) {
|
|
115
|
+
if (this.options.mode === "off") return;
|
|
116
|
+
const state = this.states.get(event.lifecycleId);
|
|
117
|
+
if (state === void 0) return;
|
|
118
|
+
this.states.delete(event.lifecycleId);
|
|
119
|
+
const status = eventStatus(event);
|
|
120
|
+
const attributes = this.options.makeAttributes(event, "release", status, void 0);
|
|
121
|
+
const details = status === "failure" && this.options.recordFailures ? this.options.getFailureDetails(failureCause(event)) : void 0;
|
|
122
|
+
if (this.options.mode === "events") {
|
|
123
|
+
if (state.carrier === void 0) return;
|
|
124
|
+
callSpan$2(() => state.carrier.addEvent(eventName$1("release"), attributes));
|
|
125
|
+
if (details !== void 0) finishDetails(state.carrier, details);
|
|
126
|
+
finishSpan$1(state.carrier, status, void 0);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const span = this.options.startSpan(eventName$1("release"), attributes, state.parentContext);
|
|
130
|
+
if (span !== void 0) finishSpan$1(span, status, details);
|
|
131
|
+
}
|
|
132
|
+
dispose() {
|
|
133
|
+
if (this.disposed) return;
|
|
134
|
+
this.disposed = true;
|
|
135
|
+
const pending = [...this.states.values()];
|
|
136
|
+
this.states.clear();
|
|
137
|
+
for (const state of pending) {
|
|
138
|
+
if (state.carrier !== void 0) callSpan$2(() => state.carrier.end());
|
|
139
|
+
if (state.activationSpan !== void 0 && !state.activationEnded) callSpan$2(() => state.activationSpan.end());
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const finishDetails = (span, details) => {
|
|
144
|
+
const message = details.message;
|
|
145
|
+
if (message !== void 0) callSpan$2(() => span.recordException(message));
|
|
146
|
+
if (Object.keys(details.attributes).length > 0) callSpan$2(() => span.addEvent("better-effect.failure", details.attributes));
|
|
147
|
+
};
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/opentelemetry/shutdown.ts
|
|
150
|
+
const callSpan$1 = (operation) => {
|
|
151
|
+
try {
|
|
152
|
+
Promise.resolve(operation()).catch(() => {});
|
|
153
|
+
} catch {}
|
|
154
|
+
};
|
|
155
|
+
const readMonotonicTime = () => {
|
|
156
|
+
try {
|
|
157
|
+
return performance.now();
|
|
158
|
+
} catch {
|
|
159
|
+
return Date.now();
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const boundedDuration$1 = (value) => value !== void 0 && Number.isFinite(value) && value >= 0 ? Math.min(value, 2 ** 31 - 1) : void 0;
|
|
163
|
+
const phaseOf = (phase) => {
|
|
164
|
+
switch (phase) {
|
|
165
|
+
case "shutdown-requested": return "requested";
|
|
166
|
+
case "quiesce-start":
|
|
167
|
+
case "quiesce-end": return "quiesce";
|
|
168
|
+
case "drain-start":
|
|
169
|
+
case "drain-end": return "drain";
|
|
170
|
+
case "abort-active": return "abort";
|
|
171
|
+
case "release-start":
|
|
172
|
+
case "release-end": return "release";
|
|
173
|
+
case "shutdown-complete":
|
|
174
|
+
case "shutdown-failure": return "complete";
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const eventName = (phase) => `better-effect.shutdown.${phase}`;
|
|
178
|
+
const isStart = (phase) => phase === "quiesce-start" || phase === "drain-start" || phase === "release-start";
|
|
179
|
+
const isEnd = (phase) => phase === "quiesce-end" || phase === "drain-end" || phase === "release-end";
|
|
180
|
+
const isTerminal = (phase) => phase === "shutdown-complete" || phase === "shutdown-failure";
|
|
181
|
+
const finishSpan = (span, status, details) => {
|
|
182
|
+
callSpan$1(() => span.setStatus({ code: status === "failure" ? SpanStatusCode.ERROR : SpanStatusCode.OK }));
|
|
183
|
+
if (details !== void 0) {
|
|
184
|
+
const message = details.message;
|
|
185
|
+
if (message !== void 0) callSpan$1(() => span.recordException(message));
|
|
186
|
+
if (Object.keys(details.attributes).length > 0) callSpan$1(() => span.addEvent("better-effect.failure", details.attributes));
|
|
187
|
+
}
|
|
188
|
+
callSpan$1(() => span.end());
|
|
189
|
+
};
|
|
190
|
+
/** Internal shutdown state machine for the OpenTelemetry Runtime observer. */
|
|
191
|
+
var ShutdownTelemetry = class {
|
|
192
|
+
options;
|
|
193
|
+
states = /* @__PURE__ */ new Map();
|
|
194
|
+
disposed = false;
|
|
195
|
+
constructor(options) {
|
|
196
|
+
this.options = options;
|
|
197
|
+
}
|
|
198
|
+
onEvent(event) {
|
|
199
|
+
if (this.disposed || this.options.mode === "off") return;
|
|
200
|
+
if (event.phase === "shutdown-requested") {
|
|
201
|
+
this.start(event);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const state = this.states.get(event.reason);
|
|
205
|
+
if (state === void 0) return;
|
|
206
|
+
if (state.span === void 0 && state.starting) {
|
|
207
|
+
state.pendingEvents.push(event);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (state.span === void 0 && isTerminal(event.phase)) {
|
|
211
|
+
this.states.delete(event.reason);
|
|
212
|
+
state.pendingEvents.length = 0;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (state.span !== void 0) this.process(state, event);
|
|
216
|
+
}
|
|
217
|
+
start(event) {
|
|
218
|
+
if (this.states.has(event.reason)) return;
|
|
219
|
+
const state = {
|
|
220
|
+
reason: event.reason,
|
|
221
|
+
starting: true,
|
|
222
|
+
span: void 0,
|
|
223
|
+
phaseSpans: /* @__PURE__ */ new Map(),
|
|
224
|
+
phaseStartedAt: /* @__PURE__ */ new Map(),
|
|
225
|
+
pendingPhaseEnds: /* @__PURE__ */ new Map(),
|
|
226
|
+
pendingEvents: []
|
|
227
|
+
};
|
|
228
|
+
this.states.set(event.reason, state);
|
|
229
|
+
const span = this.options.startSpan("better-effect.shutdown", this.options.makeAttributes(event, "requested", void 0), this.options.rootContext);
|
|
230
|
+
if (!this.states.has(event.reason)) {
|
|
231
|
+
if (span !== void 0) callSpan$1(() => span.end());
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
state.starting = false;
|
|
235
|
+
state.span = span;
|
|
236
|
+
if (span !== void 0) this.addEvent(state, event);
|
|
237
|
+
const pendingEvents = state.pendingEvents.splice(0);
|
|
238
|
+
for (const pendingEvent of pendingEvents) this.process(state, pendingEvent);
|
|
239
|
+
}
|
|
240
|
+
process(state, event) {
|
|
241
|
+
const phase = phaseOf(event.phase);
|
|
242
|
+
if (isStart(event.phase)) {
|
|
243
|
+
this.startPhase(state, event, phase);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (isEnd(event.phase)) {
|
|
247
|
+
if (!state.phaseStartedAt.has(phase)) return;
|
|
248
|
+
if (this.options.mode === "spans" && !state.phaseSpans.has(phase)) {
|
|
249
|
+
state.pendingPhaseEnds.set(phase, event);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
this.finishPhase(state, event, phase);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (event.phase === "abort-active") {
|
|
256
|
+
if (state.phaseStartedAt.has("abort")) return;
|
|
257
|
+
state.phaseStartedAt.set("abort", readMonotonicTime());
|
|
258
|
+
if (this.options.mode === "events") {
|
|
259
|
+
this.addEvent(state, event);
|
|
260
|
+
state.phaseStartedAt.delete("abort");
|
|
261
|
+
} else {
|
|
262
|
+
const span = this.startPhaseSpan(state, event, "abort");
|
|
263
|
+
if (span !== void 0) {
|
|
264
|
+
state.phaseStartedAt.delete("abort");
|
|
265
|
+
finishSpan(span, "success", void 0);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (isTerminal(event.phase)) this.finish(state, event);
|
|
271
|
+
}
|
|
272
|
+
startPhase(state, event, phase) {
|
|
273
|
+
if (state.phaseStartedAt.has(phase)) return;
|
|
274
|
+
state.phaseStartedAt.set(phase, readMonotonicTime());
|
|
275
|
+
if (this.options.mode === "events") {
|
|
276
|
+
this.addEvent(state, event);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const span = this.startPhaseSpan(state, event, phase);
|
|
280
|
+
if (!this.states.has(state.reason)) {
|
|
281
|
+
if (span !== void 0) callSpan$1(() => span.end());
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (span !== void 0) {
|
|
285
|
+
state.phaseSpans.set(phase, span);
|
|
286
|
+
const pendingEnd = state.pendingPhaseEnds.get(phase);
|
|
287
|
+
if (pendingEnd !== void 0) {
|
|
288
|
+
state.pendingPhaseEnds.delete(phase);
|
|
289
|
+
this.finishPhase(state, pendingEnd, phase);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
startPhaseSpan(state, event, phase) {
|
|
294
|
+
let parent = this.options.rootContext;
|
|
295
|
+
if (state.span !== void 0) try {
|
|
296
|
+
parent = trace.setSpan(this.options.rootContext, state.span);
|
|
297
|
+
} catch {
|
|
298
|
+
parent = this.options.rootContext;
|
|
299
|
+
}
|
|
300
|
+
return this.options.startSpan(eventName(phase), this.options.makeAttributes(event, phase, void 0), parent);
|
|
301
|
+
}
|
|
302
|
+
addEvent(state, event, durationMsOverride) {
|
|
303
|
+
const span = state.span;
|
|
304
|
+
if (span === void 0) return;
|
|
305
|
+
const phase = phaseOf(event.phase);
|
|
306
|
+
const startedAt = state.phaseStartedAt.get(phase);
|
|
307
|
+
const durationMs = durationMsOverride ?? (startedAt === void 0 ? void 0 : Math.max(0, readMonotonicTime() - startedAt));
|
|
308
|
+
callSpan$1(() => span.addEvent(eventName(phase), this.options.makeAttributes(event, phase, durationMs)));
|
|
309
|
+
}
|
|
310
|
+
finishPhase(state, event, phase) {
|
|
311
|
+
const span = state.phaseSpans.get(phase);
|
|
312
|
+
const startedAt = state.phaseStartedAt.get(phase);
|
|
313
|
+
const durationMs = startedAt === void 0 ? event.durationMs : Math.max(0, readMonotonicTime() - startedAt);
|
|
314
|
+
state.phaseSpans.delete(phase);
|
|
315
|
+
state.phaseStartedAt.delete(phase);
|
|
316
|
+
state.pendingPhaseEnds.delete(phase);
|
|
317
|
+
if (this.options.mode === "events") {
|
|
318
|
+
this.addEvent(state, event, durationMs);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (span !== void 0) {
|
|
322
|
+
callSpan$1(() => span.setAttribute("better_effect.duration_ms", boundedDuration$1(durationMs) ?? 0));
|
|
323
|
+
finishSpan(span, "success", void 0);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
finish(state, event) {
|
|
327
|
+
if (this.states.get(state.reason) !== state) return;
|
|
328
|
+
this.states.delete(state.reason);
|
|
329
|
+
const status = event.phase === "shutdown-failure" ? "failure" : "success";
|
|
330
|
+
const details = status === "failure" && this.options.recordFailures ? this.options.getFailureDetails(event.error) : void 0;
|
|
331
|
+
for (const span of state.phaseSpans.values()) callSpan$1(() => span.end());
|
|
332
|
+
state.phaseSpans.clear();
|
|
333
|
+
state.phaseStartedAt.clear();
|
|
334
|
+
state.pendingPhaseEnds.clear();
|
|
335
|
+
state.pendingEvents.length = 0;
|
|
336
|
+
if (state.span === void 0) return;
|
|
337
|
+
this.addEvent(state, event);
|
|
338
|
+
finishSpan(state.span, status, details);
|
|
339
|
+
}
|
|
340
|
+
dispose() {
|
|
341
|
+
if (this.disposed) return;
|
|
342
|
+
this.disposed = true;
|
|
343
|
+
const pending = [...this.states.values()];
|
|
344
|
+
this.states.clear();
|
|
345
|
+
for (const state of pending) {
|
|
346
|
+
for (const span of state.phaseSpans.values()) callSpan$1(() => span.end());
|
|
347
|
+
if (state.span !== void 0) callSpan$1(() => state.span.end());
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/opentelemetry/index.ts
|
|
353
|
+
const DEFAULT_TRACER_NAME = "better-effect";
|
|
354
|
+
const DEFAULT_EXECUTION_SPAN_NAME = "better-effect.execution";
|
|
355
|
+
const DEFAULT_SERVICE_SPAN_NAME = "better-effect.service";
|
|
356
|
+
const DEFAULT_MAX_ATTRIBUTE_LENGTH = 256;
|
|
357
|
+
const DEFAULT_MAX_ATTRIBUTE_COUNT = 32;
|
|
358
|
+
const DEFAULT_MAX_RESOLUTION_PATH_LENGTH = 16;
|
|
359
|
+
const MAX_ATTRIBUTE_LENGTH = 4096;
|
|
360
|
+
const MAX_ATTRIBUTE_COUNT = 128;
|
|
361
|
+
const MAX_RESOLUTION_PATH_LENGTH = 128;
|
|
362
|
+
const MAX_ARRAY_LENGTH = 16;
|
|
363
|
+
const EXECUTION_ID_ATTRIBUTE = "better_effect.execution_id";
|
|
364
|
+
const PROGRAM_NAME_ATTRIBUTE = "better_effect.program_name";
|
|
365
|
+
const OUTCOME_ATTRIBUTE = "better_effect.outcome";
|
|
366
|
+
const SERVICE_TAG_ATTRIBUTE = "better_effect.service_tag";
|
|
367
|
+
const RESOLUTION_PATH_ATTRIBUTE = "better_effect.resolution_path";
|
|
368
|
+
const RESOLUTION_PATH_TRUNCATED_ATTRIBUTE = "better_effect.resolution_path_truncated";
|
|
369
|
+
const LIBRARY_ATTRIBUTE = "better_effect.library";
|
|
370
|
+
const LIFECYCLE_NAME_ATTRIBUTE = "better_effect.lifecycle_name";
|
|
371
|
+
const LIFECYCLE_PHASE_ATTRIBUTE = "better_effect.lifecycle_phase";
|
|
372
|
+
const SHUTDOWN_PHASE_ATTRIBUTE = "better_effect.shutdown_phase";
|
|
373
|
+
const SHUTDOWN_REASON_ATTRIBUTE = "better_effect.shutdown_reason";
|
|
374
|
+
const SHUTDOWN_SIGNAL_ATTRIBUTE = "better_effect.shutdown_signal";
|
|
375
|
+
const DURATION_ATTRIBUTE = "better_effect.duration_ms";
|
|
376
|
+
const FAILURE_EVENT_NAME = "better-effect.failure";
|
|
377
|
+
const serviceEventStatus = (event) => "error" in event && event.error !== void 0 ? "failure" : event.outcome.status;
|
|
378
|
+
const executionEventStatus = (event) => event.cleanupFailure !== void 0 ? "failure" : event.outcome.status;
|
|
379
|
+
const isStartedExecutionSpan = (execution) => execution.span !== void 0;
|
|
380
|
+
const isActiveExecutionSpan = (execution) => isStartedExecutionSpan(execution) && execution.executionContext !== void 0;
|
|
381
|
+
const isTelemetryMode = (value) => value === "off" || value === "events" || value === "spans";
|
|
382
|
+
const boundedDuration = (value) => value !== void 0 && Number.isFinite(value) && value >= 0 ? Math.min(value, 2 ** 31 - 1) : void 0;
|
|
383
|
+
const addOutcomeAttribute = (attributes, outcome, limit) => {
|
|
384
|
+
if (outcome !== void 0) addAttribute(attributes, OUTCOME_ATTRIBUTE, outcome, limit);
|
|
385
|
+
};
|
|
386
|
+
const addDurationAttribute = (attributes, durationMs, limit) => {
|
|
387
|
+
const duration = boundedDuration(durationMs);
|
|
388
|
+
if (duration !== void 0) addAttribute(attributes, DURATION_ATTRIBUTE, duration, limit);
|
|
389
|
+
};
|
|
390
|
+
const makeLifecycleAttributes = (event, phase, outcome, durationMs, limits) => {
|
|
391
|
+
const attributes = {};
|
|
392
|
+
const name = boundedNonEmptyString(event.name, limits.maxAttributeLength);
|
|
393
|
+
if (name !== void 0) addAttribute(attributes, LIFECYCLE_NAME_ATTRIBUTE, name, limits.maxAttributeCount);
|
|
394
|
+
addAttribute(attributes, LIFECYCLE_PHASE_ATTRIBUTE, phase, limits.maxAttributeCount);
|
|
395
|
+
addOutcomeAttribute(attributes, outcome, limits.maxAttributeCount);
|
|
396
|
+
addDurationAttribute(attributes, durationMs, limits.maxAttributeCount);
|
|
397
|
+
return attributes;
|
|
398
|
+
};
|
|
399
|
+
const makeShutdownAttributes = (event, phase, durationMs, limits) => {
|
|
400
|
+
const attributes = {};
|
|
401
|
+
const reason = event.reason;
|
|
402
|
+
addAttribute(attributes, SHUTDOWN_PHASE_ATTRIBUTE, phase, limits.maxAttributeCount);
|
|
403
|
+
addAttribute(attributes, SHUTDOWN_REASON_ATTRIBUTE, reason.kind, limits.maxAttributeCount);
|
|
404
|
+
if (reason.signal === "SIGINT" || reason.signal === "SIGTERM") addAttribute(attributes, SHUTDOWN_SIGNAL_ATTRIBUTE, reason.signal, limits.maxAttributeCount);
|
|
405
|
+
if (event.activeExecutions !== void 0 && Number.isSafeInteger(event.activeExecutions)) addAttribute(attributes, "better_effect.active_executions", Math.max(0, event.activeExecutions), limits.maxAttributeCount);
|
|
406
|
+
addDurationAttribute(attributes, durationMs ?? event.durationMs, limits.maxAttributeCount);
|
|
407
|
+
if (event.phase === "shutdown-complete") addOutcomeAttribute(attributes, "success", limits.maxAttributeCount);
|
|
408
|
+
else if (event.phase === "shutdown-failure") addOutcomeAttribute(attributes, "failure", limits.maxAttributeCount);
|
|
409
|
+
return attributes;
|
|
410
|
+
};
|
|
411
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
412
|
+
const isSpan = (value) => isRecord(value) && typeof value.end === "function";
|
|
413
|
+
const isTracer = (value) => isRecord(value) && typeof value.startSpan === "function";
|
|
414
|
+
const boundedString = (value, limit) => typeof value === "string" ? value.slice(0, limit) : void 0;
|
|
415
|
+
const boundedNonEmptyString = (value, limit) => {
|
|
416
|
+
const text = boundedString(value, limit);
|
|
417
|
+
return text === void 0 || text.length === 0 ? void 0 : text;
|
|
418
|
+
};
|
|
419
|
+
const normalizeLimit = (value, fallback, maximum, name) => {
|
|
420
|
+
if (value === void 0) return fallback;
|
|
421
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > maximum) throw new RangeError(`${name} must be an integer between 1 and ${maximum}`);
|
|
422
|
+
return value;
|
|
423
|
+
};
|
|
424
|
+
const normalizeLimits = (options) => ({
|
|
425
|
+
maxAttributeLength: normalizeLimit(options.maxAttributeLength, DEFAULT_MAX_ATTRIBUTE_LENGTH, MAX_ATTRIBUTE_LENGTH, "maxAttributeLength"),
|
|
426
|
+
maxAttributeCount: normalizeLimit(options.maxAttributeCount, DEFAULT_MAX_ATTRIBUTE_COUNT, MAX_ATTRIBUTE_COUNT, "maxAttributeCount"),
|
|
427
|
+
maxResolutionPathLength: normalizeLimit(options.maxResolutionPathLength, DEFAULT_MAX_RESOLUTION_PATH_LENGTH, MAX_RESOLUTION_PATH_LENGTH, "maxResolutionPathLength")
|
|
428
|
+
});
|
|
429
|
+
const normalizeStringArray = (values, limit) => {
|
|
430
|
+
const normalized = [];
|
|
431
|
+
const length = Math.min(values.length, MAX_ARRAY_LENGTH);
|
|
432
|
+
for (let index = 0; index < length; index += 1) {
|
|
433
|
+
const value = values[index];
|
|
434
|
+
if (value === void 0 || value === null) continue;
|
|
435
|
+
if (typeof value !== "string") return;
|
|
436
|
+
normalized.push(value.slice(0, limit));
|
|
437
|
+
}
|
|
438
|
+
return normalized;
|
|
439
|
+
};
|
|
440
|
+
const normalizeNumberArray = (values) => {
|
|
441
|
+
const normalized = [];
|
|
442
|
+
const length = Math.min(values.length, MAX_ARRAY_LENGTH);
|
|
443
|
+
normalized.length = length;
|
|
444
|
+
for (let index = 0; index < length; index += 1) {
|
|
445
|
+
if (!(index in values)) continue;
|
|
446
|
+
const value = values[index];
|
|
447
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return;
|
|
448
|
+
normalized[index] = value;
|
|
449
|
+
}
|
|
450
|
+
return normalized;
|
|
451
|
+
};
|
|
452
|
+
const normalizeBooleanArray = (values) => {
|
|
453
|
+
const normalized = [];
|
|
454
|
+
const length = Math.min(values.length, MAX_ARRAY_LENGTH);
|
|
455
|
+
normalized.length = length;
|
|
456
|
+
for (let index = 0; index < length; index += 1) {
|
|
457
|
+
if (!(index in values)) continue;
|
|
458
|
+
const value = values[index];
|
|
459
|
+
if (typeof value !== "boolean") return;
|
|
460
|
+
normalized[index] = value;
|
|
461
|
+
}
|
|
462
|
+
return normalized;
|
|
463
|
+
};
|
|
464
|
+
const normalizeAttributeValue = (value, maxAttributeLength) => {
|
|
465
|
+
if (typeof value === "string") return value.slice(0, maxAttributeLength);
|
|
466
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
467
|
+
if (typeof value === "boolean") return value;
|
|
468
|
+
if (!Array.isArray(value) || value.length === 0) return;
|
|
469
|
+
return normalizeStringArray(value, maxAttributeLength) ?? normalizeNumberArray(value) ?? normalizeBooleanArray(value);
|
|
470
|
+
};
|
|
471
|
+
const normalizeAttributes = (value, limits) => {
|
|
472
|
+
const result = {};
|
|
473
|
+
let inspected = 0;
|
|
474
|
+
let normalizedCount = 0;
|
|
475
|
+
if (!isRecord(value)) return result;
|
|
476
|
+
for (const key in value) {
|
|
477
|
+
if (inspected >= limits.maxAttributeCount) break;
|
|
478
|
+
inspected += 1;
|
|
479
|
+
if (!Object.prototype.propertyIsEnumerable.call(value, key)) continue;
|
|
480
|
+
if (key.length === 0 || key.length > limits.maxAttributeLength) continue;
|
|
481
|
+
let normalized;
|
|
482
|
+
try {
|
|
483
|
+
normalized = normalizeAttributeValue(value[key], limits.maxAttributeLength);
|
|
484
|
+
} catch {
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (normalized !== void 0) {
|
|
488
|
+
result[key] = normalized;
|
|
489
|
+
normalizedCount += 1;
|
|
490
|
+
if (normalizedCount >= limits.maxAttributeCount) break;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return result;
|
|
494
|
+
};
|
|
495
|
+
const selectAllowlistedAttributes = (source, allowlist, limits) => {
|
|
496
|
+
if (source === void 0 || allowlist === void 0) return {};
|
|
497
|
+
const selected = {};
|
|
498
|
+
const length = Math.min(allowlist.length, limits.maxAttributeCount);
|
|
499
|
+
let selectedCount = 0;
|
|
500
|
+
for (let index = 0; index < length; index += 1) {
|
|
501
|
+
const key = allowlist[index];
|
|
502
|
+
if (typeof key !== "string" || key.length === 0 || key.length > limits.maxAttributeLength) continue;
|
|
503
|
+
try {
|
|
504
|
+
if (!(key in selected)) selectedCount += 1;
|
|
505
|
+
selected[key] = source[key];
|
|
506
|
+
} catch {}
|
|
507
|
+
if (selectedCount >= limits.maxAttributeCount) break;
|
|
508
|
+
}
|
|
509
|
+
return normalizeAttributes(selected, limits);
|
|
510
|
+
};
|
|
511
|
+
const makeExecutionAttributes = (event, options, limits) => {
|
|
512
|
+
const result = selectAllowlistedAttributes(event.attributes, options.executionAttributeAllowlist, limits);
|
|
513
|
+
if (event.attributes !== void 0 && options.sanitizeExecutionAttributes !== void 0) try {
|
|
514
|
+
const sanitized = options.sanitizeExecutionAttributes(event.attributes);
|
|
515
|
+
ignoreRejection(sanitized);
|
|
516
|
+
Object.assign(result, normalizeAttributes(sanitized, limits));
|
|
517
|
+
} catch {}
|
|
518
|
+
return result;
|
|
519
|
+
};
|
|
520
|
+
const makeFailureDetails = (cause, sanitizer, limits) => {
|
|
521
|
+
if (sanitizer === void 0) return;
|
|
522
|
+
let sanitized;
|
|
523
|
+
try {
|
|
524
|
+
sanitized = sanitizer(cause);
|
|
525
|
+
ignoreRejection(sanitized);
|
|
526
|
+
} catch {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (typeof sanitized === "string") return {
|
|
530
|
+
message: sanitized.slice(0, limits.maxAttributeLength),
|
|
531
|
+
attributes: {}
|
|
532
|
+
};
|
|
533
|
+
if (!isRecord(sanitized)) return;
|
|
534
|
+
let message;
|
|
535
|
+
let attributes;
|
|
536
|
+
try {
|
|
537
|
+
message = boundedString(sanitized.message, limits.maxAttributeLength);
|
|
538
|
+
attributes = sanitized.attributes;
|
|
539
|
+
} catch {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
let normalizedAttributes;
|
|
543
|
+
try {
|
|
544
|
+
normalizedAttributes = normalizeAttributes(attributes, limits);
|
|
545
|
+
} catch {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const details = { attributes: normalizedAttributes };
|
|
549
|
+
if (message !== void 0) details.message = message;
|
|
550
|
+
return details;
|
|
551
|
+
};
|
|
552
|
+
const mergeAdditionalAttributes = (target, additional, limit) => {
|
|
553
|
+
for (const [key, value] of Object.entries(additional)) {
|
|
554
|
+
if (Object.keys(target).length >= limit) break;
|
|
555
|
+
if (!(key in target)) target[key] = value;
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
const ignoreRejection = (value) => {
|
|
559
|
+
try {
|
|
560
|
+
Promise.resolve(value).catch(() => {});
|
|
561
|
+
} catch {}
|
|
562
|
+
};
|
|
563
|
+
const callSpan = (operation) => {
|
|
564
|
+
try {
|
|
565
|
+
ignoreRejection(operation());
|
|
566
|
+
} catch {}
|
|
567
|
+
};
|
|
568
|
+
const addFailureDetails = (span, details) => {
|
|
569
|
+
const message = details.message;
|
|
570
|
+
if (message !== void 0) callSpan(() => span.recordException(message));
|
|
571
|
+
if (Object.keys(details.attributes).length > 0) callSpan(() => span.addEvent(FAILURE_EVENT_NAME, details.attributes));
|
|
572
|
+
};
|
|
573
|
+
const addAttribute = (target, key, value, limit) => {
|
|
574
|
+
if (key in target || Object.keys(target).length < limit) target[key] = value;
|
|
575
|
+
};
|
|
576
|
+
const makeExecutionSpanAttributes = (event, options, limits) => {
|
|
577
|
+
const attributes = {};
|
|
578
|
+
const executionId = boundedNonEmptyString(event.executionId, limits.maxAttributeLength);
|
|
579
|
+
const name = boundedNonEmptyString(event.name, limits.maxAttributeLength);
|
|
580
|
+
if (executionId !== void 0) addAttribute(attributes, EXECUTION_ID_ATTRIBUTE, executionId, limits.maxAttributeCount);
|
|
581
|
+
if (name !== void 0) addAttribute(attributes, PROGRAM_NAME_ATTRIBUTE, name, limits.maxAttributeCount);
|
|
582
|
+
addAttribute(attributes, LIBRARY_ATTRIBUTE, "better-effect", limits.maxAttributeCount);
|
|
583
|
+
mergeAdditionalAttributes(attributes, makeExecutionAttributes(event, options, limits), limits.maxAttributeCount);
|
|
584
|
+
return attributes;
|
|
585
|
+
};
|
|
586
|
+
const makeResolutionPathAttributes = (path, limits) => {
|
|
587
|
+
const tags = [];
|
|
588
|
+
for (const service of path.slice(0, limits.maxResolutionPathLength)) {
|
|
589
|
+
const tag = boundedNonEmptyString(service.serviceTag, limits.maxAttributeLength);
|
|
590
|
+
if (tag !== void 0) tags.push(tag);
|
|
591
|
+
}
|
|
592
|
+
const result = {};
|
|
593
|
+
if (tags.length > 0) result[RESOLUTION_PATH_ATTRIBUTE] = tags;
|
|
594
|
+
if (path.length > limits.maxResolutionPathLength) result[RESOLUTION_PATH_TRUNCATED_ATTRIBUTE] = true;
|
|
595
|
+
return result;
|
|
596
|
+
};
|
|
597
|
+
const makeServiceEventAttributes = (event, limits, executionId) => {
|
|
598
|
+
const serviceTag = boundedNonEmptyString(event.service.serviceTag, limits.maxAttributeLength);
|
|
599
|
+
const attributes = {};
|
|
600
|
+
if (serviceTag !== void 0) addAttribute(attributes, SERVICE_TAG_ATTRIBUTE, serviceTag, limits.maxAttributeCount);
|
|
601
|
+
addAttribute(attributes, OUTCOME_ATTRIBUTE, serviceEventStatus(event), limits.maxAttributeCount);
|
|
602
|
+
if ("resolutionPath" in event) mergeAdditionalAttributes(attributes, makeResolutionPathAttributes(event.resolutionPath, limits), limits.maxAttributeCount);
|
|
603
|
+
if (executionId !== void 0) addAttribute(attributes, EXECUTION_ID_ATTRIBUTE, executionId, limits.maxAttributeCount);
|
|
604
|
+
return attributes;
|
|
605
|
+
};
|
|
606
|
+
const readActiveContext = () => {
|
|
607
|
+
try {
|
|
608
|
+
return context.active();
|
|
609
|
+
} catch {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
const setSpanInContext = (parent, span) => {
|
|
614
|
+
try {
|
|
615
|
+
return trace.setSpan(parent, span);
|
|
616
|
+
} catch {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
const resolveTracer = (options) => {
|
|
621
|
+
const hasTracer = options.tracer !== void 0;
|
|
622
|
+
const hasProvider = options.provider !== void 0;
|
|
623
|
+
if (hasTracer === hasProvider) throw new TypeError("OpenTelemetryRuntimeObserver requires exactly one tracer or provider");
|
|
624
|
+
if (hasTracer) {
|
|
625
|
+
if (!isTracer(options.tracer)) throw new TypeError("OpenTelemetryRuntimeObserver requires a tracer with startSpan");
|
|
626
|
+
return options.tracer;
|
|
627
|
+
}
|
|
628
|
+
if (hasProvider) {
|
|
629
|
+
const tracerName = options.tracerName ?? DEFAULT_TRACER_NAME;
|
|
630
|
+
const tracer = options.provider.getTracer(tracerName, options.tracerVersion);
|
|
631
|
+
if (!isTracer(tracer)) throw new TypeError("OpenTelemetry provider did not return a tracer with startSpan");
|
|
632
|
+
return tracer;
|
|
633
|
+
}
|
|
634
|
+
throw new TypeError("OpenTelemetryRuntimeObserver requires an existing tracer or provider");
|
|
635
|
+
};
|
|
636
|
+
/**
|
|
637
|
+
* Best-effort RuntimeObserver adapter for an existing OpenTelemetry tracer.
|
|
638
|
+
*
|
|
639
|
+
* The adapter never installs a global provider, SDK, context manager, or
|
|
640
|
+
* exporter. All observer hooks are synchronous and isolate tracer failures.
|
|
641
|
+
*/
|
|
642
|
+
var OpenTelemetryRuntimeObserver = class OpenTelemetryRuntimeObserver {
|
|
643
|
+
tracer;
|
|
644
|
+
options;
|
|
645
|
+
limits;
|
|
646
|
+
executionSpans = /* @__PURE__ */ new Map();
|
|
647
|
+
serviceResolution;
|
|
648
|
+
lifecycleTelemetry;
|
|
649
|
+
shutdownTelemetry;
|
|
650
|
+
recordFailures;
|
|
651
|
+
disposed = false;
|
|
652
|
+
constructor(options) {
|
|
653
|
+
this.tracer = resolveTracer(options);
|
|
654
|
+
this.options = options;
|
|
655
|
+
this.limits = normalizeLimits(options);
|
|
656
|
+
this.serviceResolution = options.serviceResolution ?? "off";
|
|
657
|
+
this.recordFailures = options.recordFailures === true;
|
|
658
|
+
if (!isTelemetryMode(this.serviceResolution)) throw new TypeError("serviceResolution must be off, events, or spans");
|
|
659
|
+
const lifecycle = options.lifecycle ?? "off";
|
|
660
|
+
if (!isTelemetryMode(lifecycle)) throw new TypeError("lifecycle must be off, events, or spans");
|
|
661
|
+
const shutdown = options.shutdown ?? "off";
|
|
662
|
+
if (!isTelemetryMode(shutdown)) throw new TypeError("shutdown must be off, events, or spans");
|
|
663
|
+
this.lifecycleTelemetry = new LifecycleTelemetry({
|
|
664
|
+
mode: lifecycle,
|
|
665
|
+
recordFailures: this.recordFailures,
|
|
666
|
+
getParentContext: () => readActiveContext() ?? ROOT_CONTEXT,
|
|
667
|
+
startSpan: this.startSpan.bind(this),
|
|
668
|
+
makeAttributes: (event, phase, outcome, durationMs) => makeLifecycleAttributes(event, phase, outcome, durationMs, this.limits),
|
|
669
|
+
getFailureDetails: (cause) => makeFailureDetails(cause, this.options.sanitizeFailure, this.limits)
|
|
670
|
+
});
|
|
671
|
+
this.shutdownTelemetry = new ShutdownTelemetry({
|
|
672
|
+
mode: shutdown,
|
|
673
|
+
recordFailures: this.recordFailures,
|
|
674
|
+
rootContext: ROOT_CONTEXT,
|
|
675
|
+
startSpan: this.startSpan.bind(this),
|
|
676
|
+
makeAttributes: (event, phase, durationMs) => makeShutdownAttributes(event, phase, durationMs, this.limits),
|
|
677
|
+
getFailureDetails: (cause) => makeFailureDetails(cause, this.options.sanitizeFailure, this.limits)
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
static make(options) {
|
|
681
|
+
return new OpenTelemetryRuntimeObserver(options);
|
|
682
|
+
}
|
|
683
|
+
onExecutionStart = (event) => {
|
|
684
|
+
this.safely(() => this.startExecution(event));
|
|
685
|
+
};
|
|
686
|
+
onExecutionEnd = (event) => {
|
|
687
|
+
this.safely(() => this.endExecution(event));
|
|
688
|
+
};
|
|
689
|
+
onServiceResolve = (event) => {
|
|
690
|
+
this.safely(() => this.observeService("better-effect.service.resolve", event));
|
|
691
|
+
};
|
|
692
|
+
onServiceAcquire = (event) => {
|
|
693
|
+
this.safely(() => this.observeService("better-effect.service.acquire", event));
|
|
694
|
+
};
|
|
695
|
+
onResourceRelease = (event) => {
|
|
696
|
+
this.safely(() => this.observeService("better-effect.service.release", event));
|
|
697
|
+
};
|
|
698
|
+
onLifecycleStart = (event) => {
|
|
699
|
+
this.safely(() => this.lifecycleTelemetry.onStart(event));
|
|
700
|
+
};
|
|
701
|
+
onLifecycleEnd = (event) => {
|
|
702
|
+
this.safely(() => this.lifecycleTelemetry.onEnd(event));
|
|
703
|
+
};
|
|
704
|
+
onLifecycleRelease = (event) => {
|
|
705
|
+
this.safely(() => this.lifecycleTelemetry.onRelease(event));
|
|
706
|
+
};
|
|
707
|
+
onShutdown = (event) => {
|
|
708
|
+
this.safely(() => this.shutdownTelemetry.onEvent(event));
|
|
709
|
+
};
|
|
710
|
+
/** End any spans whose malformed or missing end event left them pending. */
|
|
711
|
+
dispose() {
|
|
712
|
+
if (this.disposed) return;
|
|
713
|
+
this.disposed = true;
|
|
714
|
+
const pending = [...this.executionSpans.values()];
|
|
715
|
+
for (const execution of pending) {
|
|
716
|
+
const span = execution.span;
|
|
717
|
+
if (span === void 0) continue;
|
|
718
|
+
this.endExecutionSpan(execution, span);
|
|
719
|
+
}
|
|
720
|
+
this.lifecycleTelemetry.dispose();
|
|
721
|
+
this.shutdownTelemetry.dispose();
|
|
722
|
+
}
|
|
723
|
+
[Symbol.dispose]() {
|
|
724
|
+
this.dispose();
|
|
725
|
+
}
|
|
726
|
+
async [Symbol.asyncDispose]() {
|
|
727
|
+
this.dispose();
|
|
728
|
+
}
|
|
729
|
+
safely(operation) {
|
|
730
|
+
if (this.disposed) return;
|
|
731
|
+
try {
|
|
732
|
+
operation();
|
|
733
|
+
} catch {}
|
|
734
|
+
}
|
|
735
|
+
startSpan(name, attributes, parentContext) {
|
|
736
|
+
try {
|
|
737
|
+
const span = parentContext === void 0 ? this.tracer.startSpan(name, { attributes }) : this.tracer.startSpan(name, { attributes }, parentContext);
|
|
738
|
+
if (!isSpan(span)) {
|
|
739
|
+
ignoreRejection(span);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
return span;
|
|
743
|
+
} catch {
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
ownsExecutionSpan(execution, span) {
|
|
748
|
+
return this.executionSpans.get(execution.executionId) === execution && execution.span === span;
|
|
749
|
+
}
|
|
750
|
+
claimExecutionSpan(execution, span) {
|
|
751
|
+
if (this.executionSpans.get(execution.executionId) !== execution || execution.span !== span) return false;
|
|
752
|
+
this.executionSpans.delete(execution.executionId);
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
755
|
+
endExecutionSpan(execution, span) {
|
|
756
|
+
if (this.claimExecutionSpan(execution, span)) callSpan(() => span.end());
|
|
757
|
+
}
|
|
758
|
+
finishPendingExecution(execution) {
|
|
759
|
+
if (execution.pendingEnd !== void 0 && isActiveExecutionSpan(execution)) this.finishExecution(execution, execution.pendingEnd);
|
|
760
|
+
}
|
|
761
|
+
startExecution(event) {
|
|
762
|
+
if (this.executionSpans.has(event.executionId)) return;
|
|
763
|
+
const execution = {
|
|
764
|
+
executionId: event.executionId,
|
|
765
|
+
ended: false
|
|
766
|
+
};
|
|
767
|
+
this.executionSpans.set(event.executionId, execution);
|
|
768
|
+
const parentContext = readActiveContext() ?? ROOT_CONTEXT;
|
|
769
|
+
let span;
|
|
770
|
+
try {
|
|
771
|
+
span = this.startSpan(boundedNonEmptyString(event.name, this.limits.maxAttributeLength) ?? DEFAULT_EXECUTION_SPAN_NAME, makeExecutionSpanAttributes(event, this.options, this.limits), parentContext);
|
|
772
|
+
} catch {
|
|
773
|
+
this.claimExecutionSpan(execution, void 0);
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (span === void 0) {
|
|
777
|
+
this.claimExecutionSpan(execution, void 0);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
execution.span = span;
|
|
781
|
+
if (this.disposed) {
|
|
782
|
+
this.endExecutionSpan(execution, span);
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (!this.ownsExecutionSpan(execution, span)) return;
|
|
786
|
+
const executionContext = setSpanInContext(parentContext, span);
|
|
787
|
+
if (executionContext === void 0) {
|
|
788
|
+
const pendingEnd = execution.pendingEnd;
|
|
789
|
+
if (pendingEnd !== void 0 && isStartedExecutionSpan(execution)) this.finishExecution(execution, pendingEnd);
|
|
790
|
+
else this.endExecutionSpan(execution, span);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
execution.executionContext = executionContext;
|
|
794
|
+
this.finishPendingExecution(execution);
|
|
795
|
+
}
|
|
796
|
+
endExecution(event) {
|
|
797
|
+
const execution = this.executionSpans.get(event.executionId);
|
|
798
|
+
if (execution === void 0 || execution.ended) return;
|
|
799
|
+
execution.ended = true;
|
|
800
|
+
if (!isActiveExecutionSpan(execution)) {
|
|
801
|
+
execution.pendingEnd = event;
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
this.finishExecution(execution, event);
|
|
805
|
+
}
|
|
806
|
+
finishExecution(execution, event) {
|
|
807
|
+
const span = execution.span;
|
|
808
|
+
if (!this.claimExecutionSpan(execution, span)) return;
|
|
809
|
+
try {
|
|
810
|
+
const status = executionEventStatus(event);
|
|
811
|
+
callSpan(() => span.setAttribute(OUTCOME_ATTRIBUTE, status));
|
|
812
|
+
callSpan(() => span.setStatus({ code: status === "failure" ? SpanStatusCode.ERROR : SpanStatusCode.OK }));
|
|
813
|
+
if (status === "failure" && this.recordFailures) {
|
|
814
|
+
const cause = event.outcome.status === "failure" ? event.outcome.cause : event.cleanupFailure;
|
|
815
|
+
const details = makeFailureDetails(cause, this.options.sanitizeFailure, this.limits);
|
|
816
|
+
if (details !== void 0) addFailureDetails(span, details);
|
|
817
|
+
}
|
|
818
|
+
} finally {
|
|
819
|
+
callSpan(() => span.end());
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
findCorrelatedExecution(event) {
|
|
823
|
+
if (event.executionId === void 0) return;
|
|
824
|
+
const execution = this.executionSpans.get(event.executionId);
|
|
825
|
+
return execution !== void 0 && isActiveExecutionSpan(execution) ? execution : void 0;
|
|
826
|
+
}
|
|
827
|
+
observeService(name, event) {
|
|
828
|
+
if (this.serviceResolution === "off") return;
|
|
829
|
+
const execution = this.findCorrelatedExecution(event);
|
|
830
|
+
const executionId = execution === void 0 ? void 0 : this.findExecutionId(execution);
|
|
831
|
+
const attributes = makeServiceEventAttributes(event, this.limits, executionId);
|
|
832
|
+
let failureCause;
|
|
833
|
+
if ("error" in event && event.error !== void 0) failureCause = event.error;
|
|
834
|
+
else if (event.outcome.status === "failure") failureCause = event.outcome.cause;
|
|
835
|
+
const details = serviceEventStatus(event) === "failure" && this.recordFailures ? makeFailureDetails(failureCause, this.options.sanitizeFailure, this.limits) : void 0;
|
|
836
|
+
if (details?.attributes !== void 0) mergeAdditionalAttributes(attributes, details.attributes, this.limits.maxAttributeCount);
|
|
837
|
+
if (execution !== void 0) {
|
|
838
|
+
this.observeCorrelatedService(name, event, execution, attributes, details);
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
this.observeStandaloneService(name, event, attributes, details);
|
|
842
|
+
}
|
|
843
|
+
findExecutionId(execution) {
|
|
844
|
+
return boundedNonEmptyString(execution.executionId, this.limits.maxAttributeLength);
|
|
845
|
+
}
|
|
846
|
+
observeCorrelatedService(name, event, execution, attributes, details) {
|
|
847
|
+
if (this.serviceResolution === "events") {
|
|
848
|
+
callSpan(() => execution.span.addEvent(name, attributes));
|
|
849
|
+
if (details !== void 0) addFailureDetails(execution.span, details);
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
const span = this.startSpan(name, attributes, execution.executionContext);
|
|
853
|
+
if (span === void 0) return;
|
|
854
|
+
this.finishServiceSpan(span, event, details);
|
|
855
|
+
}
|
|
856
|
+
observeStandaloneService(name, event, attributes, details) {
|
|
857
|
+
const span = this.startSpan(this.serviceResolution === "events" ? DEFAULT_SERVICE_SPAN_NAME : name, attributes, ROOT_CONTEXT);
|
|
858
|
+
if (span === void 0) return;
|
|
859
|
+
if (this.serviceResolution === "events") callSpan(() => span.addEvent(name, attributes));
|
|
860
|
+
this.finishServiceSpan(span, event, details);
|
|
861
|
+
}
|
|
862
|
+
finishServiceSpan(span, event, details) {
|
|
863
|
+
callSpan(() => span.setStatus({ code: serviceEventStatus(event) === "failure" ? SpanStatusCode.ERROR : SpanStatusCode.OK }));
|
|
864
|
+
if (details !== void 0) addFailureDetails(span, details);
|
|
865
|
+
callSpan(() => span.end());
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
//#endregion
|
|
869
|
+
export { OpenTelemetryRuntimeObserver };
|
|
870
|
+
|
|
871
|
+
//# sourceMappingURL=opentelemetry.mjs.map
|