@capxul/observability 4.20.0-beta.3 → 4.20.0-beta.31
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 +79 -0
- package/dist/engineering.d.mts +22 -16
- package/dist/engineering.mjs +362 -263
- package/dist/index.d.mts +25234 -3288
- package/dist/index.mjs +2 -2
- package/dist/src-L9SjUzWV.mjs +9634 -0
- package/package.json +12 -9
- package/dist/src-CyMef9yy.mjs +0 -3078
package/dist/engineering.mjs
CHANGED
|
@@ -1,8 +1,190 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { I as CLI_CAPABILITIES, L as CLI_COMMAND_NAMES, R as CLI_INVOCATION_KINDS, an as isCapxulOperation, cn as ORGANIZATION_SETUP_STATUSES, dn as isCredentialField, fn as CAPXUL_ERROR_CODES, ln as redactSecrets, pn as FAILURE_MODES, sn as ORGANIZATION_SETUP_REASONS, un as redactUrlSecrets } from "./src-L9SjUzWV.mjs";
|
|
2
2
|
import { Cause, Clock, Context, Deferred, Duration, Effect, Exit, FiberSet, Layer, LogLevel, Logger, Metric, Option, References, Schedule, Stream, Tracer } from "effect";
|
|
3
|
-
import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientResponse } from "effect/
|
|
4
|
-
import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/
|
|
5
|
-
//#region src/
|
|
3
|
+
import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientResponse } from "effect/http";
|
|
4
|
+
import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/observability";
|
|
5
|
+
//#region ../protocol/src/primitives/record.ts
|
|
6
|
+
/** A non-null object that is not an array: a value whose fields can be read by name. */
|
|
7
|
+
function isRecord(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
/** The field `key` of `value` when `value` is a record, else undefined. */
|
|
11
|
+
function readField(value, key) {
|
|
12
|
+
return isRecord(value) ? value[key] : void 0;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/delivery/admission.ts
|
|
16
|
+
function newDeliveryState(budget, ownsMetrics) {
|
|
17
|
+
return {
|
|
18
|
+
budget,
|
|
19
|
+
ownsMetrics,
|
|
20
|
+
closed: Deferred.makeUnsafe(),
|
|
21
|
+
isClosed: false,
|
|
22
|
+
metricsRunning: false,
|
|
23
|
+
logs: [],
|
|
24
|
+
spans: /* @__PURE__ */ new Map(),
|
|
25
|
+
terminalBodies: /* @__PURE__ */ new WeakSet(),
|
|
26
|
+
bodyLeases: /* @__PURE__ */ new WeakMap(),
|
|
27
|
+
pendingBodies: /* @__PURE__ */ new Set()
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Hold the records a body carries until its request finishes or the body is discarded. */
|
|
31
|
+
function holdBody(state, body, releases) {
|
|
32
|
+
if (releases.length === 0) return;
|
|
33
|
+
const lease = {
|
|
34
|
+
started: false,
|
|
35
|
+
release: () => {
|
|
36
|
+
if (!state.pendingBodies.delete(lease)) return;
|
|
37
|
+
state.bodyLeases.delete(body);
|
|
38
|
+
for (const release of releases) release();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
state.bodyLeases.set(body, lease);
|
|
42
|
+
state.pendingBodies.add(lease);
|
|
43
|
+
}
|
|
44
|
+
const discardBody = (state, body) => state.bodyLeases.get(body)?.release();
|
|
45
|
+
const drainLogs = (state) => {
|
|
46
|
+
for (const release of state.logs.splice(0)) release();
|
|
47
|
+
};
|
|
48
|
+
function closeDelivery(state) {
|
|
49
|
+
state.isClosed = true;
|
|
50
|
+
Deferred.doneUnsafe(state.closed, Effect.void);
|
|
51
|
+
for (const lease of state.pendingBodies) if (!lease.started) lease.release();
|
|
52
|
+
drainLogs(state);
|
|
53
|
+
for (const release of state.spans.values()) release();
|
|
54
|
+
state.spans.clear();
|
|
55
|
+
if (state.ownsMetrics) state.budget.metricRegistry.clear();
|
|
56
|
+
}
|
|
57
|
+
const text = (value) => typeof value === "string" ? value.slice(0, 128) : "capxul.engineering";
|
|
58
|
+
const attributes = (input, limit = 32) => {
|
|
59
|
+
const out = {};
|
|
60
|
+
let count = 0;
|
|
61
|
+
let bytes = 0;
|
|
62
|
+
try {
|
|
63
|
+
for (const key in input) {
|
|
64
|
+
if (count++ >= limit) break;
|
|
65
|
+
if (key.length > 64) continue;
|
|
66
|
+
const value = Object.getOwnPropertyDescriptor(input, key)?.value;
|
|
67
|
+
const scalar = typeof value === "string" ? value.slice(0, 128) : typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
68
|
+
if (scalar === void 0) continue;
|
|
69
|
+
bytes += new TextEncoder().encode(JSON.stringify([key, scalar])).length;
|
|
70
|
+
if (bytes > (limit >= 24 ? 2e3 : 300)) break;
|
|
71
|
+
out[key] = scalar;
|
|
72
|
+
}
|
|
73
|
+
} catch {}
|
|
74
|
+
return out;
|
|
75
|
+
};
|
|
76
|
+
const terminalName = (name) => name === "cli.command.completed" || name === "cli.wallet.step" || CLI_COMMAND_NAMES.some((command) => name === `cli.command.${command}`);
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/delivery/records.ts
|
|
79
|
+
function admittedLogger(state, delegate) {
|
|
80
|
+
return Logger.make((options) => {
|
|
81
|
+
if (state.isClosed) return;
|
|
82
|
+
let release;
|
|
83
|
+
try {
|
|
84
|
+
const message = text(Array.isArray(options.message) ? Object.getOwnPropertyDescriptor(options.message, "0")?.value : options.message);
|
|
85
|
+
release = state.budget.admitRecord(terminalName(message));
|
|
86
|
+
if (release === void 0) return;
|
|
87
|
+
const context = Context.add(options.fiber.context, References.CurrentLogAnnotations, attributes(options.fiber.getRef(References.CurrentLogAnnotations)));
|
|
88
|
+
const fiber = Object.create(options.fiber);
|
|
89
|
+
Object.defineProperty(fiber, "getRef", { value: (ref) => Context.get(context, ref) });
|
|
90
|
+
state.logs.push(release);
|
|
91
|
+
delegate.log({
|
|
92
|
+
...options,
|
|
93
|
+
message,
|
|
94
|
+
cause: Cause.empty,
|
|
95
|
+
fiber
|
|
96
|
+
});
|
|
97
|
+
} catch {
|
|
98
|
+
if (release !== void 0) {
|
|
99
|
+
const index = state.logs.indexOf(release);
|
|
100
|
+
if (index !== -1) state.logs.splice(index, 1);
|
|
101
|
+
release();
|
|
102
|
+
}
|
|
103
|
+
state.budget.drop();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function admittedTracer(state, delegate) {
|
|
108
|
+
return Tracer.make({
|
|
109
|
+
span(options) {
|
|
110
|
+
const name = text(options.name);
|
|
111
|
+
const release = state.isClosed ? void 0 : state.budget.admitRecord(terminalName(name));
|
|
112
|
+
const spanOptions = {
|
|
113
|
+
...options,
|
|
114
|
+
name,
|
|
115
|
+
annotations: Context.empty(),
|
|
116
|
+
links: []
|
|
117
|
+
};
|
|
118
|
+
const span = release === void 0 ? new Tracer.NativeSpan({
|
|
119
|
+
...spanOptions,
|
|
120
|
+
sampled: false
|
|
121
|
+
}) : delegate.span(spanOptions);
|
|
122
|
+
if (release !== void 0) state.spans.set(span.spanId, release);
|
|
123
|
+
const wrapped = boundedSpan(state, span);
|
|
124
|
+
wrapped.addLinks(options.links ?? []);
|
|
125
|
+
return wrapped;
|
|
126
|
+
},
|
|
127
|
+
...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/** The span as a producer sees it: at most 32 fields and 2000 attribute bytes, 2 events, 2 links. */
|
|
131
|
+
function boundedSpan(state, span) {
|
|
132
|
+
const { budget } = state;
|
|
133
|
+
const wrapped = Object.create(span);
|
|
134
|
+
let fields = 0, events = 0, links = 0, attributeBytes = 0;
|
|
135
|
+
let ended = false;
|
|
136
|
+
Object.defineProperties(wrapped, {
|
|
137
|
+
attribute: { value: (key, value) => {
|
|
138
|
+
if (fields >= 32 && !span.attributes.has(key)) {
|
|
139
|
+
budget.drop();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const safe = attributes({ [key]: value }, 1);
|
|
143
|
+
if (!Object.hasOwn(safe, key)) return;
|
|
144
|
+
const previous = span.attributes.has(key) ? new TextEncoder().encode(JSON.stringify([key, span.attributes.get(key)])).length : 0;
|
|
145
|
+
const next = new TextEncoder().encode(JSON.stringify([key, safe[key]])).length;
|
|
146
|
+
if (attributeBytes - previous + next > 2e3) {
|
|
147
|
+
budget.drop();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
attributeBytes += next - previous;
|
|
151
|
+
if (!span.attributes.has(key)) fields += 1;
|
|
152
|
+
span.attribute(key, safe[key]);
|
|
153
|
+
} },
|
|
154
|
+
event: { value: (eventName, time, attrs) => {
|
|
155
|
+
if (events++ >= 2) {
|
|
156
|
+
budget.drop();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
span.event(text(eventName), time, attributes(attrs ?? {}, 4));
|
|
160
|
+
} },
|
|
161
|
+
addLinks: { value: (items) => {
|
|
162
|
+
for (const item of items.slice(0, Math.max(0, 2 - links))) {
|
|
163
|
+
if (!/^[a-f0-9]{32}$/u.test(item.span.traceId) || !/^[a-f0-9]{16}$/u.test(item.span.spanId)) {
|
|
164
|
+
budget.drop();
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
span.addLinks([{
|
|
168
|
+
span: Tracer.externalSpan({
|
|
169
|
+
traceId: item.span.traceId,
|
|
170
|
+
spanId: item.span.spanId,
|
|
171
|
+
sampled: item.span.sampled
|
|
172
|
+
}),
|
|
173
|
+
attributes: attributes(item.attributes, 4)
|
|
174
|
+
}]);
|
|
175
|
+
links += 1;
|
|
176
|
+
}
|
|
177
|
+
} },
|
|
178
|
+
end: { value: (time, exit) => {
|
|
179
|
+
if (ended) return;
|
|
180
|
+
ended = true;
|
|
181
|
+
span.end(time, Exit.isFailure(exit) ? Exit.fail("CLI operation failed") : Exit.void);
|
|
182
|
+
} }
|
|
183
|
+
});
|
|
184
|
+
return wrapped;
|
|
185
|
+
}
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/delivery/budget.ts
|
|
6
188
|
/** Fixed admission limits for one CLI invocation and its SDK client. */
|
|
7
189
|
const CLI_ENGINEERING_LIMITS = Object.freeze({
|
|
8
190
|
records: 80,
|
|
@@ -52,9 +234,8 @@ var CliMetricRegistry = class extends Map {
|
|
|
52
234
|
return this;
|
|
53
235
|
}
|
|
54
236
|
const labels = Object.entries(entry.attributes ?? {});
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
if (!["Counter", "Histogram"].includes(entry.type) || (entry.description?.length ?? 0) > 128 || labels.length > 6 || labels.some(([name, value]) => !metricValues[name]?.includes(String(value))) || entry.type === "Histogram" && (!Array.isArray(buckets) || buckets.length > 32)) {
|
|
237
|
+
const buckets = readField(entry.hooks.get(Context.empty()), "buckets");
|
|
238
|
+
if (!["Counter", "Histogram"].includes(entry.type) || (entry.description?.length ?? 0) > 128 || labels.length > 6 || labels.some(([name, value]) => !metricValues[name]?.includes(value)) || entry.type === "Histogram" && (!Array.isArray(buckets) || buckets.length > 32)) {
|
|
58
239
|
this.dropped();
|
|
59
240
|
return this;
|
|
60
241
|
}
|
|
@@ -118,266 +299,123 @@ function makeCliEngineeringBudget() {
|
|
|
118
299
|
}
|
|
119
300
|
});
|
|
120
301
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
bytes += new TextEncoder().encode(JSON.stringify([key, scalar])).length;
|
|
134
|
-
if (bytes > (limit >= 24 ? 2e3 : 300)) break;
|
|
135
|
-
out[key] = scalar;
|
|
136
|
-
}
|
|
137
|
-
} catch {}
|
|
138
|
-
return out;
|
|
139
|
-
};
|
|
140
|
-
const terminalName = (name) => name === "cli.command.completed" || CLI_COMMAND_NAMES.some((command) => name === `cli.command.${command}`);
|
|
141
|
-
/** Native exporters retain their serialization and scheduling; this owns admission only. */
|
|
142
|
-
function cliEngineeringDelivery(budget, ownsMetrics) {
|
|
143
|
-
const closed = Deferred.makeUnsafe();
|
|
144
|
-
let isClosed = false;
|
|
145
|
-
let metricsRunning = false;
|
|
146
|
-
const logs = [];
|
|
147
|
-
const spans = /* @__PURE__ */ new Map();
|
|
148
|
-
const terminalBodies = /* @__PURE__ */ new WeakSet();
|
|
149
|
-
const bodyLeases = /* @__PURE__ */ new WeakMap();
|
|
150
|
-
const pendingBodies = /* @__PURE__ */ new Set();
|
|
151
|
-
const holdBody = (body, releases) => {
|
|
152
|
-
if (releases.length === 0) return;
|
|
153
|
-
const lease = {
|
|
154
|
-
started: false,
|
|
155
|
-
release: () => {
|
|
156
|
-
if (!pendingBodies.delete(lease)) return;
|
|
157
|
-
bodyLeases.delete(body);
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/delivery/requests.ts
|
|
304
|
+
function admittedSerialization(state, delegate) {
|
|
305
|
+
return {
|
|
306
|
+
logs(data) {
|
|
307
|
+
const releases = state.logs.splice(0);
|
|
308
|
+
try {
|
|
309
|
+
const body = delegate.logs(data);
|
|
310
|
+
if (data.resourceLogs.some((r) => r.scopeLogs.some((s) => s.logRecords?.some((l) => terminalName(l.body?.stringValue ?? ""))))) state.terminalBodies.add(body);
|
|
311
|
+
holdBody(state, body, releases);
|
|
312
|
+
return body;
|
|
313
|
+
} catch (error) {
|
|
158
314
|
for (const release of releases) release();
|
|
315
|
+
throw error;
|
|
159
316
|
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
317
|
+
},
|
|
318
|
+
traces(data) {
|
|
319
|
+
const releases = [];
|
|
320
|
+
for (const resource of data.resourceSpans) for (const scope of resource.scopeSpans) for (const span of scope.spans) {
|
|
321
|
+
const release = state.spans.get(span.spanId);
|
|
322
|
+
if (release !== void 0) releases.push(release);
|
|
323
|
+
state.spans.delete(span.spanId);
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
const body = delegate.traces(data);
|
|
327
|
+
if (data.resourceSpans.some((r) => r.scopeSpans.some((s) => s.spans.some((span) => terminalName(span.name))))) state.terminalBodies.add(body);
|
|
328
|
+
holdBody(state, body, releases);
|
|
329
|
+
return body;
|
|
330
|
+
} catch (error) {
|
|
331
|
+
for (const release of releases) release();
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
metrics(data) {
|
|
336
|
+
const body = delegate.metrics(data);
|
|
337
|
+
state.terminalBodies.add(body);
|
|
338
|
+
return body;
|
|
339
|
+
}
|
|
176
340
|
};
|
|
341
|
+
}
|
|
342
|
+
/** A 429's Retry-After, bounded to two seconds; any other failure retries at once. */
|
|
343
|
+
const retryAfter = Schedule.spaced("100 millis").pipe(Schedule.addDelay(({ input }) => Effect.map(Clock.currentTimeMillis, (now) => {
|
|
344
|
+
if (!HttpClientError.isHttpClientError(input) || input.reason._tag !== "StatusCodeError" || input.reason.response.status !== 429) return Duration.zero;
|
|
345
|
+
const header = input.reason.response.headers["retry-after"];
|
|
346
|
+
const numeric = Number(header);
|
|
347
|
+
const delay = header === void 0 ? 2e3 : Number.isFinite(numeric) ? numeric * 1e3 : Date.parse(header) - now;
|
|
348
|
+
return Duration.millis(Number.isFinite(delay) ? Math.min(2e3, Math.max(0, delay)) : 2e3);
|
|
349
|
+
})));
|
|
350
|
+
function admittedTransport(state, client, enabled = () => true) {
|
|
351
|
+
const { budget } = state;
|
|
352
|
+
const retrying = HttpClient.transform(client, (response, request) => Effect.suspend(() => {
|
|
353
|
+
if (enabled()) return response;
|
|
354
|
+
budget.drop();
|
|
355
|
+
return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
|
|
356
|
+
})).pipe(HttpClient.transformResponse((response) => Effect.tap(response, (result) => {
|
|
357
|
+
let received = 0;
|
|
358
|
+
return Stream.runForEach(result.stream, (chunk) => Effect.sync(() => {
|
|
359
|
+
received += chunk.byteLength;
|
|
360
|
+
if (received > 65536) throw new Error("Engineering response limit exceeded");
|
|
361
|
+
})).pipe(Effect.catchReason("HttpClientError", "EmptyBodyError", () => Effect.void));
|
|
362
|
+
}))).pipe(HttpClient.filterStatusOk, HttpClient.retryTransient({
|
|
363
|
+
times: 1,
|
|
364
|
+
schedule: retryAfter
|
|
365
|
+
}));
|
|
366
|
+
return HttpClient.transform(retrying, (response, request) => Effect.suspend(() => {
|
|
367
|
+
if (state.isClosed) {
|
|
368
|
+
discardBody(state, request.body);
|
|
369
|
+
return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
|
|
370
|
+
}
|
|
371
|
+
const droppedResponse = () => HttpClientResponse.fromWeb(request, new Response(null, { status: 200 }));
|
|
372
|
+
if (request.body._tag === "Empty") {
|
|
373
|
+
discardBody(state, request.body);
|
|
374
|
+
return Effect.succeed(droppedResponse());
|
|
375
|
+
}
|
|
376
|
+
const bytes = request.body.contentLength;
|
|
377
|
+
const release = budget.admitRequest(bytes ?? Number.POSITIVE_INFINITY, state.terminalBodies.has(request.body));
|
|
378
|
+
if (release === void 0) {
|
|
379
|
+
discardBody(state, request.body);
|
|
380
|
+
return Effect.succeed(droppedResponse());
|
|
381
|
+
}
|
|
382
|
+
const lease = state.bodyLeases.get(request.body);
|
|
383
|
+
if (lease !== void 0) lease.started = true;
|
|
384
|
+
return response.pipe(Effect.asVoid, Effect.timeoutOption(CLI_ENGINEERING_LIMITS.requestTimeoutMs), Effect.raceFirst(Deferred.await(state.closed).pipe(Effect.andThen(Effect.interrupt))), Effect.tap((result) => Effect.sync(() => {
|
|
385
|
+
if (Option.isNone(result)) budget.drop();
|
|
386
|
+
})), Effect.catchCause(() => Effect.sync(() => {
|
|
387
|
+
budget.drop();
|
|
388
|
+
})), Effect.as(droppedResponse()), Effect.ensuring(Effect.sync(() => {
|
|
389
|
+
release();
|
|
390
|
+
discardBody(state, request.body);
|
|
391
|
+
})));
|
|
392
|
+
}));
|
|
393
|
+
}
|
|
394
|
+
//#endregion
|
|
395
|
+
//#region src/engineering-delivery.ts
|
|
396
|
+
/** Native exporters retain their serialization and scheduling; this owns admission only. */
|
|
397
|
+
function cliEngineeringDelivery(budget, ownsMetrics) {
|
|
398
|
+
const state = newDeliveryState(budget, ownsMetrics);
|
|
177
399
|
return {
|
|
178
|
-
close,
|
|
179
|
-
discardBody,
|
|
400
|
+
close: () => closeDelivery(state),
|
|
401
|
+
discardBody: (body) => discardBody(state, body),
|
|
180
402
|
metrics(run) {
|
|
181
403
|
return Effect.suspend(() => {
|
|
182
|
-
if (isClosed || metricsRunning) return Effect.void;
|
|
183
|
-
metricsRunning = true;
|
|
404
|
+
if (state.isClosed || state.metricsRunning) return Effect.void;
|
|
405
|
+
state.metricsRunning = true;
|
|
184
406
|
return run.pipe(Effect.ensuring(Effect.sync(() => {
|
|
185
|
-
metricsRunning = false;
|
|
407
|
+
state.metricsRunning = false;
|
|
186
408
|
})));
|
|
187
409
|
});
|
|
188
410
|
},
|
|
189
|
-
logger(delegate)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
try {
|
|
194
|
-
const message = text(Array.isArray(options.message) ? Object.getOwnPropertyDescriptor(options.message, "0")?.value : options.message);
|
|
195
|
-
release = budget.admitRecord(terminalName(message));
|
|
196
|
-
if (release === void 0) return;
|
|
197
|
-
const context = Context.add(options.fiber.context, References.CurrentLogAnnotations, attributes(options.fiber.getRef(References.CurrentLogAnnotations)));
|
|
198
|
-
const fiber = Object.create(options.fiber);
|
|
199
|
-
Object.defineProperty(fiber, "getRef", { value: (ref) => Context.get(context, ref) });
|
|
200
|
-
logs.push(release);
|
|
201
|
-
delegate.log({
|
|
202
|
-
...options,
|
|
203
|
-
message,
|
|
204
|
-
cause: Cause.empty,
|
|
205
|
-
fiber
|
|
206
|
-
});
|
|
207
|
-
} catch {
|
|
208
|
-
if (release !== void 0) {
|
|
209
|
-
const index = logs.indexOf(release);
|
|
210
|
-
if (index !== -1) logs.splice(index, 1);
|
|
211
|
-
release();
|
|
212
|
-
}
|
|
213
|
-
budget.drop();
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
},
|
|
217
|
-
tracer(delegate) {
|
|
218
|
-
return Tracer.make({
|
|
219
|
-
span(options) {
|
|
220
|
-
const name = text(options.name);
|
|
221
|
-
const release = isClosed ? void 0 : budget.admitRecord(terminalName(name));
|
|
222
|
-
const spanOptions = {
|
|
223
|
-
...options,
|
|
224
|
-
name,
|
|
225
|
-
annotations: Context.empty(),
|
|
226
|
-
links: []
|
|
227
|
-
};
|
|
228
|
-
const span = release === void 0 ? new Tracer.NativeSpan({
|
|
229
|
-
...spanOptions,
|
|
230
|
-
sampled: false
|
|
231
|
-
}) : delegate.span(spanOptions);
|
|
232
|
-
if (release !== void 0) spans.set(span.spanId, release);
|
|
233
|
-
const wrapped = Object.create(span);
|
|
234
|
-
let fields = 0, events = 0, links = 0, attributeBytes = 0;
|
|
235
|
-
let ended = false;
|
|
236
|
-
Object.defineProperties(wrapped, {
|
|
237
|
-
attribute: { value: (key, value) => {
|
|
238
|
-
if (fields >= 32 && !span.attributes.has(key)) {
|
|
239
|
-
budget.drop();
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
const safe = attributes({ [key]: value }, 1);
|
|
243
|
-
if (!Object.hasOwn(safe, key)) return;
|
|
244
|
-
const previous = span.attributes.has(key) ? new TextEncoder().encode(JSON.stringify([key, span.attributes.get(key)])).length : 0;
|
|
245
|
-
const next = new TextEncoder().encode(JSON.stringify([key, safe[key]])).length;
|
|
246
|
-
if (attributeBytes - previous + next > 2e3) {
|
|
247
|
-
budget.drop();
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
attributeBytes += next - previous;
|
|
251
|
-
if (!span.attributes.has(key)) fields += 1;
|
|
252
|
-
span.attribute(key, safe[key]);
|
|
253
|
-
} },
|
|
254
|
-
event: { value: (eventName, time, attrs) => {
|
|
255
|
-
if (events++ >= 2) {
|
|
256
|
-
budget.drop();
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
span.event(text(eventName), time, attributes(attrs ?? {}, 4));
|
|
260
|
-
} },
|
|
261
|
-
addLinks: { value: (items) => {
|
|
262
|
-
for (const item of items.slice(0, Math.max(0, 2 - links))) {
|
|
263
|
-
if (!/^[a-f0-9]{32}$/u.test(item.span.traceId) || !/^[a-f0-9]{16}$/u.test(item.span.spanId)) {
|
|
264
|
-
budget.drop();
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
span.addLinks([{
|
|
268
|
-
span: Tracer.externalSpan({
|
|
269
|
-
traceId: item.span.traceId,
|
|
270
|
-
spanId: item.span.spanId,
|
|
271
|
-
sampled: item.span.sampled
|
|
272
|
-
}),
|
|
273
|
-
attributes: attributes(item.attributes, 4)
|
|
274
|
-
}]);
|
|
275
|
-
links += 1;
|
|
276
|
-
}
|
|
277
|
-
} },
|
|
278
|
-
end: { value: (time, exit) => {
|
|
279
|
-
if (ended) return;
|
|
280
|
-
ended = true;
|
|
281
|
-
span.end(time, Exit.isFailure(exit) ? Exit.fail("CLI operation failed") : Exit.void);
|
|
282
|
-
} }
|
|
283
|
-
});
|
|
284
|
-
wrapped.addLinks(options.links ?? []);
|
|
285
|
-
return wrapped;
|
|
286
|
-
},
|
|
287
|
-
...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
|
|
288
|
-
});
|
|
289
|
-
},
|
|
290
|
-
serialization(delegate) {
|
|
291
|
-
return {
|
|
292
|
-
logs(data) {
|
|
293
|
-
const releases = logs.splice(0);
|
|
294
|
-
try {
|
|
295
|
-
const body = delegate.logs(data);
|
|
296
|
-
if (data.resourceLogs.some((r) => r.scopeLogs.some((s) => s.logRecords?.some((l) => l.body?.stringValue === "cli.command.completed")))) terminalBodies.add(body);
|
|
297
|
-
holdBody(body, releases);
|
|
298
|
-
return body;
|
|
299
|
-
} catch (error) {
|
|
300
|
-
for (const release of releases) release();
|
|
301
|
-
throw error;
|
|
302
|
-
}
|
|
303
|
-
},
|
|
304
|
-
traces(data) {
|
|
305
|
-
const releases = [];
|
|
306
|
-
for (const resource of data.resourceSpans) for (const scope of resource.scopeSpans) for (const span of scope.spans) {
|
|
307
|
-
const release = spans.get(span.spanId);
|
|
308
|
-
if (release !== void 0) releases.push(release);
|
|
309
|
-
spans.delete(span.spanId);
|
|
310
|
-
}
|
|
311
|
-
try {
|
|
312
|
-
const body = delegate.traces(data);
|
|
313
|
-
if (data.resourceSpans.some((r) => r.scopeSpans.some((s) => s.spans.some((span) => terminalName(span.name))))) terminalBodies.add(body);
|
|
314
|
-
holdBody(body, releases);
|
|
315
|
-
return body;
|
|
316
|
-
} catch (error) {
|
|
317
|
-
for (const release of releases) release();
|
|
318
|
-
throw error;
|
|
319
|
-
}
|
|
320
|
-
},
|
|
321
|
-
metrics(data) {
|
|
322
|
-
const body = delegate.metrics(data);
|
|
323
|
-
terminalBodies.add(body);
|
|
324
|
-
return body;
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
},
|
|
328
|
-
transport(client, enabled = () => true) {
|
|
329
|
-
const retrying = HttpClient.transform(client, (response, request) => Effect.suspend(() => {
|
|
330
|
-
if (enabled()) return response;
|
|
331
|
-
budget.drop();
|
|
332
|
-
return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
|
|
333
|
-
})).pipe(HttpClient.transformResponse((response) => Effect.tap(response, (result) => {
|
|
334
|
-
let received = 0;
|
|
335
|
-
return Stream.runForEach(result.stream, (chunk) => Effect.sync(() => {
|
|
336
|
-
received += chunk.byteLength;
|
|
337
|
-
if (received > 65536) throw new Error("Engineering response limit exceeded");
|
|
338
|
-
})).pipe(Effect.catchTag("HttpClientError", (error) => error.reason._tag === "EmptyBodyError" ? Effect.void : Effect.fail(error)));
|
|
339
|
-
}))).pipe(HttpClient.filterStatusOk, HttpClient.retryTransient({
|
|
340
|
-
times: 1,
|
|
341
|
-
schedule: Schedule.spaced("100 millis").pipe(Schedule.addDelay(({ input }) => Effect.map(Clock.currentTimeMillis, (now) => {
|
|
342
|
-
if (!HttpClientError.isHttpClientError(input) || input.reason._tag !== "StatusCodeError" || input.reason.response.status !== 429) return Duration.zero;
|
|
343
|
-
const header = input.reason.response.headers["retry-after"];
|
|
344
|
-
const numeric = Number(header);
|
|
345
|
-
const delay = header === void 0 ? 2e3 : Number.isFinite(numeric) ? numeric * 1e3 : Date.parse(header) - now;
|
|
346
|
-
return Duration.millis(Number.isFinite(delay) ? Math.min(2e3, Math.max(0, delay)) : 2e3);
|
|
347
|
-
})))
|
|
348
|
-
}));
|
|
349
|
-
return HttpClient.transform(retrying, (response, request) => Effect.suspend(() => {
|
|
350
|
-
if (isClosed) {
|
|
351
|
-
discardBody(request.body);
|
|
352
|
-
return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
|
|
353
|
-
}
|
|
354
|
-
const droppedResponse = () => HttpClientResponse.fromWeb(request, new Response(null, { status: 200 }));
|
|
355
|
-
if (request.body._tag === "Empty") {
|
|
356
|
-
discardBody(request.body);
|
|
357
|
-
return Effect.succeed(droppedResponse());
|
|
358
|
-
}
|
|
359
|
-
const bytes = request.body.contentLength;
|
|
360
|
-
const release = budget.admitRequest(bytes ?? Number.POSITIVE_INFINITY, terminalBodies.has(request.body));
|
|
361
|
-
if (release === void 0) {
|
|
362
|
-
discardBody(request.body);
|
|
363
|
-
return Effect.succeed(droppedResponse());
|
|
364
|
-
}
|
|
365
|
-
const lease = bodyLeases.get(request.body);
|
|
366
|
-
if (lease !== void 0) lease.started = true;
|
|
367
|
-
return response.pipe(Effect.asVoid, Effect.timeoutOption(CLI_ENGINEERING_LIMITS.requestTimeoutMs), Effect.raceFirst(Deferred.await(closed).pipe(Effect.andThen(Effect.interrupt))), Effect.tap((result) => Effect.sync(() => {
|
|
368
|
-
if (Option.isNone(result)) budget.drop();
|
|
369
|
-
})), Effect.catchCause(() => Effect.sync(() => {
|
|
370
|
-
budget.drop();
|
|
371
|
-
})), Effect.as(droppedResponse()), Effect.ensuring(Effect.sync(() => {
|
|
372
|
-
release();
|
|
373
|
-
discardBody(request.body);
|
|
374
|
-
})));
|
|
375
|
-
}));
|
|
376
|
-
}
|
|
411
|
+
logger: (delegate) => admittedLogger(state, delegate),
|
|
412
|
+
tracer: (delegate) => admittedTracer(state, delegate),
|
|
413
|
+
serialization: (delegate) => admittedSerialization(state, delegate),
|
|
414
|
+
transport: (client, enabled) => admittedTransport(state, client, enabled)
|
|
377
415
|
};
|
|
378
416
|
}
|
|
379
417
|
//#endregion
|
|
380
|
-
//#region src/engineering.ts
|
|
418
|
+
//#region src/engineering/config.ts
|
|
381
419
|
const ENGINEERING_CAPXUL_ENVS = [
|
|
382
420
|
"development",
|
|
383
421
|
"staging",
|
|
@@ -385,7 +423,10 @@ const ENGINEERING_CAPXUL_ENVS = [
|
|
|
385
423
|
"local"
|
|
386
424
|
];
|
|
387
425
|
const traceHeaderFilter = (_name) => true;
|
|
388
|
-
const credentialHeaderPattern = Object.assign(/./u, {
|
|
426
|
+
const credentialHeaderPattern = Object.assign(/./u, { exec: (name) => isCredentialField(name, "header") ? Object.assign([name], {
|
|
427
|
+
index: 0,
|
|
428
|
+
input: name
|
|
429
|
+
}) : null });
|
|
389
430
|
const postHogOtlpEndpoints = (host) => {
|
|
390
431
|
const base = host.replace(/\/+$/, "");
|
|
391
432
|
return {
|
|
@@ -424,6 +465,8 @@ const validateEngineeringTelemetryConfig = (config) => {
|
|
|
424
465
|
headers: Object.freeze({ authorization: headerEntries[0][1] })
|
|
425
466
|
};
|
|
426
467
|
};
|
|
468
|
+
//#endregion
|
|
469
|
+
//#region src/engineering/tracer.ts
|
|
427
470
|
const failureText = (failure, key, limit, source) => {
|
|
428
471
|
if (failure === null || typeof failure !== "object") return void 0;
|
|
429
472
|
try {
|
|
@@ -457,7 +500,7 @@ const browserExportClient = (client) => HttpClient.transform(client, (response,
|
|
|
457
500
|
if (request.body._tag === "Stream" || request.body._tag === "FormData" || bytes === void 0 || !Number.isSafeInteger(bytes) || bytes < 0 || unfinishedBrowserExportBytes + bytes > BROWSER_KEEPALIVE_BYTES) return;
|
|
458
501
|
unfinishedBrowserExportBytes += bytes;
|
|
459
502
|
return bytes;
|
|
460
|
-
}), (bytes) => response.pipe(Effect.provideService(FetchHttpClient.RequestInit, { keepalive: bytes !== void 0 }), Effect.tap((result) => bytes === void 0 ? Effect.void : Stream.runDrain(result.stream).pipe(Effect.
|
|
503
|
+
}), (bytes) => response.pipe(Effect.provideService(FetchHttpClient.RequestInit, { keepalive: bytes !== void 0 }), Effect.tap((result) => bytes === void 0 ? Effect.void : Stream.runDrain(result.stream).pipe(Effect.catchIf((error) => error.reason._tag === "EmptyBodyError", () => Effect.void)))), (bytes) => Effect.sync(() => {
|
|
461
504
|
if (bytes !== void 0) unfinishedBrowserExportBytes -= bytes;
|
|
462
505
|
})));
|
|
463
506
|
const spanUpstream = (span) => {
|
|
@@ -477,11 +520,15 @@ function redactHttpPath(path, fullUrl, source) {
|
|
|
477
520
|
const prefix = path.startsWith("/") ? origin : "";
|
|
478
521
|
return redactUrlSecrets(`${prefix}${path}`, source).slice(prefix.length, prefix.length + 2048);
|
|
479
522
|
}
|
|
523
|
+
/** A span whose prototype is `span`: it answers every Span member until an own property overrides it. */
|
|
524
|
+
function inheritSpan(span) {
|
|
525
|
+
return Object.create(span);
|
|
526
|
+
}
|
|
480
527
|
/** Preserve domain exits while preventing the OTLP serializer from seeing raw causes. */
|
|
481
528
|
const makeLeakSafeEngineeringTracer = (delegate, enabled = () => true) => Tracer.make({
|
|
482
529
|
span(options) {
|
|
483
530
|
const span = enabled() ? delegate.span(options) : new Tracer.NativeSpan(options);
|
|
484
|
-
const wrapped =
|
|
531
|
+
const wrapped = inheritSpan(span);
|
|
485
532
|
Object.defineProperty(wrapped, "attribute", { value: (name, value) => {
|
|
486
533
|
const source = spanUpstream(span);
|
|
487
534
|
const header = /^http\.(?:request|response)\.header\.(.+)$/u.exec(name)?.[1];
|
|
@@ -521,20 +568,25 @@ const makeLeakSafeEngineeringTracer = (delegate, enabled = () => true) => Tracer
|
|
|
521
568
|
},
|
|
522
569
|
...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
|
|
523
570
|
});
|
|
571
|
+
//#endregion
|
|
572
|
+
//#region src/engineering/minimal.ts
|
|
524
573
|
const MINIMAL_LOG_NAMES = /* @__PURE__ */ new Set([
|
|
525
574
|
"capxul_sdk_operation",
|
|
526
575
|
"identity.transition",
|
|
527
576
|
"convex.connection.changed",
|
|
528
577
|
"convex.action.connection_lost",
|
|
529
578
|
"cli.command.completed",
|
|
530
|
-
"cli.sdk.operation.failed"
|
|
579
|
+
"cli.sdk.operation.failed",
|
|
580
|
+
"cli.signer.wait",
|
|
581
|
+
"cli.wallet.step"
|
|
531
582
|
]);
|
|
532
583
|
const MINIMAL_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
533
584
|
...CAPXUL_ERROR_CODES,
|
|
534
585
|
"CLI_USAGE",
|
|
535
586
|
"CLI_CONFIG",
|
|
536
587
|
"CLI_STORAGE",
|
|
537
|
-
"CLI_DEFECT"
|
|
588
|
+
"CLI_DEFECT",
|
|
589
|
+
"CLI_DEADLINE"
|
|
538
590
|
]);
|
|
539
591
|
const MINIMAL_NUMBERS = /* @__PURE__ */ new Set([
|
|
540
592
|
"schema_version",
|
|
@@ -546,7 +598,14 @@ const MINIMAL_NUMBERS = /* @__PURE__ */ new Set([
|
|
|
546
598
|
"http_status",
|
|
547
599
|
"rpc.code",
|
|
548
600
|
"connection_count",
|
|
549
|
-
"connection_retries"
|
|
601
|
+
"connection_retries",
|
|
602
|
+
"elapsed_ms",
|
|
603
|
+
"total_ms",
|
|
604
|
+
"token_calls",
|
|
605
|
+
"call",
|
|
606
|
+
"attempt",
|
|
607
|
+
"bridge_protocol_version",
|
|
608
|
+
"httpStatus"
|
|
550
609
|
]);
|
|
551
610
|
const MINIMAL_CORRELATIONS = /* @__PURE__ */ new Set([
|
|
552
611
|
"correlation_id",
|
|
@@ -581,8 +640,39 @@ const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
|
|
|
581
640
|
["command", CLI_COMMAND_NAMES],
|
|
582
641
|
["capability", CLI_CAPABILITIES],
|
|
583
642
|
["invocation_kind", CLI_INVOCATION_KINDS],
|
|
584
|
-
["
|
|
643
|
+
["diagnostic_scope", [
|
|
644
|
+
"openfort.step",
|
|
645
|
+
"openfort.token",
|
|
646
|
+
"openfort.encryptionSession",
|
|
647
|
+
"openfort.embeddedState",
|
|
648
|
+
"openfort.storageCleared",
|
|
649
|
+
"openfort.configure",
|
|
650
|
+
"openfort.get",
|
|
651
|
+
"openfort.address",
|
|
652
|
+
"openfort.staleCacheHeal",
|
|
653
|
+
"openfort.signerRecovery",
|
|
654
|
+
"openfort.signerStatusListener"
|
|
655
|
+
]],
|
|
656
|
+
["step", [
|
|
657
|
+
"initialize",
|
|
658
|
+
"token",
|
|
659
|
+
"encryptionSession",
|
|
660
|
+
"embeddedState",
|
|
661
|
+
"configure",
|
|
662
|
+
"get",
|
|
663
|
+
"healStaleCache",
|
|
664
|
+
"walletReady"
|
|
665
|
+
]],
|
|
666
|
+
["provider", ["better-auth", "oidc"]],
|
|
667
|
+
["cli_identity_kind", ["anonymous", "identified"]],
|
|
585
668
|
["output_mode", ["human", "json"]],
|
|
669
|
+
["setup_status", ORGANIZATION_SETUP_STATUSES],
|
|
670
|
+
["setup_reason", ORGANIZATION_SETUP_REASONS],
|
|
671
|
+
["signer_stage", [
|
|
672
|
+
"address",
|
|
673
|
+
"readiness",
|
|
674
|
+
"sign"
|
|
675
|
+
]],
|
|
586
676
|
["unit", [
|
|
587
677
|
"1",
|
|
588
678
|
"s",
|
|
@@ -603,6 +693,7 @@ const MINIMAL_VERSION_KEYS = /* @__PURE__ */ new Set([
|
|
|
603
693
|
]);
|
|
604
694
|
function minimalStringValue(key, text) {
|
|
605
695
|
if (key === "operation") return isCapxulOperation(text);
|
|
696
|
+
if (key === "organization_id") return /^org_[0-9A-Za-z]+$/u.test(text);
|
|
606
697
|
if (MINIMAL_CODE_KEYS.has(key)) return MINIMAL_ERROR_CODES.has(text);
|
|
607
698
|
if (MINIMAL_VERSION_KEYS.has(key)) return /^[A-Za-z0-9][A-Za-z0-9._+@-]{0,127}$/u.test(text);
|
|
608
699
|
if (key === "trace_id") return traceId(text);
|
|
@@ -628,6 +719,8 @@ function minimalAttributes(attributes, metric = false) {
|
|
|
628
719
|
}] : [];
|
|
629
720
|
});
|
|
630
721
|
}
|
|
722
|
+
//#endregion
|
|
723
|
+
//#region src/engineering/metric-points.ts
|
|
631
724
|
const finiteProtocolNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
632
725
|
const protocolCount = (value, maximum = Number.MAX_SAFE_INTEGER) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= maximum ? value : void 0;
|
|
633
726
|
const protocolFlags = (value) => protocolCount(Object.getOwnPropertyDescriptor(value, "flags")?.value, 4294967295);
|
|
@@ -677,6 +770,8 @@ function minimalHistogramPoints(points) {
|
|
|
677
770
|
}];
|
|
678
771
|
});
|
|
679
772
|
}
|
|
773
|
+
//#endregion
|
|
774
|
+
//#region src/engineering/serialization.ts
|
|
680
775
|
function minimalSerialization(config) {
|
|
681
776
|
const service = config.serviceName ?? "capxul-sdk";
|
|
682
777
|
const resource = { attributes: Object.entries({
|
|
@@ -767,7 +862,7 @@ function minimalSerialization(config) {
|
|
|
767
862
|
const gauge = metric.gauge === void 0 ? void 0 : { dataPoints: minimalNumberPoints(metric.gauge.dataPoints) };
|
|
768
863
|
const sum = metric.sum === void 0 ? void 0 : {
|
|
769
864
|
aggregationTemporality: protocolCount(metric.sum.aggregationTemporality, 2) ?? 0,
|
|
770
|
-
isMonotonic: metric.sum.isMonotonic
|
|
865
|
+
isMonotonic: metric.sum.isMonotonic,
|
|
771
866
|
dataPoints: minimalNumberPoints(metric.sum.dataPoints)
|
|
772
867
|
};
|
|
773
868
|
const histogram = metric.histogram === void 0 ? void 0 : {
|
|
@@ -797,14 +892,18 @@ function minimalSerialization(config) {
|
|
|
797
892
|
}
|
|
798
893
|
};
|
|
799
894
|
}
|
|
895
|
+
//#endregion
|
|
896
|
+
//#region src/engineering/logs.ts
|
|
800
897
|
/** Attempt to drain all engineering signals; completion does not prove ingestion. */
|
|
801
898
|
const flushEngineeringTelemetry = Effect.flatMap(OtlpExporter.Flusher, (flusher) => flusher.flush);
|
|
802
899
|
var EngineeringLog = class extends Context.Service()("capxul/EngineeringLog") {};
|
|
803
900
|
/** Deliver an owned engineering record without changing the caller's console verbosity. */
|
|
804
901
|
const logEngineeringInfo = (message, attributes) => {
|
|
805
902
|
const record = Effect.logInfo(message).pipe(Effect.annotateLogs(attributes));
|
|
806
|
-
return Effect.flatMap(LogLevel.isEnabled("Info"), (visible) => visible ? record : Effect.flatMap(EngineeringLog, (logger) => record.pipe(Effect.provideService(Logger.CurrentLoggers, /* @__PURE__ */ new Set([logger])), Effect.provideService(References.MinimumLogLevel, "All"))));
|
|
903
|
+
return Effect.flatMap(LogLevel.isEnabled("Info"), (visible) => visible ? record : Effect.flatMap(Effect.serviceOption(EngineeringLog), (logger) => logger._tag === "None" ? Effect.void : record.pipe(Effect.provideService(Logger.CurrentLoggers, /* @__PURE__ */ new Set([logger.value])), Effect.provideService(References.MinimumLogLevel, "All"))));
|
|
807
904
|
};
|
|
905
|
+
//#endregion
|
|
906
|
+
//#region src/engineering.ts
|
|
808
907
|
/** Shared browser/server OTLP layer. OtlpLogger merges with incumbent loggers once. */
|
|
809
908
|
const makeEngineeringTelemetryLayer = (config) => {
|
|
810
909
|
const validated = validateEngineeringTelemetryConfig(config);
|