@capxul/observability 4.20.0-beta.2 → 4.20.0-beta.21
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/dist/engineering.d.mts +48 -4
- package/dist/engineering.mjs +482 -23
- package/dist/index.d.mts +507 -17
- package/dist/index.mjs +2 -2236
- package/dist/src-CCuVZt-H.mjs +3463 -0
- package/package.json +1 -1
- package/dist/operations-sJPrehYY.mjs +0 -748
package/dist/engineering.d.mts
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
|
-
import { Duration, Effect, Layer, Tracer } from "effect";
|
|
2
|
-
import {
|
|
1
|
+
import { Context, Duration, Effect, Layer, Logger, Metric, References, Tracer } from "effect";
|
|
2
|
+
import { HttpClient, HttpClientError } from "effect/unstable/http";
|
|
3
|
+
import { OtlpExporter, OtlpSerialization } from "effect/unstable/observability";
|
|
4
|
+
//#region src/engineering-delivery.d.ts
|
|
5
|
+
/** Fixed admission limits for one CLI invocation and its SDK client. */
|
|
6
|
+
declare const CLI_ENGINEERING_LIMITS: Readonly<{
|
|
7
|
+
records: 80;
|
|
8
|
+
ordinaryRecords: 64;
|
|
9
|
+
recordBytes: 16384;
|
|
10
|
+
requests: 5;
|
|
11
|
+
ordinaryRequests: 2;
|
|
12
|
+
requestBytes: 1572864;
|
|
13
|
+
metricSeries: 16;
|
|
14
|
+
requestTimeoutMs: 1000;
|
|
15
|
+
}>;
|
|
16
|
+
type MetricEntry = Metric.Metric.Metadata<unknown, unknown>;
|
|
17
|
+
interface CliEngineeringStats {
|
|
18
|
+
readonly records: number;
|
|
19
|
+
readonly reservedRecordBytes: number;
|
|
20
|
+
readonly peakRecords: number;
|
|
21
|
+
readonly peakReservedRecordBytes: number;
|
|
22
|
+
readonly activeRequests: number;
|
|
23
|
+
readonly requestBytes: number;
|
|
24
|
+
readonly peakRequests: number;
|
|
25
|
+
readonly peakRequestBytes: number;
|
|
26
|
+
/** Rejected records, fields, metric registrations and export attempts; not delivered-event count. */
|
|
27
|
+
readonly dropped: number;
|
|
28
|
+
readonly metricSeries: number;
|
|
29
|
+
}
|
|
30
|
+
/** Shared, closure-owned admission state; no queue or mutable counters are exposed. */
|
|
31
|
+
declare function makeCliEngineeringBudget(): Readonly<{
|
|
32
|
+
metricRegistry: Map<string, MetricEntry>;
|
|
33
|
+
emptyMetricRegistry: () => Map<string, MetricEntry>;
|
|
34
|
+
drop: () => void;
|
|
35
|
+
admitRecord(terminal: boolean): (() => void) | undefined;
|
|
36
|
+
admitRequest(bytes: number, terminal: boolean): (() => void) | undefined;
|
|
37
|
+
snapshot(): CliEngineeringStats;
|
|
38
|
+
}>;
|
|
39
|
+
type CliEngineeringBudget = ReturnType<typeof makeCliEngineeringBudget>;
|
|
40
|
+
//#endregion
|
|
3
41
|
//#region src/engineering.d.ts
|
|
4
42
|
type EngineeringProducer = "browser" | "server";
|
|
5
43
|
declare const ENGINEERING_CAPXUL_ENVS: readonly ["development", "staging", "production", "local"];
|
|
@@ -17,6 +55,8 @@ interface EngineeringTelemetryConfig {
|
|
|
17
55
|
readonly privacy?: "minimal";
|
|
18
56
|
readonly shutdownTimeout?: Duration.Input;
|
|
19
57
|
readonly exportInterval?: Duration.Input;
|
|
58
|
+
/** CLI-only shared admission and transport lifetime. Omission preserves host defaults. */
|
|
59
|
+
readonly deliveryBudget?: CliEngineeringBudget;
|
|
20
60
|
}
|
|
21
61
|
declare const traceHeaderFilter: (_name: string) => boolean;
|
|
22
62
|
declare const postHogOtlpEndpoints: (host: string) => {
|
|
@@ -28,7 +68,11 @@ declare const postHogOtlpEndpoints: (host: string) => {
|
|
|
28
68
|
declare const makeLeakSafeEngineeringTracer: (delegate: Tracer.Tracer, enabled?: () => boolean) => Tracer.Tracer;
|
|
29
69
|
/** Attempt to drain all engineering signals; completion does not prove ingestion. */
|
|
30
70
|
declare const flushEngineeringTelemetry: Effect.Effect<void, never, OtlpExporter.Flusher>;
|
|
71
|
+
declare const EngineeringLog_base: Context.ServiceClass<EngineeringLog, "capxul/EngineeringLog", Logger.Logger<unknown, void>>;
|
|
72
|
+
declare class EngineeringLog extends EngineeringLog_base {}
|
|
73
|
+
/** Deliver an owned engineering record without changing the caller's console verbosity. */
|
|
74
|
+
declare const logEngineeringInfo: (message: string, attributes: Readonly<Record<string, unknown>>) => Effect.Effect<void, never, never>;
|
|
31
75
|
/** Shared browser/server OTLP layer. OtlpLogger merges with incumbent loggers once. */
|
|
32
|
-
declare const makeEngineeringTelemetryLayer: (config: EngineeringTelemetryConfig) => Layer.Layer<OtlpExporter.Flusher, never, never>;
|
|
76
|
+
declare const makeEngineeringTelemetryLayer: (config: EngineeringTelemetryConfig) => Layer.Layer<EngineeringLog | OtlpExporter.Flusher, never, never>;
|
|
33
77
|
//#endregion
|
|
34
|
-
export { ENGINEERING_CAPXUL_ENVS, EngineeringCapxulEnv, EngineeringProducer, EngineeringTelemetryConfig, flushEngineeringTelemetry, makeEngineeringTelemetryLayer, makeLeakSafeEngineeringTracer, postHogOtlpEndpoints, traceHeaderFilter };
|
|
78
|
+
export { CLI_ENGINEERING_LIMITS, type CliEngineeringBudget, type CliEngineeringStats, ENGINEERING_CAPXUL_ENVS, EngineeringCapxulEnv, EngineeringProducer, EngineeringTelemetryConfig, flushEngineeringTelemetry, logEngineeringInfo, makeCliEngineeringBudget, makeEngineeringTelemetryLayer, makeLeakSafeEngineeringTracer, postHogOtlpEndpoints, traceHeaderFilter };
|
package/dist/engineering.mjs
CHANGED
|
@@ -1,7 +1,382 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Cause, Effect, Exit, FiberSet, Layer, Logger, Stream, Tracer } from "effect";
|
|
3
|
-
import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientResponse } from "effect/unstable/http";
|
|
1
|
+
import { _ as CLI_COMMAND_NAMES, cn as ORGANIZATION_SETUP_REASONS, dn as FAILURE_MODES, fn as isCredentialField, g as CLI_CAPABILITIES, ln as ORGANIZATION_SETUP_STATUSES, mn as redactUrlSecrets, on as isCapxulOperation, pn as redactSecrets, un as CAPXUL_ERROR_CODES, v as CLI_INVOCATION_KINDS } from "./src-CCuVZt-H.mjs";
|
|
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/unstable/http";
|
|
4
4
|
import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
|
|
5
|
+
//#region src/engineering-delivery.ts
|
|
6
|
+
/** Fixed admission limits for one CLI invocation and its SDK client. */
|
|
7
|
+
const CLI_ENGINEERING_LIMITS = Object.freeze({
|
|
8
|
+
records: 80,
|
|
9
|
+
ordinaryRecords: 64,
|
|
10
|
+
recordBytes: 16384,
|
|
11
|
+
requests: 5,
|
|
12
|
+
ordinaryRequests: 2,
|
|
13
|
+
requestBytes: 1572864,
|
|
14
|
+
metricSeries: 16,
|
|
15
|
+
requestTimeoutMs: 1e3
|
|
16
|
+
});
|
|
17
|
+
const metricNames = /* @__PURE__ */ new Set(["capxul.cli.command.count", "capxul.cli.command.duration"]);
|
|
18
|
+
const metricValues = {
|
|
19
|
+
command: CLI_COMMAND_NAMES,
|
|
20
|
+
capability: CLI_CAPABILITIES,
|
|
21
|
+
invocation_kind: CLI_INVOCATION_KINDS,
|
|
22
|
+
outcome: [
|
|
23
|
+
"succeeded",
|
|
24
|
+
"refused",
|
|
25
|
+
"failed",
|
|
26
|
+
"cancelled"
|
|
27
|
+
],
|
|
28
|
+
capxul_env: [
|
|
29
|
+
"development",
|
|
30
|
+
"staging",
|
|
31
|
+
"production",
|
|
32
|
+
"local"
|
|
33
|
+
],
|
|
34
|
+
unit: [
|
|
35
|
+
"1",
|
|
36
|
+
"s",
|
|
37
|
+
"ms"
|
|
38
|
+
]
|
|
39
|
+
};
|
|
40
|
+
/** Only this bounded registry is supplied to the CLI's metric producers. */
|
|
41
|
+
var CliMetricRegistry = class extends Map {
|
|
42
|
+
dropped;
|
|
43
|
+
enabled;
|
|
44
|
+
constructor(dropped, enabled = true) {
|
|
45
|
+
super();
|
|
46
|
+
this.dropped = dropped;
|
|
47
|
+
this.enabled = enabled;
|
|
48
|
+
}
|
|
49
|
+
set(key, entry) {
|
|
50
|
+
if (!this.enabled || !metricNames.has(entry.id) || !this.has(key) && this.size >= CLI_ENGINEERING_LIMITS.metricSeries) {
|
|
51
|
+
this.dropped();
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
const labels = Object.entries(entry.attributes ?? {});
|
|
55
|
+
const state = entry.hooks.get(Context.empty());
|
|
56
|
+
const buckets = typeof state === "object" && state !== null && "buckets" in state ? state.buckets : void 0;
|
|
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)) {
|
|
58
|
+
this.dropped();
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
return super.set(key, entry);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
/** Shared, closure-owned admission state; no queue or mutable counters are exposed. */
|
|
65
|
+
function makeCliEngineeringBudget() {
|
|
66
|
+
let records = 0, peakRecords = 0, activeRequests = 0, requestBytes = 0;
|
|
67
|
+
let peakRequests = 0, peakRequestBytes = 0, dropped = 0;
|
|
68
|
+
const drop = () => {
|
|
69
|
+
dropped += 1;
|
|
70
|
+
};
|
|
71
|
+
const metricRegistry = new CliMetricRegistry(drop);
|
|
72
|
+
return Object.freeze({
|
|
73
|
+
metricRegistry,
|
|
74
|
+
emptyMetricRegistry: () => new CliMetricRegistry(drop, false),
|
|
75
|
+
drop,
|
|
76
|
+
admitRecord(terminal) {
|
|
77
|
+
if (records >= (terminal ? CLI_ENGINEERING_LIMITS.records : CLI_ENGINEERING_LIMITS.ordinaryRecords)) {
|
|
78
|
+
drop();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
records += 1;
|
|
82
|
+
peakRecords = Math.max(peakRecords, records);
|
|
83
|
+
let released = false;
|
|
84
|
+
return () => {
|
|
85
|
+
if (!released) {
|
|
86
|
+
released = true;
|
|
87
|
+
records -= 1;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
admitRequest(bytes, terminal) {
|
|
92
|
+
if (bytes > CLI_ENGINEERING_LIMITS.requestBytes || activeRequests >= (terminal ? CLI_ENGINEERING_LIMITS.requests : CLI_ENGINEERING_LIMITS.ordinaryRequests)) {
|
|
93
|
+
drop();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
activeRequests += 1;
|
|
97
|
+
requestBytes += bytes;
|
|
98
|
+
peakRequests = Math.max(peakRequests, activeRequests);
|
|
99
|
+
peakRequestBytes = Math.max(peakRequestBytes, requestBytes);
|
|
100
|
+
return () => {
|
|
101
|
+
activeRequests -= 1;
|
|
102
|
+
requestBytes -= bytes;
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
snapshot() {
|
|
106
|
+
return {
|
|
107
|
+
records,
|
|
108
|
+
reservedRecordBytes: records * CLI_ENGINEERING_LIMITS.recordBytes,
|
|
109
|
+
peakRecords,
|
|
110
|
+
peakReservedRecordBytes: peakRecords * CLI_ENGINEERING_LIMITS.recordBytes,
|
|
111
|
+
activeRequests,
|
|
112
|
+
requestBytes,
|
|
113
|
+
peakRequests,
|
|
114
|
+
peakRequestBytes,
|
|
115
|
+
dropped,
|
|
116
|
+
metricSeries: metricRegistry.size
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const text = (value) => typeof value === "string" ? value.slice(0, 128) : "capxul.engineering";
|
|
122
|
+
const attributes = (input, limit = 32) => {
|
|
123
|
+
const out = {};
|
|
124
|
+
let count = 0;
|
|
125
|
+
let bytes = 0;
|
|
126
|
+
try {
|
|
127
|
+
for (const key in input) {
|
|
128
|
+
if (count++ >= limit) break;
|
|
129
|
+
if (key.length > 64) continue;
|
|
130
|
+
const value = Object.getOwnPropertyDescriptor(input, key)?.value;
|
|
131
|
+
const scalar = typeof value === "string" ? value.slice(0, 128) : typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
132
|
+
if (scalar === void 0) continue;
|
|
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" || name === "cli.wallet.step" || 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);
|
|
158
|
+
for (const release of releases) release();
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
bodyLeases.set(body, lease);
|
|
162
|
+
pendingBodies.add(lease);
|
|
163
|
+
};
|
|
164
|
+
const discardBody = (body) => bodyLeases.get(body)?.release();
|
|
165
|
+
const drainLogs = () => {
|
|
166
|
+
for (const release of logs.splice(0)) release();
|
|
167
|
+
};
|
|
168
|
+
const close = () => {
|
|
169
|
+
isClosed = true;
|
|
170
|
+
Deferred.doneUnsafe(closed, Effect.void);
|
|
171
|
+
for (const lease of pendingBodies) if (!lease.started) lease.release();
|
|
172
|
+
drainLogs();
|
|
173
|
+
for (const release of spans.values()) release();
|
|
174
|
+
spans.clear();
|
|
175
|
+
if (ownsMetrics) budget.metricRegistry.clear();
|
|
176
|
+
};
|
|
177
|
+
return {
|
|
178
|
+
close,
|
|
179
|
+
discardBody,
|
|
180
|
+
metrics(run) {
|
|
181
|
+
return Effect.suspend(() => {
|
|
182
|
+
if (isClosed || metricsRunning) return Effect.void;
|
|
183
|
+
metricsRunning = true;
|
|
184
|
+
return run.pipe(Effect.ensuring(Effect.sync(() => {
|
|
185
|
+
metricsRunning = false;
|
|
186
|
+
})));
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
logger(delegate) {
|
|
190
|
+
return Logger.make((options) => {
|
|
191
|
+
if (isClosed) return;
|
|
192
|
+
let release;
|
|
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) => terminalName(l.body?.stringValue ?? ""))))) 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
|
+
}
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
//#endregion
|
|
5
380
|
//#region src/engineering.ts
|
|
6
381
|
const ENGINEERING_CAPXUL_ENVS = [
|
|
7
382
|
"development",
|
|
@@ -152,14 +527,17 @@ const MINIMAL_LOG_NAMES = /* @__PURE__ */ new Set([
|
|
|
152
527
|
"convex.connection.changed",
|
|
153
528
|
"convex.action.connection_lost",
|
|
154
529
|
"cli.command.completed",
|
|
155
|
-
"cli.sdk.operation.failed"
|
|
530
|
+
"cli.sdk.operation.failed",
|
|
531
|
+
"cli.signer.wait",
|
|
532
|
+
"cli.wallet.step"
|
|
156
533
|
]);
|
|
157
534
|
const MINIMAL_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
158
535
|
...CAPXUL_ERROR_CODES,
|
|
159
536
|
"CLI_USAGE",
|
|
160
537
|
"CLI_CONFIG",
|
|
161
538
|
"CLI_STORAGE",
|
|
162
|
-
"CLI_DEFECT"
|
|
539
|
+
"CLI_DEFECT",
|
|
540
|
+
"CLI_DEADLINE"
|
|
163
541
|
]);
|
|
164
542
|
const MINIMAL_NUMBERS = /* @__PURE__ */ new Set([
|
|
165
543
|
"schema_version",
|
|
@@ -171,15 +549,25 @@ const MINIMAL_NUMBERS = /* @__PURE__ */ new Set([
|
|
|
171
549
|
"http_status",
|
|
172
550
|
"rpc.code",
|
|
173
551
|
"connection_count",
|
|
174
|
-
"connection_retries"
|
|
552
|
+
"connection_retries",
|
|
553
|
+
"elapsed_ms",
|
|
554
|
+
"total_ms",
|
|
555
|
+
"token_calls",
|
|
556
|
+
"call",
|
|
557
|
+
"attempt",
|
|
558
|
+
"bridge_protocol_version",
|
|
559
|
+
"httpStatus"
|
|
175
560
|
]);
|
|
176
561
|
const MINIMAL_CORRELATIONS = /* @__PURE__ */ new Set([
|
|
177
562
|
"correlation_id",
|
|
178
563
|
"journey_id",
|
|
179
|
-
"invocation_id"
|
|
564
|
+
"invocation_id",
|
|
565
|
+
"posthogDistinctId"
|
|
180
566
|
]);
|
|
181
567
|
const MINIMAL_METRIC_LABELS = /* @__PURE__ */ new Set([
|
|
182
568
|
"command",
|
|
569
|
+
"capability",
|
|
570
|
+
"invocation_kind",
|
|
183
571
|
"outcome",
|
|
184
572
|
"capxul_env",
|
|
185
573
|
"unit"
|
|
@@ -187,7 +575,7 @@ const MINIMAL_METRIC_LABELS = /* @__PURE__ */ new Set([
|
|
|
187
575
|
const MINIMAL_METRIC_NAMES = /* @__PURE__ */ new Set(["capxul.cli.command.count", "capxul.cli.command.duration"]);
|
|
188
576
|
const traceId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{32}$/u.test(value);
|
|
189
577
|
const spanId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{16}$/u.test(value);
|
|
190
|
-
const minimalName = (value) => typeof value === "string" && (MINIMAL_LOG_NAMES.has(value) || isCapxulOperation(value)) ? value : "capxul.engineering";
|
|
578
|
+
const minimalName = (value) => typeof value === "string" && (MINIMAL_LOG_NAMES.has(value) || isCapxulOperation(value) || CLI_COMMAND_NAMES.some((command) => value === `cli.command.${command}`)) ? value : "capxul.engineering";
|
|
191
579
|
const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
|
|
192
580
|
["outcome", [
|
|
193
581
|
"succeeded",
|
|
@@ -200,8 +588,42 @@ const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
|
|
|
200
588
|
["failure_mode", FAILURE_MODES],
|
|
201
589
|
["capxul_env", ENGINEERING_CAPXUL_ENVS],
|
|
202
590
|
["producer", ["browser", "server"]],
|
|
203
|
-
["command",
|
|
591
|
+
["command", CLI_COMMAND_NAMES],
|
|
592
|
+
["capability", CLI_CAPABILITIES],
|
|
593
|
+
["invocation_kind", CLI_INVOCATION_KINDS],
|
|
594
|
+
["diagnostic_scope", [
|
|
595
|
+
"openfort.step",
|
|
596
|
+
"openfort.token",
|
|
597
|
+
"openfort.encryptionSession",
|
|
598
|
+
"openfort.embeddedState",
|
|
599
|
+
"openfort.storageCleared",
|
|
600
|
+
"openfort.configure",
|
|
601
|
+
"openfort.get",
|
|
602
|
+
"openfort.address",
|
|
603
|
+
"openfort.staleCacheHeal",
|
|
604
|
+
"openfort.signerRecovery",
|
|
605
|
+
"openfort.signerStatusListener"
|
|
606
|
+
]],
|
|
607
|
+
["step", [
|
|
608
|
+
"initialize",
|
|
609
|
+
"token",
|
|
610
|
+
"encryptionSession",
|
|
611
|
+
"embeddedState",
|
|
612
|
+
"configure",
|
|
613
|
+
"get",
|
|
614
|
+
"healStaleCache",
|
|
615
|
+
"walletReady"
|
|
616
|
+
]],
|
|
617
|
+
["provider", ["better-auth", "oidc"]],
|
|
618
|
+
["cli_identity_kind", ["anonymous", "identified"]],
|
|
204
619
|
["output_mode", ["human", "json"]],
|
|
620
|
+
["setup_status", ORGANIZATION_SETUP_STATUSES],
|
|
621
|
+
["setup_reason", ORGANIZATION_SETUP_REASONS],
|
|
622
|
+
["signer_stage", [
|
|
623
|
+
"address",
|
|
624
|
+
"readiness",
|
|
625
|
+
"sign"
|
|
626
|
+
]],
|
|
205
627
|
["unit", [
|
|
206
628
|
"1",
|
|
207
629
|
"s",
|
|
@@ -222,6 +644,7 @@ const MINIMAL_VERSION_KEYS = /* @__PURE__ */ new Set([
|
|
|
222
644
|
]);
|
|
223
645
|
function minimalStringValue(key, text) {
|
|
224
646
|
if (key === "operation") return isCapxulOperation(text);
|
|
647
|
+
if (key === "organization_id") return /^org_[0-9A-Za-z]+$/u.test(text);
|
|
225
648
|
if (MINIMAL_CODE_KEYS.has(key)) return MINIMAL_ERROR_CODES.has(text);
|
|
226
649
|
if (MINIMAL_VERSION_KEYS.has(key)) return /^[A-Za-z0-9][A-Za-z0-9._+@-]{0,127}$/u.test(text);
|
|
227
650
|
if (key === "trace_id") return traceId(text);
|
|
@@ -418,10 +841,17 @@ function minimalSerialization(config) {
|
|
|
418
841
|
}
|
|
419
842
|
/** Attempt to drain all engineering signals; completion does not prove ingestion. */
|
|
420
843
|
const flushEngineeringTelemetry = Effect.flatMap(OtlpExporter.Flusher, (flusher) => flusher.flush);
|
|
844
|
+
var EngineeringLog = class extends Context.Service()("capxul/EngineeringLog") {};
|
|
845
|
+
/** Deliver an owned engineering record without changing the caller's console verbosity. */
|
|
846
|
+
const logEngineeringInfo = (message, attributes) => {
|
|
847
|
+
const record = Effect.logInfo(message).pipe(Effect.annotateLogs(attributes));
|
|
848
|
+
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"))));
|
|
849
|
+
};
|
|
421
850
|
/** Shared browser/server OTLP layer. OtlpLogger merges with incumbent loggers once. */
|
|
422
851
|
const makeEngineeringTelemetryLayer = (config) => {
|
|
423
852
|
const validated = validateEngineeringTelemetryConfig(config);
|
|
424
853
|
return Layer.suspend(() => {
|
|
854
|
+
const bounded = validated.deliveryBudget === void 0 ? void 0 : cliEngineeringDelivery(validated.deliveryBudget, validated.serviceName === "capxul-cli");
|
|
425
855
|
const endpoints = postHogOtlpEndpoints(validated.host);
|
|
426
856
|
let disabled = false;
|
|
427
857
|
const enabled = () => {
|
|
@@ -435,7 +865,12 @@ const makeEngineeringTelemetryLayer = (config) => {
|
|
|
435
865
|
};
|
|
436
866
|
const delivery = {
|
|
437
867
|
...validated.shutdownTimeout === void 0 ? {} : { shutdownTimeout: validated.shutdownTimeout },
|
|
438
|
-
...validated.exportInterval === void 0 ? {} : { exportInterval: validated.exportInterval }
|
|
868
|
+
...validated.exportInterval === void 0 ? {} : { exportInterval: validated.exportInterval },
|
|
869
|
+
...bounded === void 0 ? {} : {
|
|
870
|
+
maxBatchSize: Number.MAX_SAFE_INTEGER,
|
|
871
|
+
exportInterval: 1e3,
|
|
872
|
+
shutdownTimeout: validated.serviceName === "capxul-cli" ? 0 : 500
|
|
873
|
+
}
|
|
439
874
|
};
|
|
440
875
|
const resource = {
|
|
441
876
|
serviceName: validated.serviceName ?? "capxul-sdk",
|
|
@@ -451,22 +886,38 @@ const makeEngineeringTelemetryLayer = (config) => {
|
|
|
451
886
|
headers: validated.headers,
|
|
452
887
|
resource,
|
|
453
888
|
...delivery
|
|
454
|
-
}).pipe(Effect.map((tracer) => makeLeakSafeEngineeringTracer(tracer, enabled)))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
|
|
455
|
-
const
|
|
889
|
+
}).pipe(Effect.map((tracer) => makeLeakSafeEngineeringTracer(bounded?.tracer(tracer) ?? tracer, enabled)))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
|
|
890
|
+
const ownedLogging = Layer.effect(EngineeringLog, OtlpLogger.make({
|
|
456
891
|
url: endpoints.logs,
|
|
457
892
|
headers: validated.headers,
|
|
458
893
|
resource,
|
|
459
|
-
...delivery
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
894
|
+
...delivery,
|
|
895
|
+
...bounded === void 0 ? {} : { excludeLogSpans: true }
|
|
896
|
+
}).pipe(Effect.map((nativeLogger) => {
|
|
897
|
+
const logger = bounded?.logger(nativeLogger) ?? nativeLogger;
|
|
898
|
+
return Logger.make((options) => {
|
|
899
|
+
if (enabled()) logger.log(options);
|
|
900
|
+
});
|
|
901
|
+
}))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
|
|
902
|
+
const logging = Logger.layer([EngineeringLog], { mergeWithExisting: true }).pipe(Layer.provideMerge(ownedLogging));
|
|
903
|
+
const metricConfig = {
|
|
464
904
|
url: endpoints.metrics,
|
|
465
905
|
headers: validated.headers,
|
|
466
906
|
resource,
|
|
467
907
|
temporality: "delta",
|
|
468
908
|
...delivery
|
|
469
|
-
}
|
|
909
|
+
};
|
|
910
|
+
const metrics = bounded === void 0 ? OtlpMetrics.layer(metricConfig) : validated.serviceName !== "capxul-cli" ? Layer.empty : Layer.effectDiscard(Effect.gen(function* () {
|
|
911
|
+
const flusher = yield* OtlpExporter.Flusher;
|
|
912
|
+
yield* OtlpMetrics.make({
|
|
913
|
+
...metricConfig,
|
|
914
|
+
exportInterval: Number.POSITIVE_INFINITY,
|
|
915
|
+
shutdownTimeout: 0
|
|
916
|
+
}).pipe(Effect.provideService(OtlpExporter.Flusher, {
|
|
917
|
+
...flusher,
|
|
918
|
+
register: (run) => flusher.register(bounded.metrics(run))
|
|
919
|
+
}));
|
|
920
|
+
})).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
|
|
470
921
|
const headerPolicy = Layer.merge(Layer.succeed(HttpClient.TracerHeaderFilter, traceHeaderFilter), Layer.succeed(Headers.CurrentRedactedNames, [credentialHeaderPattern]));
|
|
471
922
|
const browserLifecycle = Layer.effectDiscard(Effect.gen(function* () {
|
|
472
923
|
if (validated.producer !== "browser" || !hasBrowserDocument()) return;
|
|
@@ -489,18 +940,26 @@ const makeEngineeringTelemetryLayer = (config) => {
|
|
|
489
940
|
}));
|
|
490
941
|
}));
|
|
491
942
|
const transport = Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, (client) => {
|
|
492
|
-
const
|
|
493
|
-
|
|
943
|
+
const originalTransport = validated.producer === "browser" && hasBrowserDocument() ? browserExportClient(client) : client;
|
|
944
|
+
const selectedTransport = bounded?.transport(originalTransport, enabled) ?? originalTransport;
|
|
945
|
+
return HttpClient.transform(selectedTransport, (response, request) => Effect.suspend(() => {
|
|
946
|
+
if (enabled() && request.body._tag !== "Empty") return response;
|
|
947
|
+
bounded?.discardBody(request.body);
|
|
948
|
+
return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null, { status: 200 })));
|
|
949
|
+
}));
|
|
494
950
|
})).pipe(Layer.provide(FetchHttpClient.layer));
|
|
495
951
|
const serialization = Layer.effect(OtlpSerialization.OtlpSerialization, Effect.map(OtlpSerialization.OtlpSerialization, (json) => {
|
|
496
952
|
const projected = minimalSerialization(validated);
|
|
497
|
-
|
|
953
|
+
const selected = validated.privacy === "minimal" ? projected : {
|
|
498
954
|
...json,
|
|
499
955
|
metrics: projected.metrics
|
|
500
956
|
};
|
|
957
|
+
return bounded?.serialization(selected) ?? selected;
|
|
501
958
|
})).pipe(Layer.provide(OtlpSerialization.layerJson));
|
|
502
|
-
|
|
959
|
+
const combined = browserLifecycle.pipe(Layer.provideMerge(Layer.mergeAll(tracing, logging, metrics, headerPolicy)), Layer.provide(serialization), Layer.provide(transport));
|
|
960
|
+
const scoped = bounded === void 0 ? combined : combined.pipe(Layer.provideMerge(Layer.effectDiscard(Effect.addFinalizer(() => Effect.sync(bounded.close)))));
|
|
961
|
+
return validated.deliveryBudget === void 0 ? scoped : scoped.pipe(Layer.provideMerge(Layer.succeed(Metric.MetricRegistry, validated.serviceName === "capxul-cli" ? validated.deliveryBudget.metricRegistry : validated.deliveryBudget.emptyMetricRegistry())));
|
|
503
962
|
});
|
|
504
963
|
};
|
|
505
964
|
//#endregion
|
|
506
|
-
export { ENGINEERING_CAPXUL_ENVS, flushEngineeringTelemetry, makeEngineeringTelemetryLayer, makeLeakSafeEngineeringTracer, postHogOtlpEndpoints, traceHeaderFilter };
|
|
965
|
+
export { CLI_ENGINEERING_LIMITS, ENGINEERING_CAPXUL_ENVS, flushEngineeringTelemetry, logEngineeringInfo, makeCliEngineeringBudget, makeEngineeringTelemetryLayer, makeLeakSafeEngineeringTracer, postHogOtlpEndpoints, traceHeaderFilter };
|