brass-runtime 1.17.0 → 1.18.1
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 +36 -3
- package/dist/agent/cli/main.cjs +31 -32
- package/dist/agent/cli/main.js +3 -4
- package/dist/agent/cli/main.mjs +3 -4
- package/dist/agent/index.cjs +4 -5
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +3 -4
- package/dist/agent/index.mjs +3 -4
- package/dist/{chunk-7X3K5RMS.js → chunk-22HZQG5F.js} +9 -11
- package/dist/{chunk-GLE2WY7Z.cjs → chunk-2JHJ4YHS.cjs} +417 -124
- package/dist/{chunk-Q2I37RP3.cjs → chunk-2OW6IFY2.cjs} +44 -323
- package/dist/{chunk-7ZPEZ57L.cjs → chunk-5LC7V2OZ.cjs} +18 -20
- package/dist/{chunk-AGR5B2BC.cjs → chunk-5RZ7YITF.cjs} +564 -12
- package/dist/{chunk-DNFJLJMW.mjs → chunk-6MLAZPBL.mjs} +48 -24
- package/dist/{chunk-EJ6BPYVR.mjs → chunk-6V2AWT4R.mjs} +1 -1
- package/dist/{chunk-3AYM6WPJ.js → chunk-7DU7IQHK.js} +20 -299
- package/dist/{chunk-SK7UZRNI.mjs → chunk-7GBJYOX7.mjs} +528 -23
- package/dist/chunk-7TKI527D.cjs +123 -0
- package/dist/{chunk-52OB2ROS.js → chunk-7VQLEN37.js} +2 -4
- package/dist/{chunk-KH4SYAOS.mjs → chunk-B5FKOLTB.mjs} +20 -299
- package/dist/{chunk-FHQGHPMO.mjs → chunk-BC6Q6BCO.mjs} +2 -4
- package/dist/{chunk-4P2HHGAX.mjs → chunk-COOW7BJX.mjs} +32 -11
- package/dist/{chunk-2HQTDLHF.mjs → chunk-EEN5OTCR.mjs} +555 -3
- package/dist/{chunk-KZJQ723N.cjs → chunk-EICAJDNX.cjs} +13 -15
- package/dist/chunk-ELIECDYN.cjs +33 -0
- package/dist/{chunk-GYM3LLGS.mjs → chunk-H626ZTDZ.mjs} +399 -106
- package/dist/{chunk-C3MDXTRZ.js → chunk-HCJ4S3YB.js} +48 -24
- package/dist/{chunk-7JIJOVCT.js → chunk-IPSMXUWA.js} +2 -4
- package/dist/{chunk-4ROBZFL6.cjs → chunk-J6DUHITE.cjs} +6 -8
- package/dist/{chunk-6RY2FFN4.mjs → chunk-JWIEMBE6.mjs} +9 -11
- package/dist/{chunk-PD4EJTQC.cjs → chunk-KNTJ7FQB.cjs} +5 -5
- package/dist/chunk-KTGDLBLD.mjs +123 -0
- package/dist/chunk-LSYQ3C2M.js +33 -0
- package/dist/{chunk-RKGKFN2A.js → chunk-OW5VHAOE.js} +1 -1
- package/dist/{chunk-EOC4UHBS.mjs → chunk-RBHNOKH4.mjs} +2 -2
- package/dist/{chunk-6IXXWIUM.js → chunk-S4HXADU4.js} +555 -3
- package/dist/{chunk-FH2X7BVP.js → chunk-TTSPIU3U.js} +399 -106
- package/dist/{chunk-5QC7LRZ3.js → chunk-UAKAF32U.js} +2 -2
- package/dist/{chunk-CZIVE6NT.cjs → chunk-UUMKZJRJ.cjs} +48 -24
- package/dist/{chunk-MBEJI5HF.mjs → chunk-WCBNXPN6.mjs} +2 -4
- package/dist/{chunk-52PPNNI4.cjs → chunk-WGE2FEZE.cjs} +2 -2
- package/dist/{chunk-WBGRHGBP.cjs → chunk-WI7GZF3B.cjs} +114 -93
- package/dist/chunk-WUDHOZIH.js +6234 -0
- package/dist/{chunk-F6XWZQY4.cjs → chunk-WVSZOPGQ.cjs} +583 -78
- package/dist/chunk-XPIMJQYS.cjs +6234 -0
- package/dist/{chunk-VWIPB6I5.js → chunk-YGR2IN4R.js} +528 -23
- package/dist/chunk-YM3EDNYD.js +123 -0
- package/dist/chunk-YWLLH27R.mjs +33 -0
- package/dist/{chunk-BKK77SBA.js → chunk-YZ5LQ32F.js} +32 -11
- package/dist/chunk-Z3ZZMQUZ.mjs +6234 -0
- package/dist/core/index.cjs +37 -9
- package/dist/core/index.d.ts +19 -152
- package/dist/core/index.js +86 -58
- package/dist/core/index.mjs +86 -58
- package/dist/defaultClient-Cid0JoUR.d.ts +1648 -0
- package/dist/{effect-DIUHZ9IN.d.ts → effect-DnGUuhw6.d.ts} +22 -1
- package/dist/http/index.cjs +206 -59
- package/dist/http/index.d.ts +55 -819
- package/dist/http/index.js +220 -73
- package/dist/http/index.mjs +220 -73
- package/dist/http/testing.cjs +31 -10
- package/dist/http/testing.d.ts +16 -5
- package/dist/http/testing.js +29 -8
- package/dist/http/testing.mjs +29 -8
- package/dist/index.cjs +116 -88
- package/dist/index.d.ts +9 -8
- package/dist/index.js +87 -59
- package/dist/index.mjs +87 -59
- package/dist/{schedule-CK3Ml_7p.d.ts → layer-D2LFcBVx.d.ts} +176 -2
- package/dist/observability/index.cjs +20 -7
- package/dist/observability/index.d.ts +32 -8
- package/dist/observability/index.js +19 -6
- package/dist/observability/index.mjs +19 -6
- package/dist/perf/cli.cjs +26 -28
- package/dist/perf/cli.js +11 -13
- package/dist/perf/cli.mjs +11 -13
- package/dist/perf/index.cjs +13 -15
- package/dist/perf/index.js +11 -13
- package/dist/perf/index.mjs +11 -13
- package/dist/schema/index.cjs +2 -2
- package/dist/schema/index.js +1 -1
- package/dist/schema/index.mjs +1 -1
- package/dist/{server-D6JZ15_e.d.ts → server-Bf1zNYZk.d.ts} +5 -5
- package/dist/{stream-B4oK9JFP.d.ts → stream-I7bkvF7a.d.ts} +1 -1
- package/dist/{tracer-Hwt1cl7h.d.ts → tracer-DF83nLn6.d.ts} +2 -2
- package/dist/{tracing-DqbTKGcf.d.ts → tracing-CWV4gT0u.d.ts} +1 -1
- package/docs/README.md +2 -0
- package/docs/ai/PUBLIC_API.md +28 -7
- package/docs/articles/brass-runtime-http-observability.md +467 -0
- package/docs/frameworks/angular.md +51 -0
- package/docs/frameworks/express.md +58 -0
- package/docs/frameworks/fastify.md +49 -0
- package/docs/frameworks/nestjs.md +53 -0
- package/docs/frameworks/nextjs.md +55 -0
- package/docs/frameworks/react.md +44 -0
- package/docs/frameworks/vanilla.md +56 -0
- package/docs/guides/layers.md +130 -0
- package/docs/http-recipes.md +31 -1
- package/docs/http.md +50 -1
- package/docs/observability.md +132 -0
- package/docs/performance-profiler.md +6 -2
- package/docs/recipes/layers.md +46 -2
- package/docs/recipes/testing.md +25 -0
- package/package.json +6 -2
- package/dist/chunk-3LOYJFRR.cjs +0 -300
- package/dist/chunk-3Y2RIUMM.js +0 -300
- package/dist/chunk-5EC274J5.cjs +0 -2874
- package/dist/chunk-5VRJNBLZ.mjs +0 -2874
- package/dist/chunk-62AZW6UT.cjs +0 -313
- package/dist/chunk-74ZTY6CP.js +0 -2871
- package/dist/chunk-7CMJS3QE.mjs +0 -2871
- package/dist/chunk-A2OM6NEH.mjs +0 -194
- package/dist/chunk-B33ICAKP.js +0 -313
- package/dist/chunk-JF5WGYJJ.cjs +0 -194
- package/dist/chunk-KN32XNTH.mjs +0 -313
- package/dist/chunk-KQLYONSE.cjs +0 -2871
- package/dist/chunk-L2SYFEBS.js +0 -194
- package/dist/chunk-MIIYDLGM.js +0 -2874
- package/dist/chunk-PWC3RBQE.mjs +0 -300
- package/dist/client-CZHU674n.d.ts +0 -820
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RuntimeService
|
|
3
|
+
} from "./chunk-LSYQ3C2M.js";
|
|
1
4
|
import {
|
|
2
5
|
exemplarFromTraceContext,
|
|
3
6
|
injectTraceContext,
|
|
@@ -6,19 +9,33 @@ import {
|
|
|
6
9
|
makeRequestObservabilityContext,
|
|
7
10
|
normalizeHttpRoute,
|
|
8
11
|
sanitizeHttpTarget,
|
|
12
|
+
shouldSampleWith,
|
|
9
13
|
spanEvent,
|
|
10
14
|
validateHttpObservabilityOptions,
|
|
11
15
|
withSpan
|
|
12
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-YZ5LQ32F.js";
|
|
13
17
|
import {
|
|
18
|
+
HttpClientService,
|
|
14
19
|
getHttpRequestPolicy,
|
|
15
20
|
httpErrorStatus,
|
|
16
|
-
isRetryableHttpError
|
|
17
|
-
|
|
21
|
+
isRetryableHttpError,
|
|
22
|
+
makeDefaultHttpClient
|
|
23
|
+
} from "./chunk-WUDHOZIH.js";
|
|
24
|
+
import {
|
|
25
|
+
registerHttpEffect
|
|
26
|
+
} from "./chunk-TRM4JUZQ.js";
|
|
18
27
|
import {
|
|
28
|
+
layerEffect,
|
|
29
|
+
makeServiceTag
|
|
30
|
+
} from "./chunk-S4HXADU4.js";
|
|
31
|
+
import {
|
|
32
|
+
Runtime,
|
|
33
|
+
fromPromiseAbortable,
|
|
19
34
|
getCurrentFiber
|
|
20
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-TTSPIU3U.js";
|
|
21
36
|
import {
|
|
37
|
+
Cause,
|
|
38
|
+
asyncEffect,
|
|
22
39
|
asyncFail,
|
|
23
40
|
asyncFlatMap,
|
|
24
41
|
asyncFold,
|
|
@@ -28,6 +45,9 @@ import {
|
|
|
28
45
|
|
|
29
46
|
// src/observability/http.ts
|
|
30
47
|
var DEFAULT_DURATION_BUCKETS = [1, 5, 10, 25, 50, 100, 250, 500, 1e3, 2500, 5e3, 1e4];
|
|
48
|
+
var MAX_HTTP_METRIC_CACHE_ENTRIES = 4096;
|
|
49
|
+
var EMPTY_SPAN_ATTRIBUTES = Object.freeze({});
|
|
50
|
+
var TRACE_SAMPLER_RATIO = /* @__PURE__ */ Symbol.for("brass-runtime.traceSampler.ratio");
|
|
31
51
|
var POLICY_LABEL_NAMES = {
|
|
32
52
|
preset: "policy",
|
|
33
53
|
lane: "lane",
|
|
@@ -103,10 +123,19 @@ function withHttpObservability(options) {
|
|
|
103
123
|
validateHttpObservabilityOptions(options ?? {});
|
|
104
124
|
}
|
|
105
125
|
const resolved = resolveHttpObservabilityOptions(options);
|
|
126
|
+
const metricCache = makeHttpMetricCache();
|
|
127
|
+
attachMetricCache(resolved.metrics, metricCache);
|
|
106
128
|
return (next) => {
|
|
129
|
+
const adaptiveLimiter = adaptiveLimiterOf(next);
|
|
130
|
+
if (canUseLeanHttpMetricsPath(resolved, adaptiveLimiter)) {
|
|
131
|
+
return withLeanHttpMetricsObservability(next, resolved, metricCache);
|
|
132
|
+
}
|
|
133
|
+
if (canUseLeanHttpSampledSpanPath(resolved, adaptiveLimiter)) {
|
|
134
|
+
return withLeanHttpSampledSpanObservability(next, resolved, metricCache);
|
|
135
|
+
}
|
|
107
136
|
return (req) => {
|
|
108
137
|
const run = asyncFlatMap(
|
|
109
|
-
beginHttpObservation(req, resolved),
|
|
138
|
+
beginHttpObservation(req, resolved, metricCache),
|
|
110
139
|
(state) => asyncFlatMap(
|
|
111
140
|
logHttpRequest(req, resolved),
|
|
112
141
|
() => asyncFlatMap(
|
|
@@ -116,23 +145,25 @@ function withHttpObservability(options) {
|
|
|
116
145
|
(error) => {
|
|
117
146
|
const finish = state.finishWithError(error);
|
|
118
147
|
const adaptiveLimiterAttrs = observeAdaptiveLimiter(wireReq, next, resolved);
|
|
148
|
+
const traceEvent = !shouldEmitHttpSpanEvents(resolved) ? asyncSucceed(void 0) : spanEvent("http.client.error", {
|
|
149
|
+
...finish.spanAttributes,
|
|
150
|
+
...adaptiveLimiterAttrs,
|
|
151
|
+
"error.type": error._tag
|
|
152
|
+
});
|
|
119
153
|
return asyncFlatMap(
|
|
120
|
-
|
|
121
|
-
...finish.spanAttributes,
|
|
122
|
-
...adaptiveLimiterAttrs,
|
|
123
|
-
"error.type": error._tag
|
|
124
|
-
}),
|
|
154
|
+
traceEvent,
|
|
125
155
|
() => asyncFlatMap(logHttpError(req, error, finish, resolved), () => asyncFail(error))
|
|
126
156
|
);
|
|
127
157
|
},
|
|
128
158
|
(res) => {
|
|
129
159
|
const finish = state.finishWithResponse(res);
|
|
130
160
|
const adaptiveLimiterAttrs = observeAdaptiveLimiter(wireReq, next, resolved);
|
|
161
|
+
const traceEvent = !shouldEmitHttpSpanEvents(resolved) ? asyncSucceed(void 0) : spanEvent("http.client.response", {
|
|
162
|
+
...finish.spanAttributes,
|
|
163
|
+
...adaptiveLimiterAttrs
|
|
164
|
+
});
|
|
131
165
|
return asyncFlatMap(
|
|
132
|
-
|
|
133
|
-
...finish.spanAttributes,
|
|
134
|
-
...adaptiveLimiterAttrs
|
|
135
|
-
}),
|
|
166
|
+
traceEvent,
|
|
136
167
|
() => asyncFlatMap(logHttpResponse(req, res, finish, resolved), () => asyncSucceed(res))
|
|
137
168
|
);
|
|
138
169
|
}
|
|
@@ -146,26 +177,410 @@ function withHttpObservability(options) {
|
|
|
146
177
|
};
|
|
147
178
|
}
|
|
148
179
|
var makeHttpObservabilityMiddleware = withHttpObservability;
|
|
149
|
-
|
|
180
|
+
var metricResetRegistry = /* @__PURE__ */ new WeakMap();
|
|
181
|
+
function canUseLeanHttpMetricsPath(options, adaptiveLimiter) {
|
|
182
|
+
return Boolean(
|
|
183
|
+
options.metrics && options.logs === false && options.spans === false && !options.injectTraceHeaders && (!options.adaptiveLimiter.enabled || !adaptiveLimiter)
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
function canUseLeanHttpSampledSpanPath(options, adaptiveLimiter) {
|
|
187
|
+
return Boolean(
|
|
188
|
+
options.spans !== false && options.spans.events === false && options.logs === false && !options.injectTraceHeaders && (!options.adaptiveLimiter.enabled || !adaptiveLimiter)
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
function withLeanHttpSampledSpanObservability(next, options, metricCache) {
|
|
192
|
+
return (req) => asyncEffect((env, cb) => {
|
|
193
|
+
const startedAt = options.clock();
|
|
194
|
+
const baseLabels = requestBaseLabels(req, options);
|
|
195
|
+
const handles = beginLeanHttpMetricsObservation(baseLabels, options, metricCache);
|
|
196
|
+
const span = startLeanHttpSpan(req, options);
|
|
197
|
+
let finished = false;
|
|
198
|
+
let cancelInner;
|
|
199
|
+
const finish = (exit) => {
|
|
200
|
+
if (finished) return;
|
|
201
|
+
finished = true;
|
|
202
|
+
cancelInner = void 0;
|
|
203
|
+
if (exit._tag === "Success") {
|
|
204
|
+
recordHttpMetricsFinish(
|
|
205
|
+
handles,
|
|
206
|
+
httpStatusOutcome(exit.value.status),
|
|
207
|
+
String(exit.value.status),
|
|
208
|
+
startedAt,
|
|
209
|
+
options,
|
|
210
|
+
metricCache
|
|
211
|
+
);
|
|
212
|
+
finishLeanHttpSpan(span, "success");
|
|
213
|
+
} else {
|
|
214
|
+
const error = httpErrorFromCause(exit.cause);
|
|
215
|
+
const status = httpErrorStatus(error);
|
|
216
|
+
recordHttpMetricsFinish(
|
|
217
|
+
handles,
|
|
218
|
+
httpErrorOutcome(error),
|
|
219
|
+
status !== void 0 ? String(status) : void 0,
|
|
220
|
+
startedAt,
|
|
221
|
+
options,
|
|
222
|
+
metricCache
|
|
223
|
+
);
|
|
224
|
+
finishLeanHttpSpan(span, Cause.containsInterrupt(exit.cause) ? "interrupted" : "failure", error);
|
|
225
|
+
}
|
|
226
|
+
cb(exit);
|
|
227
|
+
};
|
|
228
|
+
try {
|
|
229
|
+
cancelInner = registerHttpEffect(next(req), env, finish);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
const defectExit = {
|
|
232
|
+
_tag: "Failure",
|
|
233
|
+
cause: Cause.die(error)
|
|
234
|
+
};
|
|
235
|
+
finish(defectExit);
|
|
236
|
+
}
|
|
237
|
+
return () => {
|
|
238
|
+
if (!finished) {
|
|
239
|
+
finished = true;
|
|
240
|
+
recordHttpMetricsFinish(
|
|
241
|
+
handles,
|
|
242
|
+
"abort",
|
|
243
|
+
void 0,
|
|
244
|
+
startedAt,
|
|
245
|
+
options,
|
|
246
|
+
metricCache
|
|
247
|
+
);
|
|
248
|
+
finishLeanHttpSpan(span, "interrupted");
|
|
249
|
+
}
|
|
250
|
+
const cancel = cancelInner;
|
|
251
|
+
cancelInner = void 0;
|
|
252
|
+
cancel?.();
|
|
253
|
+
};
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
function withLeanHttpMetricsObservability(next, options, metricCache) {
|
|
257
|
+
return (req) => asyncEffect((env, cb) => {
|
|
258
|
+
const startedAt = options.clock();
|
|
259
|
+
const baseLabels = requestBaseLabels(req, options);
|
|
260
|
+
const handles = beginLeanHttpMetricsObservation(baseLabels, options, metricCache);
|
|
261
|
+
let finished = false;
|
|
262
|
+
let cancelInner;
|
|
263
|
+
const finish = (exit) => {
|
|
264
|
+
if (finished) return;
|
|
265
|
+
finished = true;
|
|
266
|
+
cancelInner = void 0;
|
|
267
|
+
if (exit._tag === "Success") {
|
|
268
|
+
recordHttpMetricsFinish(
|
|
269
|
+
handles,
|
|
270
|
+
httpStatusOutcome(exit.value.status),
|
|
271
|
+
String(exit.value.status),
|
|
272
|
+
startedAt,
|
|
273
|
+
options,
|
|
274
|
+
metricCache
|
|
275
|
+
);
|
|
276
|
+
} else {
|
|
277
|
+
const error = httpErrorFromCause(exit.cause);
|
|
278
|
+
const status = httpErrorStatus(error);
|
|
279
|
+
recordHttpMetricsFinish(
|
|
280
|
+
handles,
|
|
281
|
+
httpErrorOutcome(error),
|
|
282
|
+
status !== void 0 ? String(status) : void 0,
|
|
283
|
+
startedAt,
|
|
284
|
+
options,
|
|
285
|
+
metricCache
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
cb(exit);
|
|
289
|
+
};
|
|
290
|
+
try {
|
|
291
|
+
cancelInner = registerHttpEffect(next(req), env, finish);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
const defectExit = {
|
|
294
|
+
_tag: "Failure",
|
|
295
|
+
cause: Cause.die(error)
|
|
296
|
+
};
|
|
297
|
+
finish(defectExit);
|
|
298
|
+
}
|
|
299
|
+
return () => {
|
|
300
|
+
if (!finished) {
|
|
301
|
+
finished = true;
|
|
302
|
+
recordHttpMetricsFinish(
|
|
303
|
+
handles,
|
|
304
|
+
"abort",
|
|
305
|
+
void 0,
|
|
306
|
+
startedAt,
|
|
307
|
+
options,
|
|
308
|
+
metricCache
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
const cancel = cancelInner;
|
|
312
|
+
cancelInner = void 0;
|
|
313
|
+
cancel?.();
|
|
314
|
+
};
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function beginLeanHttpMetricsObservation(baseLabels, options, metricCache) {
|
|
318
|
+
const cacheKey = metricCacheKey("brass_http_client", baseLabels);
|
|
319
|
+
const existing = metricCache.disabled ? void 0 : metricCache.leanEntries.get(cacheKey);
|
|
320
|
+
if (existing) {
|
|
321
|
+
existing.inFlight?.increment();
|
|
322
|
+
return existing;
|
|
323
|
+
}
|
|
324
|
+
const inFlight = options.metrics ? getCachedMetric(
|
|
325
|
+
metricCache,
|
|
326
|
+
metricCache.inFlightGauges,
|
|
327
|
+
metricCacheKey("brass_http_client_in_flight", baseLabels),
|
|
328
|
+
() => options.metrics.gauge("brass_http_client_in_flight", baseLabels)
|
|
329
|
+
) : void 0;
|
|
330
|
+
inFlight?.increment();
|
|
331
|
+
const created = {
|
|
332
|
+
baseLabels,
|
|
333
|
+
inFlight,
|
|
334
|
+
finishMetrics: /* @__PURE__ */ new Map()
|
|
335
|
+
};
|
|
336
|
+
if (!metricCache.disabled && metricCache.leanEntries.size < MAX_HTTP_METRIC_CACHE_ENTRIES) {
|
|
337
|
+
metricCache.leanEntries.set(cacheKey, created);
|
|
338
|
+
}
|
|
339
|
+
return created;
|
|
340
|
+
}
|
|
341
|
+
function recordHttpMetricsFinish(handles, outcome, status, startedAt, options, metricCache) {
|
|
342
|
+
const durationMs = Math.max(0, options.clock() - startedAt);
|
|
343
|
+
handles.inFlight?.decrement();
|
|
344
|
+
if (!options.metrics) return;
|
|
345
|
+
const finishKey = `${outcome}|${status ?? "none"}`;
|
|
346
|
+
let finishMetrics = metricCache.disabled ? void 0 : handles.finishMetrics.get(finishKey);
|
|
347
|
+
if (!finishMetrics) {
|
|
348
|
+
const labels = requestFinishLabelsFromBase(handles.baseLabels, outcome, status);
|
|
349
|
+
finishMetrics = {
|
|
350
|
+
counter: options.metrics.counter("brass_http_client_requests_total", labels),
|
|
351
|
+
histogram: options.metrics.histogram(
|
|
352
|
+
"brass_http_client_duration_ms",
|
|
353
|
+
[...options.durationBuckets ?? DEFAULT_DURATION_BUCKETS],
|
|
354
|
+
labels
|
|
355
|
+
)
|
|
356
|
+
};
|
|
357
|
+
if (!metricCache.disabled) handles.finishMetrics.set(finishKey, finishMetrics);
|
|
358
|
+
}
|
|
359
|
+
finishMetrics.counter.increment();
|
|
360
|
+
finishMetrics.histogram.observe(durationMs);
|
|
361
|
+
}
|
|
362
|
+
function startLeanHttpSpan(req, options) {
|
|
363
|
+
const configuredSampleRate = options.spans === false ? void 0 : options.spans.sampleRate;
|
|
364
|
+
if (configuredSampleRate !== void 0) {
|
|
365
|
+
if (configuredSampleRate <= 0) return void 0;
|
|
366
|
+
if (configuredSampleRate < 1 && Math.random() >= configuredSampleRate) return void 0;
|
|
367
|
+
}
|
|
368
|
+
const fiber = getCurrentFiber();
|
|
369
|
+
const runtime = fiber?.runtime;
|
|
370
|
+
const sink = options.spanSink ?? runtime?.hooks;
|
|
371
|
+
if (!fiber?.fiberContext || !runtime || !sink) return void 0;
|
|
372
|
+
const previousTrace = fiber.fiberContext.trace ?? null;
|
|
373
|
+
const brass = runtime.env?.brass;
|
|
374
|
+
if (previousTrace?.sampled === false && brass?.respectRemoteSampled !== false) return void 0;
|
|
375
|
+
const name = spanName(req, options.spans);
|
|
376
|
+
const tracer = resolveRuntimeTracer(runtime);
|
|
377
|
+
let traceId = previousTrace?.traceId;
|
|
378
|
+
let sampled = previousTrace?.sampled;
|
|
379
|
+
let attributes;
|
|
380
|
+
if (!previousTrace && configuredSampleRate === void 0) {
|
|
381
|
+
const ratio = traceSamplerRatio(brass?.sampler);
|
|
382
|
+
if (ratio !== void 0) {
|
|
383
|
+
if (ratio <= 0) return void 0;
|
|
384
|
+
if (ratio < 1 && Math.random() >= ratio) return void 0;
|
|
385
|
+
sampled = true;
|
|
386
|
+
}
|
|
387
|
+
traceId = tracer.newTraceId();
|
|
388
|
+
} else if (!previousTrace) {
|
|
389
|
+
sampled = true;
|
|
390
|
+
traceId = tracer.newTraceId();
|
|
391
|
+
}
|
|
392
|
+
attributes = spanStartAttributes(req, options);
|
|
393
|
+
if (sampled === void 0) {
|
|
394
|
+
traceId = traceId ?? tracer.newTraceId();
|
|
395
|
+
sampled = shouldSampleWith(brass?.sampler, {
|
|
396
|
+
traceId,
|
|
397
|
+
spanName: name,
|
|
398
|
+
parentSampled: previousTrace?.sampled,
|
|
399
|
+
attributes
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
if (sampled === false) return void 0;
|
|
403
|
+
const trace = {
|
|
404
|
+
traceId: traceId ?? tracer.newTraceId(),
|
|
405
|
+
spanId: tracer.newSpanId(),
|
|
406
|
+
parentSpanId: previousTrace?.spanId,
|
|
407
|
+
sampled: true,
|
|
408
|
+
traceState: previousTrace?.traceState,
|
|
409
|
+
...previousTrace?.baggage ? { baggage: previousTrace.baggage } : {}
|
|
410
|
+
};
|
|
411
|
+
const state = {
|
|
412
|
+
fiber,
|
|
413
|
+
sink,
|
|
414
|
+
previousTrace,
|
|
415
|
+
trace,
|
|
416
|
+
name,
|
|
417
|
+
ended: false
|
|
418
|
+
};
|
|
419
|
+
fiber.fiberContext = { ...fiber.fiberContext, trace };
|
|
420
|
+
fiber.addFinalizer?.((exit) => {
|
|
421
|
+
const status = exit?._tag === "Success" ? "success" : exit?.cause && Cause.containsInterrupt(exit.cause) ? "interrupted" : "failure";
|
|
422
|
+
finishLeanHttpSpan(state, status, exit?._tag === "Failure" ? exit.cause : void 0);
|
|
423
|
+
});
|
|
424
|
+
sink.emit(
|
|
425
|
+
{ type: "span.start", name, attributes, links: [] },
|
|
426
|
+
leanSpanContext(fiber, trace)
|
|
427
|
+
);
|
|
428
|
+
return state;
|
|
429
|
+
}
|
|
430
|
+
function finishLeanHttpSpan(state, status, error) {
|
|
431
|
+
if (!state || state.ended) return;
|
|
432
|
+
state.ended = true;
|
|
433
|
+
state.sink.emit(
|
|
434
|
+
{ type: "span.end", name: state.name, status, error },
|
|
435
|
+
leanSpanContext(state.fiber, state.trace)
|
|
436
|
+
);
|
|
437
|
+
if (state.fiber?.fiberContext) {
|
|
438
|
+
state.fiber.fiberContext = { ...state.fiber.fiberContext, trace: state.previousTrace };
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function leanSpanContext(fiber, trace) {
|
|
442
|
+
return {
|
|
443
|
+
fiberId: fiber?.id,
|
|
444
|
+
scopeId: fiber?.scopeId,
|
|
445
|
+
traceId: trace.traceId,
|
|
446
|
+
spanId: trace.spanId,
|
|
447
|
+
parentSpanId: trace.parentSpanId,
|
|
448
|
+
traceState: trace.traceState,
|
|
449
|
+
baggage: trace.baggage,
|
|
450
|
+
sampled: trace.sampled
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function resolveRuntimeTracer(runtime) {
|
|
454
|
+
const tracer = runtime?.env?.brass?.tracer;
|
|
455
|
+
if (tracer?.newTraceId && tracer?.newSpanId) return tracer;
|
|
456
|
+
return {
|
|
457
|
+
newTraceId: () => randomRuntimeId("trace"),
|
|
458
|
+
newSpanId: () => randomRuntimeId("span")
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function traceSamplerRatio(sampler) {
|
|
462
|
+
if (!sampler || typeof sampler === "function") return void 0;
|
|
463
|
+
const ratio = sampler[TRACE_SAMPLER_RATIO];
|
|
464
|
+
return typeof ratio === "number" && Number.isFinite(ratio) ? ratio : void 0;
|
|
465
|
+
}
|
|
466
|
+
function randomRuntimeId(prefix) {
|
|
467
|
+
const cryptoLike = globalThis.crypto;
|
|
468
|
+
if (typeof cryptoLike?.randomUUID === "function") return cryptoLike.randomUUID();
|
|
469
|
+
return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
470
|
+
}
|
|
471
|
+
function httpErrorFromCause(cause) {
|
|
472
|
+
const failure = Cause.firstFailure(cause);
|
|
473
|
+
if (failure._tag === "Some") return failure.value;
|
|
474
|
+
if (Cause.containsInterrupt(cause)) return { _tag: "Abort" };
|
|
475
|
+
const defect = Cause.firstDefect(cause);
|
|
476
|
+
const value = defect._tag === "Some" ? defect.value : Cause.toError(cause);
|
|
477
|
+
return {
|
|
478
|
+
_tag: "FetchError",
|
|
479
|
+
message: value instanceof Error ? value.message : String(value),
|
|
480
|
+
cause: value
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
function makeHttpMetricCache() {
|
|
484
|
+
return {
|
|
485
|
+
disabled: false,
|
|
486
|
+
leanEntries: /* @__PURE__ */ new Map(),
|
|
487
|
+
inFlightGauges: /* @__PURE__ */ new Map(),
|
|
488
|
+
requestCounters: /* @__PURE__ */ new Map(),
|
|
489
|
+
durationHistograms: /* @__PURE__ */ new Map()
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
function attachMetricCache(metrics, cache) {
|
|
493
|
+
if (!metrics) return;
|
|
494
|
+
const existing = metricResetRegistry.get(metrics);
|
|
495
|
+
if (existing) {
|
|
496
|
+
existing.caches.add(cache);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const entry = {
|
|
500
|
+
caches: /* @__PURE__ */ new Set([cache]),
|
|
501
|
+
reset: metrics.reset.bind(metrics)
|
|
502
|
+
};
|
|
503
|
+
try {
|
|
504
|
+
Object.defineProperty(metrics, "reset", {
|
|
505
|
+
configurable: true,
|
|
506
|
+
value: () => {
|
|
507
|
+
for (const cached of entry.caches) clearHttpMetricCache(cached);
|
|
508
|
+
entry.reset();
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
metricResetRegistry.set(metrics, entry);
|
|
512
|
+
} catch {
|
|
513
|
+
cache.disabled = true;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function clearHttpMetricCache(cache) {
|
|
517
|
+
cache.leanEntries.clear();
|
|
518
|
+
cache.inFlightGauges.clear();
|
|
519
|
+
cache.requestCounters.clear();
|
|
520
|
+
cache.durationHistograms.clear();
|
|
521
|
+
}
|
|
522
|
+
function getCachedMetric(owner, cache, key, create) {
|
|
523
|
+
if (owner.disabled) return create();
|
|
524
|
+
const existing = cache.get(key);
|
|
525
|
+
if (existing) return existing;
|
|
526
|
+
const created = create();
|
|
527
|
+
if (cache.size < MAX_HTTP_METRIC_CACHE_ENTRIES) {
|
|
528
|
+
cache.set(key, created);
|
|
529
|
+
}
|
|
530
|
+
return created;
|
|
531
|
+
}
|
|
532
|
+
function metricCacheKey(name, labels) {
|
|
533
|
+
let key = name;
|
|
534
|
+
for (const [label, value] of Object.entries(labels)) {
|
|
535
|
+
key += `|${label.length}:${label}=${value.length}:${value}`;
|
|
536
|
+
}
|
|
537
|
+
return key;
|
|
538
|
+
}
|
|
539
|
+
function beginHttpObservation(req, options, metricCache) {
|
|
150
540
|
return asyncSync(() => {
|
|
151
541
|
const startedAt = options.clock();
|
|
152
542
|
let finished = false;
|
|
153
|
-
const
|
|
543
|
+
const baseLabels = requestBaseLabels(req, options);
|
|
544
|
+
const inFlight = options.metrics ? getCachedMetric(
|
|
545
|
+
metricCache,
|
|
546
|
+
metricCache.inFlightGauges,
|
|
547
|
+
metricCacheKey("brass_http_client_in_flight", baseLabels),
|
|
548
|
+
() => options.metrics.gauge("brass_http_client_in_flight", baseLabels)
|
|
549
|
+
) : void 0;
|
|
154
550
|
inFlight?.increment();
|
|
155
551
|
const finish = (outcome, status, extra = {}) => {
|
|
156
552
|
const durationMs = Math.max(0, options.clock() - startedAt);
|
|
157
|
-
const labels =
|
|
553
|
+
const labels = requestFinishLabelsFromBase(baseLabels, outcome, status);
|
|
158
554
|
if (!finished) {
|
|
159
555
|
finished = true;
|
|
160
556
|
if (inFlight && inFlight.value() > 0) inFlight.decrement();
|
|
161
|
-
options.metrics
|
|
162
|
-
|
|
557
|
+
if (options.metrics) {
|
|
558
|
+
getCachedMetric(
|
|
559
|
+
metricCache,
|
|
560
|
+
metricCache.requestCounters,
|
|
561
|
+
metricCacheKey("brass_http_client_requests_total", labels),
|
|
562
|
+
() => options.metrics.counter("brass_http_client_requests_total", labels)
|
|
563
|
+
).increment();
|
|
564
|
+
getCachedMetric(
|
|
565
|
+
metricCache,
|
|
566
|
+
metricCache.durationHistograms,
|
|
567
|
+
metricCacheKey("brass_http_client_duration_ms", labels),
|
|
568
|
+
() => options.metrics.histogram(
|
|
569
|
+
"brass_http_client_duration_ms",
|
|
570
|
+
[...options.durationBuckets ?? DEFAULT_DURATION_BUCKETS],
|
|
571
|
+
labels
|
|
572
|
+
)
|
|
573
|
+
).observe(
|
|
574
|
+
durationMs,
|
|
575
|
+
options.spans === false ? void 0 : currentTraceExemplar(durationMs, startedAt + durationMs)
|
|
576
|
+
);
|
|
577
|
+
}
|
|
163
578
|
}
|
|
164
579
|
return {
|
|
165
580
|
durationMs,
|
|
166
581
|
outcome,
|
|
167
582
|
labels,
|
|
168
|
-
spanAttributes: {
|
|
583
|
+
spanAttributes: options.spans === false ? EMPTY_SPAN_ATTRIBUTES : {
|
|
169
584
|
"http.duration_ms": durationMs,
|
|
170
585
|
"http.outcome": outcome,
|
|
171
586
|
...status ? { "http.status_code": Number(status) } : {},
|
|
@@ -234,6 +649,7 @@ function resolveHttpObservabilityOptions(options) {
|
|
|
234
649
|
metrics: raw?.metrics === false ? void 0 : raw?.metrics,
|
|
235
650
|
logs: raw?.logs ?? {},
|
|
236
651
|
spans: raw?.spans ?? {},
|
|
652
|
+
spanSink: raw?.spanSink === false ? void 0 : raw?.spanSink,
|
|
237
653
|
adaptiveLimiter: resolveAdaptiveLimiterObservabilityOptions(raw?.adaptiveLimiter),
|
|
238
654
|
policy: resolveHttpPolicyObservabilityOptions(raw?.policy),
|
|
239
655
|
injectTraceHeaders: raw?.injectTraceHeaders ?? true,
|
|
@@ -330,6 +746,9 @@ function spanName(req, options) {
|
|
|
330
746
|
}
|
|
331
747
|
return `HTTP ${req.method}`;
|
|
332
748
|
}
|
|
749
|
+
function shouldEmitHttpSpanEvents(options) {
|
|
750
|
+
return options.spans !== false && options.spans.events !== false;
|
|
751
|
+
}
|
|
333
752
|
function spanStartAttributes(req, options) {
|
|
334
753
|
const spanOptions = options.spans === false ? {} : options.spans.attributes;
|
|
335
754
|
const custom = typeof spanOptions === "function" ? spanOptions(req) : spanOptions ?? {};
|
|
@@ -361,9 +780,9 @@ function requestBaseLabels(req, options) {
|
|
|
361
780
|
...requestPolicyMetricLabels(req, options)
|
|
362
781
|
});
|
|
363
782
|
}
|
|
364
|
-
function
|
|
783
|
+
function requestFinishLabelsFromBase(baseLabels, outcome, status) {
|
|
365
784
|
return {
|
|
366
|
-
...
|
|
785
|
+
...baseLabels,
|
|
367
786
|
outcome,
|
|
368
787
|
status: status ?? "none"
|
|
369
788
|
};
|
|
@@ -759,6 +1178,88 @@ function readProcessEnv() {
|
|
|
759
1178
|
return typeof process !== "undefined" ? process.env : {};
|
|
760
1179
|
}
|
|
761
1180
|
|
|
1181
|
+
// src/observability/layer.ts
|
|
1182
|
+
var ObservabilityService = makeServiceTag("Observability");
|
|
1183
|
+
function makeObservabilityLayer(config = {}, options = {}) {
|
|
1184
|
+
const tag = options.tag ?? ObservabilityService;
|
|
1185
|
+
return layerEffect(
|
|
1186
|
+
tag,
|
|
1187
|
+
(context) => asyncSync(() => makeObservability(resolveObservabilityLayerConfig(config, context))),
|
|
1188
|
+
shutdownObservability
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
function makeObservedRuntimeLayer(options = {}) {
|
|
1192
|
+
const {
|
|
1193
|
+
observabilityTag = ObservabilityService,
|
|
1194
|
+
runtimeTag = RuntimeService,
|
|
1195
|
+
env = {},
|
|
1196
|
+
...runtimeOptions
|
|
1197
|
+
} = options;
|
|
1198
|
+
return layerEffect(
|
|
1199
|
+
runtimeTag,
|
|
1200
|
+
(context) => asyncSync(() => {
|
|
1201
|
+
const observability = context.unsafeGet(observabilityTag);
|
|
1202
|
+
return new Runtime({
|
|
1203
|
+
...runtimeOptions,
|
|
1204
|
+
env: mergeRuntimeEnv(resolveObservedRuntimeLayerEnv(env, context, observability), observability.env),
|
|
1205
|
+
hooks: observability.hooks
|
|
1206
|
+
});
|
|
1207
|
+
})
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
function makeObservedHttpClientLayer(config = {}, options = {}) {
|
|
1211
|
+
const observabilityTag = options.observabilityTag ?? ObservabilityService;
|
|
1212
|
+
const httpTag = options.httpTag ?? HttpClientService;
|
|
1213
|
+
return layerEffect(
|
|
1214
|
+
httpTag,
|
|
1215
|
+
(context) => asyncSync(() => {
|
|
1216
|
+
const observability = context.unsafeGet(observabilityTag);
|
|
1217
|
+
const httpConfig = resolveObservedHttpClientLayerConfig(config, context, observability);
|
|
1218
|
+
const httpObservability = resolveObservedHttpObservabilityOptions(
|
|
1219
|
+
options.httpObservability,
|
|
1220
|
+
context,
|
|
1221
|
+
observability
|
|
1222
|
+
);
|
|
1223
|
+
return makeDefaultHttpClient({
|
|
1224
|
+
...httpConfig,
|
|
1225
|
+
middleware: [
|
|
1226
|
+
...httpConfig.middleware ?? [],
|
|
1227
|
+
withHttpObservability({
|
|
1228
|
+
metrics: observability.metrics,
|
|
1229
|
+
...httpObservability
|
|
1230
|
+
})
|
|
1231
|
+
]
|
|
1232
|
+
});
|
|
1233
|
+
}),
|
|
1234
|
+
shutdownDefaultHttpClient
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
function resolveObservabilityLayerConfig(config, context) {
|
|
1238
|
+
return typeof config === "function" ? config(context) : config;
|
|
1239
|
+
}
|
|
1240
|
+
function resolveObservedHttpClientLayerConfig(config, context, observability) {
|
|
1241
|
+
return typeof config === "function" ? config(context, observability) : config;
|
|
1242
|
+
}
|
|
1243
|
+
function resolveObservedHttpObservabilityOptions(options, context, observability) {
|
|
1244
|
+
return typeof options === "function" ? options(context, observability) : options ?? {};
|
|
1245
|
+
}
|
|
1246
|
+
function resolveObservedRuntimeLayerEnv(env, context, observability) {
|
|
1247
|
+
return typeof env === "function" ? env(context, observability) : env;
|
|
1248
|
+
}
|
|
1249
|
+
function mergeRuntimeEnv(env, observabilityEnv) {
|
|
1250
|
+
return Object.assign({}, env, observabilityEnv);
|
|
1251
|
+
}
|
|
1252
|
+
function shutdownObservability(observability) {
|
|
1253
|
+
return fromPromiseAbortable(
|
|
1254
|
+
() => observability.shutdown().then(() => void 0),
|
|
1255
|
+
() => void 0,
|
|
1256
|
+
{ label: "observability.shutdown" }
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
function shutdownDefaultHttpClient(client) {
|
|
1260
|
+
return client.shutdown();
|
|
1261
|
+
}
|
|
1262
|
+
|
|
762
1263
|
export {
|
|
763
1264
|
HTTP_OBSERVABILITY_CONTRACT,
|
|
764
1265
|
withHttpObservability,
|
|
@@ -773,5 +1274,9 @@ export {
|
|
|
773
1274
|
makeObservabilityPreset,
|
|
774
1275
|
makeObservabilityFromEnv,
|
|
775
1276
|
observabilityOptionsForPreset,
|
|
776
|
-
observabilityOptionsFromEnv
|
|
1277
|
+
observabilityOptionsFromEnv,
|
|
1278
|
+
ObservabilityService,
|
|
1279
|
+
makeObservabilityLayer,
|
|
1280
|
+
makeObservedRuntimeLayer,
|
|
1281
|
+
makeObservedHttpClientLayer
|
|
777
1282
|
};
|