@opengeni/observability 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/dist/index.d.ts +67 -14
- package/dist/index.js +288 -134
- package/dist/index.js.map +1 -1
- package/package.json +17 -15
- package/src/index.ts +422 -151
package/dist/index.js
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
|
|
2
|
+
import { collectDefaultMetrics, Counter, Gauge, Histogram, Registry } from "prom-client";
|
|
3
|
+
import { SandboxBackend } from "@opengeni/contracts";
|
|
4
|
+
var httpHistogramBuckets = [5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];
|
|
5
|
+
var durationHistogramBuckets = [
|
|
6
|
+
0.01,
|
|
7
|
+
0.05,
|
|
8
|
+
0.1,
|
|
9
|
+
0.25,
|
|
10
|
+
0.5,
|
|
11
|
+
1,
|
|
12
|
+
2.5,
|
|
13
|
+
5,
|
|
14
|
+
10,
|
|
15
|
+
30,
|
|
16
|
+
60,
|
|
17
|
+
120,
|
|
18
|
+
300,
|
|
19
|
+
900,
|
|
20
|
+
1800,
|
|
21
|
+
3600
|
|
22
|
+
];
|
|
23
|
+
var SANDBOX_OPERATION_BACKENDS = /* @__PURE__ */ new Set([...SandboxBackend.options, "unprovisioned"]);
|
|
24
|
+
var SANDBOX_OPERATION_NAMES = /* @__PURE__ */ new Set([
|
|
25
|
+
"desktopInput",
|
|
26
|
+
"screenshot",
|
|
27
|
+
"exec",
|
|
28
|
+
"execCommand",
|
|
29
|
+
"writeStdin",
|
|
30
|
+
"cancelExecCommand",
|
|
31
|
+
"readFile",
|
|
32
|
+
"writeFile",
|
|
33
|
+
"listDir",
|
|
34
|
+
"pathExists",
|
|
35
|
+
"viewImage",
|
|
36
|
+
"materializeEntry",
|
|
37
|
+
"editor.createFile",
|
|
38
|
+
"editor.updateFile",
|
|
39
|
+
"editor.deleteFile",
|
|
40
|
+
"resolveExposedPort",
|
|
41
|
+
"serializeSessionState"
|
|
42
|
+
]);
|
|
3
43
|
function createObservability(settings, options) {
|
|
4
44
|
return new Observability(settings, options);
|
|
5
45
|
}
|
|
@@ -14,13 +54,35 @@ var Observability = class {
|
|
|
14
54
|
"deployment.environment": settings.environment,
|
|
15
55
|
"opengeni.component": options.component
|
|
16
56
|
};
|
|
57
|
+
this.registry.setDefaultLabels({
|
|
58
|
+
service: settings.serviceName,
|
|
59
|
+
environment: settings.environment,
|
|
60
|
+
component: options.component
|
|
61
|
+
});
|
|
62
|
+
if (settings.observabilityMetricsEnabled) {
|
|
63
|
+
collectDefaultMetrics({ register: this.registry, prefix: "opengeni_" });
|
|
64
|
+
this.setGauge({
|
|
65
|
+
name: "opengeni_build_info",
|
|
66
|
+
help: "OpenGeni build information.",
|
|
67
|
+
labels: {
|
|
68
|
+
version: buildVersion(),
|
|
69
|
+
revision: settings.deploymentRevision ?? "dev"
|
|
70
|
+
},
|
|
71
|
+
value: 1
|
|
72
|
+
});
|
|
73
|
+
}
|
|
17
74
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
75
|
+
registry = new Registry();
|
|
76
|
+
counters = /* @__PURE__ */ new Map();
|
|
77
|
+
gauges = /* @__PURE__ */ new Map();
|
|
78
|
+
histograms = /* @__PURE__ */ new Map();
|
|
79
|
+
registrations = /* @__PURE__ */ new Map();
|
|
21
80
|
now;
|
|
22
81
|
exporter;
|
|
23
82
|
resourceAttributes;
|
|
83
|
+
debug(message, attributes = {}) {
|
|
84
|
+
this.log("debug", message, attributes);
|
|
85
|
+
}
|
|
24
86
|
info(message, attributes = {}) {
|
|
25
87
|
this.log("info", message, attributes);
|
|
26
88
|
}
|
|
@@ -73,7 +135,8 @@ var Observability = class {
|
|
|
73
135
|
return;
|
|
74
136
|
}
|
|
75
137
|
ended = true;
|
|
76
|
-
const
|
|
138
|
+
const sanitizedError = input.error !== void 0 && input.error !== null ? sanitizeSpanError(input.error) : void 0;
|
|
139
|
+
const errorAttributes = sanitizedError ? errorToAttributes(sanitizedError) : {};
|
|
77
140
|
this.exportSpan({
|
|
78
141
|
traceId,
|
|
79
142
|
spanId,
|
|
@@ -85,49 +148,147 @@ var Observability = class {
|
|
|
85
148
|
...input.attributes,
|
|
86
149
|
...errorAttributes
|
|
87
150
|
},
|
|
88
|
-
error:
|
|
151
|
+
...sanitizedError ? { error: sanitizedError } : {}
|
|
89
152
|
});
|
|
90
153
|
}
|
|
91
154
|
};
|
|
92
155
|
}
|
|
93
156
|
recordHttpRequest(input) {
|
|
157
|
+
this.incrementCounter({
|
|
158
|
+
name: "opengeni_http_requests_total",
|
|
159
|
+
help: "Total HTTP requests handled by OpenGeni.",
|
|
160
|
+
labels: {
|
|
161
|
+
method: input.method,
|
|
162
|
+
route: input.route,
|
|
163
|
+
status: String(input.status),
|
|
164
|
+
component: this.options.component
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
this.observeHistogram({
|
|
168
|
+
name: "opengeni_http_request_duration_seconds",
|
|
169
|
+
help: "HTTP request duration in seconds.",
|
|
170
|
+
buckets: httpHistogramBuckets,
|
|
171
|
+
value: input.durationSeconds,
|
|
172
|
+
labels: {
|
|
173
|
+
method: input.method,
|
|
174
|
+
route: input.route,
|
|
175
|
+
component: this.options.component
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
recordWorkerActivity(input) {
|
|
180
|
+
this.incrementCounter({
|
|
181
|
+
name: "opengeni_worker_activity_runs_total",
|
|
182
|
+
help: "Total worker activity executions.",
|
|
183
|
+
labels: {
|
|
184
|
+
activity: input.activity,
|
|
185
|
+
status: input.status,
|
|
186
|
+
component: this.options.component
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
this.observeHistogram({
|
|
190
|
+
name: "opengeni_worker_activity_duration_seconds",
|
|
191
|
+
help: "Worker activity duration in seconds.",
|
|
192
|
+
buckets: durationHistogramBuckets,
|
|
193
|
+
value: input.durationSeconds,
|
|
194
|
+
labels: {
|
|
195
|
+
activity: input.activity,
|
|
196
|
+
component: this.options.component
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
incrementCounter(input) {
|
|
94
201
|
if (!this.settings.observabilityMetricsEnabled) {
|
|
95
202
|
return;
|
|
96
203
|
}
|
|
97
|
-
const labels =
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this.metrics.observe("opengeni_http_request_duration_seconds", histogramBuckets, input.durationSeconds, {
|
|
105
|
-
method: input.method,
|
|
106
|
-
route: input.route,
|
|
107
|
-
component: this.options.component
|
|
108
|
-
});
|
|
204
|
+
const labels = normalizeLabels(input.labels);
|
|
205
|
+
const counter = this.counter(
|
|
206
|
+
input.name,
|
|
207
|
+
input.help ?? `${input.name} counter.`,
|
|
208
|
+
Object.keys(labels)
|
|
209
|
+
);
|
|
210
|
+
counter.inc(labels, input.amount ?? 1);
|
|
109
211
|
}
|
|
110
|
-
|
|
212
|
+
setGauge(input) {
|
|
111
213
|
if (!this.settings.observabilityMetricsEnabled) {
|
|
112
214
|
return;
|
|
113
215
|
}
|
|
114
|
-
const labels =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
component: this.options.component
|
|
118
|
-
};
|
|
119
|
-
this.metrics.increment("opengeni_worker_activity_runs_total", labels);
|
|
120
|
-
this.metrics.observe("opengeni_worker_activity_duration_seconds", histogramBuckets, input.durationSeconds, {
|
|
121
|
-
activity: input.activity,
|
|
122
|
-
component: this.options.component
|
|
123
|
-
});
|
|
216
|
+
const labels = normalizeLabels(input.labels);
|
|
217
|
+
const gauge = this.gauge(input.name, input.help ?? `${input.name} gauge.`, Object.keys(labels));
|
|
218
|
+
gauge.set(labels, input.value);
|
|
124
219
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
});
|
|
220
|
+
incrementGauge(input) {
|
|
221
|
+
if (!this.settings.observabilityMetricsEnabled) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const labels = normalizeLabels(input.labels);
|
|
225
|
+
const gauge = this.gauge(input.name, input.help ?? `${input.name} gauge.`, Object.keys(labels));
|
|
226
|
+
gauge.inc(labels, input.amount ?? 1);
|
|
227
|
+
}
|
|
228
|
+
observeHistogram(input) {
|
|
229
|
+
if (!this.settings.observabilityMetricsEnabled) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const labels = normalizeLabels(input.labels);
|
|
233
|
+
const histogram = this.histogram(
|
|
234
|
+
input.name,
|
|
235
|
+
input.help ?? `${input.name} histogram.`,
|
|
236
|
+
Object.keys(labels),
|
|
237
|
+
input.buckets ?? durationHistogramBuckets
|
|
238
|
+
);
|
|
239
|
+
histogram.observe(labels, input.value);
|
|
240
|
+
}
|
|
241
|
+
async prometheusMetrics() {
|
|
242
|
+
if (!this.settings.observabilityMetricsEnabled) {
|
|
243
|
+
return "";
|
|
244
|
+
}
|
|
245
|
+
return await this.registry.metrics();
|
|
246
|
+
}
|
|
247
|
+
counter(name, help, labelNames) {
|
|
248
|
+
const existing = this.counters.get(name);
|
|
249
|
+
if (existing) {
|
|
250
|
+
this.assertRegistration(name, "counter", labelNames);
|
|
251
|
+
return existing;
|
|
252
|
+
}
|
|
253
|
+
this.register(name, "counter", labelNames);
|
|
254
|
+
const metric = new Counter({ name, help, labelNames, registers: [this.registry] });
|
|
255
|
+
this.counters.set(name, metric);
|
|
256
|
+
return metric;
|
|
257
|
+
}
|
|
258
|
+
gauge(name, help, labelNames) {
|
|
259
|
+
const existing = this.gauges.get(name);
|
|
260
|
+
if (existing) {
|
|
261
|
+
this.assertRegistration(name, "gauge", labelNames);
|
|
262
|
+
return existing;
|
|
263
|
+
}
|
|
264
|
+
this.register(name, "gauge", labelNames);
|
|
265
|
+
const metric = new Gauge({ name, help, labelNames, registers: [this.registry] });
|
|
266
|
+
this.gauges.set(name, metric);
|
|
267
|
+
return metric;
|
|
268
|
+
}
|
|
269
|
+
histogram(name, help, labelNames, buckets) {
|
|
270
|
+
const existing = this.histograms.get(name);
|
|
271
|
+
if (existing) {
|
|
272
|
+
this.assertRegistration(name, "histogram", labelNames);
|
|
273
|
+
return existing;
|
|
274
|
+
}
|
|
275
|
+
this.register(name, "histogram", labelNames);
|
|
276
|
+
const metric = new Histogram({ name, help, labelNames, buckets, registers: [this.registry] });
|
|
277
|
+
this.histograms.set(name, metric);
|
|
278
|
+
return metric;
|
|
279
|
+
}
|
|
280
|
+
register(name, kind, labelNames) {
|
|
281
|
+
const sorted = [...labelNames].sort();
|
|
282
|
+
this.registrations.set(name, { kind, labelNames: sorted });
|
|
283
|
+
}
|
|
284
|
+
assertRegistration(name, kind, labelNames) {
|
|
285
|
+
const registration = this.registrations.get(name);
|
|
286
|
+
const sorted = [...labelNames].sort();
|
|
287
|
+
if (!registration || registration.kind !== kind || registration.labelNames.length !== sorted.length || registration.labelNames.some((label, index) => label !== sorted[index])) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
`Metric ${name} was already registered as ${registration?.kind ?? "unknown"} with labels [${registration?.labelNames.join(",") ?? ""}], not ${kind} [${sorted.join(",")}]`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
131
292
|
}
|
|
132
293
|
exportSpan(span) {
|
|
133
294
|
if (!this.settings.observabilityOtlpEndpoint) {
|
|
@@ -135,33 +296,69 @@ var Observability = class {
|
|
|
135
296
|
}
|
|
136
297
|
const endpoint = `${this.settings.observabilityOtlpEndpoint.replace(/\/$/, "")}/v1/traces`;
|
|
137
298
|
const body = {
|
|
138
|
-
resourceSpans: [
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
scopeSpans: [{
|
|
143
|
-
scope: {
|
|
144
|
-
name: "@opengeni/observability",
|
|
145
|
-
version: "0.1.0"
|
|
299
|
+
resourceSpans: [
|
|
300
|
+
{
|
|
301
|
+
resource: {
|
|
302
|
+
attributes: otlpAttributes(this.resourceAttributes)
|
|
146
303
|
},
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
304
|
+
scopeSpans: [
|
|
305
|
+
{
|
|
306
|
+
scope: {
|
|
307
|
+
name: "@opengeni/observability",
|
|
308
|
+
version: "0.1.0"
|
|
309
|
+
},
|
|
310
|
+
spans: [
|
|
311
|
+
{
|
|
312
|
+
traceId: span.traceId,
|
|
313
|
+
spanId: span.spanId,
|
|
314
|
+
name: span.name,
|
|
315
|
+
kind: 1,
|
|
316
|
+
startTimeUnixNano: millisToNanos(span.startMs),
|
|
317
|
+
endTimeUnixNano: millisToNanos(span.endMs),
|
|
318
|
+
attributes: otlpAttributes(span.attributes),
|
|
319
|
+
status: span.error ? { code: 2, message: span.error.statusMessage } : { code: 1 }
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
}
|
|
323
|
+
]
|
|
324
|
+
}
|
|
325
|
+
]
|
|
159
326
|
};
|
|
160
|
-
void this.exporter(endpoint, body, parseHeaders(this.settings.observabilityOtlpHeaders)).catch(
|
|
161
|
-
|
|
162
|
-
|
|
327
|
+
void this.exporter(endpoint, body, parseHeaders(this.settings.observabilityOtlpHeaders)).catch(
|
|
328
|
+
(error) => {
|
|
329
|
+
this.warn("OTLP span export failed", { error: errorMessage(error), endpoint });
|
|
330
|
+
}
|
|
331
|
+
);
|
|
163
332
|
}
|
|
164
333
|
};
|
|
334
|
+
function sandboxOperationMetricObserver(observability) {
|
|
335
|
+
return (observation) => {
|
|
336
|
+
const backend = SANDBOX_OPERATION_BACKENDS.has(observation.backend) ? observation.backend : "unknown";
|
|
337
|
+
const op = SANDBOX_OPERATION_NAMES.has(observation.op) ? observation.op : "unknown";
|
|
338
|
+
try {
|
|
339
|
+
observability.incrementCounter({
|
|
340
|
+
name: "opengeni_sandbox_operations_total",
|
|
341
|
+
help: "Physical routed sandbox provider operations by backend, operation, and outcome.",
|
|
342
|
+
labels: { backend, op, outcome: observation.outcome }
|
|
343
|
+
});
|
|
344
|
+
observability.observeHistogram({
|
|
345
|
+
name: "opengeni_sandbox_operation_duration_seconds",
|
|
346
|
+
help: "Physical routed sandbox provider-operation duration in seconds.",
|
|
347
|
+
labels: { backend, op },
|
|
348
|
+
value: Math.max(0, observation.durationMs) / 1e3
|
|
349
|
+
});
|
|
350
|
+
} catch {
|
|
351
|
+
try {
|
|
352
|
+
observability.incrementCounter({
|
|
353
|
+
name: "opengeni_observability_observer_errors_total",
|
|
354
|
+
help: "Observability observer failures isolated from product execution.",
|
|
355
|
+
labels: { observer: "sandbox_operation" }
|
|
356
|
+
});
|
|
357
|
+
} catch {
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
165
362
|
function logStartupDependencyRetry(observability, event) {
|
|
166
363
|
const message = event.error instanceof Error ? event.error.message : String(event.error);
|
|
167
364
|
observability.warn("Startup dependency connection failed; retrying", {
|
|
@@ -172,87 +369,38 @@ function logStartupDependencyRetry(observability, event) {
|
|
|
172
369
|
error: message
|
|
173
370
|
});
|
|
174
371
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const key = metricKey(name, labels);
|
|
180
|
-
this.counters.set(key, (this.counters.get(key) ?? 0) + amount);
|
|
181
|
-
}
|
|
182
|
-
observe(name, buckets, value, labels) {
|
|
183
|
-
const key = metricKey(name, labels);
|
|
184
|
-
const histogram = this.histograms.get(key) ?? {
|
|
185
|
-
buckets,
|
|
186
|
-
counts: buckets.map(() => 0),
|
|
187
|
-
sum: 0,
|
|
188
|
-
count: 0,
|
|
189
|
-
labels
|
|
190
|
-
};
|
|
191
|
-
histogram.sum += value;
|
|
192
|
-
histogram.count += 1;
|
|
193
|
-
for (let index = 0; index < buckets.length; index += 1) {
|
|
194
|
-
if (value <= buckets[index]) {
|
|
195
|
-
histogram.counts[index] = (histogram.counts[index] ?? 0) + 1;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
this.histograms.set(key, histogram);
|
|
199
|
-
}
|
|
200
|
-
toPrometheus(resourceLabels) {
|
|
201
|
-
const lines = [
|
|
202
|
-
"# HELP opengeni_http_requests_total Total HTTP requests handled by the OpenGeni API.",
|
|
203
|
-
"# TYPE opengeni_http_requests_total counter"
|
|
204
|
-
];
|
|
205
|
-
for (const [key, value] of this.counters) {
|
|
206
|
-
const { name, labels } = parseMetricKey(key);
|
|
207
|
-
if (name.endsWith("_total")) {
|
|
208
|
-
lines.push(`${name}${formatLabels({ ...resourceLabels, ...labels })} ${value}`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
lines.push(
|
|
212
|
-
"# HELP opengeni_http_request_duration_seconds HTTP request duration in seconds.",
|
|
213
|
-
"# TYPE opengeni_http_request_duration_seconds histogram",
|
|
214
|
-
"# HELP opengeni_worker_activity_runs_total Total worker activity executions.",
|
|
215
|
-
"# TYPE opengeni_worker_activity_runs_total counter",
|
|
216
|
-
"# HELP opengeni_worker_activity_duration_seconds Worker activity duration in seconds.",
|
|
217
|
-
"# TYPE opengeni_worker_activity_duration_seconds histogram"
|
|
218
|
-
);
|
|
219
|
-
for (const [key, histogram] of this.histograms) {
|
|
220
|
-
const { name, labels } = parseMetricKey(key);
|
|
221
|
-
const baseLabels = { ...resourceLabels, ...labels };
|
|
222
|
-
for (let index = 0; index < histogram.buckets.length; index += 1) {
|
|
223
|
-
lines.push(`${name}_bucket${formatLabels({ ...baseLabels, le: String(histogram.buckets[index]) })} ${histogram.counts[index]}`);
|
|
224
|
-
}
|
|
225
|
-
lines.push(`${name}_bucket${formatLabels({ ...baseLabels, le: "+Inf" })} ${histogram.count}`);
|
|
226
|
-
lines.push(`${name}_sum${formatLabels(baseLabels)} ${histogram.sum}`);
|
|
227
|
-
lines.push(`${name}_count${formatLabels(baseLabels)} ${histogram.count}`);
|
|
228
|
-
}
|
|
229
|
-
return `${lines.join("\n")}
|
|
230
|
-
`;
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
function metricKey(name, labels) {
|
|
234
|
-
return JSON.stringify({ name, labels: Object.fromEntries(Object.entries(labels).sort(([a], [b]) => a.localeCompare(b))) });
|
|
372
|
+
function normalizeLabels(labels = {}) {
|
|
373
|
+
return Object.fromEntries(
|
|
374
|
+
Object.entries(labels).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => [key, String(value)]).sort((left, right) => left[0].localeCompare(right[0]))
|
|
375
|
+
);
|
|
235
376
|
}
|
|
236
|
-
function
|
|
237
|
-
return
|
|
377
|
+
function buildVersion() {
|
|
378
|
+
return process.env.OPENGENI_VERSION ?? process.env.npm_package_version ?? "dev";
|
|
238
379
|
}
|
|
239
|
-
function
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
return `{${entries.map(([key, value]) => `${key}="${escapeMetricLabel(value)}"`).join(",")}}`;
|
|
380
|
+
function cleanAttributes(attributes) {
|
|
381
|
+
return Object.fromEntries(
|
|
382
|
+
Object.entries(attributes).filter(([, value]) => value !== void 0)
|
|
383
|
+
);
|
|
245
384
|
}
|
|
246
|
-
function
|
|
247
|
-
|
|
385
|
+
function sanitizeSpanError(error) {
|
|
386
|
+
const type = error instanceof Error && /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/.test(error.name) ? error.name : "Error";
|
|
387
|
+
const statusCode = errorStatusCode(error);
|
|
388
|
+
return {
|
|
389
|
+
type,
|
|
390
|
+
...statusCode === void 0 ? {} : { statusCode },
|
|
391
|
+
statusMessage: statusCode === void 0 ? "operation failed" : `HTTP ${statusCode}`
|
|
392
|
+
};
|
|
248
393
|
}
|
|
249
|
-
function
|
|
250
|
-
|
|
394
|
+
function errorStatusCode(error) {
|
|
395
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
396
|
+
const value = error.status;
|
|
397
|
+
const statusCode = Number.isInteger(value) && typeof value === "number" ? value : error.statusCode;
|
|
398
|
+
return Number.isInteger(statusCode) && typeof statusCode === "number" && statusCode >= 100 && statusCode <= 599 ? statusCode : void 0;
|
|
251
399
|
}
|
|
252
400
|
function errorToAttributes(error) {
|
|
253
401
|
return {
|
|
254
|
-
"error.type": error
|
|
255
|
-
"error.
|
|
402
|
+
"error.type": error.type,
|
|
403
|
+
...error.statusCode === void 0 ? {} : { "error.status_code": error.statusCode }
|
|
256
404
|
};
|
|
257
405
|
}
|
|
258
406
|
function errorMessage(error) {
|
|
@@ -271,7 +419,12 @@ function otlpValue(value) {
|
|
|
271
419
|
if (typeof value === "boolean") {
|
|
272
420
|
return { boolValue: value };
|
|
273
421
|
}
|
|
274
|
-
return { stringValue: value === null ? "" : value };
|
|
422
|
+
return { stringValue: value === null ? "" : boundedOtlpString(value) };
|
|
423
|
+
}
|
|
424
|
+
function boundedOtlpString(value) {
|
|
425
|
+
const bytes = new TextEncoder().encode(value);
|
|
426
|
+
if (bytes.byteLength <= 512) return value;
|
|
427
|
+
return `${new TextDecoder().decode(bytes.slice(0, 509))}\u2026`;
|
|
275
428
|
}
|
|
276
429
|
function millisToNanos(ms) {
|
|
277
430
|
return String(BigInt(Math.round(ms)) * 1000000n);
|
|
@@ -310,6 +463,7 @@ export {
|
|
|
310
463
|
Observability,
|
|
311
464
|
createObservability,
|
|
312
465
|
logStartupDependencyRetry,
|
|
313
|
-
parseHeaders
|
|
466
|
+
parseHeaders,
|
|
467
|
+
sandboxOperationMetricObserver
|
|
314
468
|
};
|
|
315
469
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type AttributeValue = string | number | boolean | null | undefined;\nexport type Attributes = Record<string, AttributeValue>;\n\nexport type ObservabilitySettings = {\n serviceName: string;\n environment: string;\n observabilityStructuredLogs: boolean;\n observabilityMetricsEnabled: boolean;\n observabilityOtlpEndpoint?: string | undefined;\n observabilityOtlpHeaders: string;\n};\n\nexport type ObservabilityOptions = {\n component: string;\n now?: () => number;\n exporter?: (url: string, body: unknown, headers: Record<string, string>) => Promise<void>;\n};\n\nexport type Span = {\n traceId: string;\n spanId: string;\n end: (input?: { attributes?: Attributes; error?: unknown }) => void;\n};\n\nconst histogramBuckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];\n\nexport function createObservability(settings: ObservabilitySettings, options: ObservabilityOptions): Observability {\n return new Observability(settings, options);\n}\n\nexport class Observability {\n private readonly metrics = new MetricsRegistry();\n private readonly now: () => number;\n private readonly exporter: (url: string, body: unknown, headers: Record<string, string>) => Promise<void>;\n private readonly resourceAttributes: Attributes;\n\n constructor(private readonly settings: ObservabilitySettings, private readonly options: ObservabilityOptions) {\n this.now = options.now ?? Date.now;\n this.exporter = options.exporter ?? defaultExporter;\n this.resourceAttributes = {\n \"service.name\": settings.serviceName,\n \"deployment.environment\": settings.environment,\n \"opengeni.component\": options.component,\n };\n }\n\n info(message: string, attributes: Attributes = {}): void {\n this.log(\"info\", message, attributes);\n }\n\n warn(message: string, attributes: Attributes = {}): void {\n this.log(\"warn\", message, attributes);\n }\n\n error(message: string, attributes: Attributes = {}): void {\n this.log(\"error\", message, attributes);\n }\n\n log(level: \"debug\" | \"info\" | \"warn\" | \"error\", message: string, attributes: Attributes = {}): void {\n if (!this.settings.observabilityStructuredLogs) {\n const line = attributes.error ? `${message}: ${String(attributes.error)}` : message;\n if (level === \"warn\") {\n console.warn(line);\n } else if (level === \"error\") {\n console.error(line);\n } else {\n console.log(line);\n }\n return;\n }\n const record = {\n timestamp: new Date(this.now()).toISOString(),\n level,\n message,\n service: this.settings.serviceName,\n environment: this.settings.environment,\n component: this.options.component,\n ...cleanAttributes(attributes),\n };\n const serialized = JSON.stringify(record);\n if (level === \"warn\") {\n console.warn(serialized);\n } else if (level === \"error\") {\n console.error(serialized);\n } else {\n console.log(serialized);\n }\n }\n\n startSpan(name: string, attributes: Attributes = {}): Span {\n const traceId = randomHex(16);\n const spanId = randomHex(8);\n const startMs = this.now();\n let ended = false;\n return {\n traceId,\n spanId,\n end: (input = {}) => {\n if (ended) {\n return;\n }\n ended = true;\n const errorAttributes = input.error ? errorToAttributes(input.error) : {};\n this.exportSpan({\n traceId,\n spanId,\n name,\n startMs,\n endMs: this.now(),\n attributes: {\n ...attributes,\n ...input.attributes,\n ...errorAttributes,\n },\n error: input.error,\n });\n },\n };\n }\n\n recordHttpRequest(input: { method: string; route: string; status: number; durationSeconds: number }): void {\n if (!this.settings.observabilityMetricsEnabled) {\n return;\n }\n const labels = {\n method: input.method,\n route: input.route,\n status: String(input.status),\n component: this.options.component,\n };\n this.metrics.increment(\"opengeni_http_requests_total\", labels);\n this.metrics.observe(\"opengeni_http_request_duration_seconds\", histogramBuckets, input.durationSeconds, {\n method: input.method,\n route: input.route,\n component: this.options.component,\n });\n }\n\n recordWorkerActivity(input: { activity: string; status: string; durationSeconds: number }): void {\n if (!this.settings.observabilityMetricsEnabled) {\n return;\n }\n const labels = {\n activity: input.activity,\n status: input.status,\n component: this.options.component,\n };\n this.metrics.increment(\"opengeni_worker_activity_runs_total\", labels);\n this.metrics.observe(\"opengeni_worker_activity_duration_seconds\", histogramBuckets, input.durationSeconds, {\n activity: input.activity,\n component: this.options.component,\n });\n }\n\n prometheusMetrics(): string {\n return this.metrics.toPrometheus({\n service: this.settings.serviceName,\n environment: this.settings.environment,\n component: this.options.component,\n });\n }\n\n private exportSpan(span: {\n traceId: string;\n spanId: string;\n name: string;\n startMs: number;\n endMs: number;\n attributes: Attributes;\n error?: unknown;\n }): void {\n if (!this.settings.observabilityOtlpEndpoint) {\n return;\n }\n const endpoint = `${this.settings.observabilityOtlpEndpoint.replace(/\\/$/, \"\")}/v1/traces`;\n const body = {\n resourceSpans: [{\n resource: {\n attributes: otlpAttributes(this.resourceAttributes),\n },\n scopeSpans: [{\n scope: {\n name: \"@opengeni/observability\",\n version: \"0.1.0\",\n },\n spans: [{\n traceId: span.traceId,\n spanId: span.spanId,\n name: span.name,\n kind: 1,\n startTimeUnixNano: millisToNanos(span.startMs),\n endTimeUnixNano: millisToNanos(span.endMs),\n attributes: otlpAttributes(span.attributes),\n status: span.error ? { code: 2, message: errorMessage(span.error) } : { code: 1 },\n }],\n }],\n }],\n };\n void this.exporter(endpoint, body, parseHeaders(this.settings.observabilityOtlpHeaders)).catch((error) => {\n this.warn(\"OTLP span export failed\", { error: errorMessage(error), endpoint });\n });\n }\n}\n\nexport type StartupDependencyRetryEvent = {\n label: string;\n attempt: number;\n attempts: number;\n delayMs: number;\n error: unknown;\n};\n\nexport function logStartupDependencyRetry(observability: Observability, event: StartupDependencyRetryEvent): void {\n const message = event.error instanceof Error ? event.error.message : String(event.error);\n observability.warn(\"Startup dependency connection failed; retrying\", {\n dependency: event.label,\n attempt: event.attempt,\n attempts: event.attempts,\n delayMs: event.delayMs,\n error: message,\n });\n}\n\nclass MetricsRegistry {\n private readonly counters = new Map<string, number>();\n private readonly histograms = new Map<string, { buckets: number[]; counts: number[]; sum: number; count: number; labels: Record<string, string> }>();\n\n increment(name: string, labels: Record<string, string>, amount = 1): void {\n const key = metricKey(name, labels);\n this.counters.set(key, (this.counters.get(key) ?? 0) + amount);\n }\n\n observe(name: string, buckets: number[], value: number, labels: Record<string, string>): void {\n const key = metricKey(name, labels);\n const histogram = this.histograms.get(key) ?? {\n buckets,\n counts: buckets.map(() => 0),\n sum: 0,\n count: 0,\n labels,\n };\n histogram.sum += value;\n histogram.count += 1;\n for (let index = 0; index < buckets.length; index += 1) {\n if (value <= buckets[index]!) {\n histogram.counts[index] = (histogram.counts[index] ?? 0) + 1;\n }\n }\n this.histograms.set(key, histogram);\n }\n\n toPrometheus(resourceLabels: Record<string, string>): string {\n const lines = [\n \"# HELP opengeni_http_requests_total Total HTTP requests handled by the OpenGeni API.\",\n \"# TYPE opengeni_http_requests_total counter\",\n ];\n for (const [key, value] of this.counters) {\n const { name, labels } = parseMetricKey(key);\n if (name.endsWith(\"_total\")) {\n lines.push(`${name}${formatLabels({ ...resourceLabels, ...labels })} ${value}`);\n }\n }\n lines.push(\n \"# HELP opengeni_http_request_duration_seconds HTTP request duration in seconds.\",\n \"# TYPE opengeni_http_request_duration_seconds histogram\",\n \"# HELP opengeni_worker_activity_runs_total Total worker activity executions.\",\n \"# TYPE opengeni_worker_activity_runs_total counter\",\n \"# HELP opengeni_worker_activity_duration_seconds Worker activity duration in seconds.\",\n \"# TYPE opengeni_worker_activity_duration_seconds histogram\",\n );\n for (const [key, histogram] of this.histograms) {\n const { name, labels } = parseMetricKey(key);\n const baseLabels = { ...resourceLabels, ...labels };\n for (let index = 0; index < histogram.buckets.length; index += 1) {\n lines.push(`${name}_bucket${formatLabels({ ...baseLabels, le: String(histogram.buckets[index]) })} ${histogram.counts[index]}`);\n }\n lines.push(`${name}_bucket${formatLabels({ ...baseLabels, le: \"+Inf\" })} ${histogram.count}`);\n lines.push(`${name}_sum${formatLabels(baseLabels)} ${histogram.sum}`);\n lines.push(`${name}_count${formatLabels(baseLabels)} ${histogram.count}`);\n }\n return `${lines.join(\"\\n\")}\\n`;\n }\n}\n\nfunction metricKey(name: string, labels: Record<string, string>): string {\n return JSON.stringify({ name, labels: Object.fromEntries(Object.entries(labels).sort(([a], [b]) => a.localeCompare(b))) });\n}\n\nfunction parseMetricKey(key: string): { name: string; labels: Record<string, string> } {\n return JSON.parse(key) as { name: string; labels: Record<string, string> };\n}\n\nfunction formatLabels(labels: Record<string, string>): string {\n const entries = Object.entries(labels).filter(([, value]) => value.length > 0);\n if (entries.length === 0) {\n return \"\";\n }\n return `{${entries.map(([key, value]) => `${key}=\"${escapeMetricLabel(value)}\"`).join(\",\")}}`;\n}\n\nfunction escapeMetricLabel(value: string): string {\n return value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, \"\\\\\\\"\").replace(/\\n/g, \"\\\\n\");\n}\n\nfunction cleanAttributes(attributes: Attributes): Record<string, string | number | boolean | null> {\n return Object.fromEntries(Object.entries(attributes).filter(([, value]) => value !== undefined)) as Record<string, string | number | boolean | null>;\n}\n\nfunction errorToAttributes(error: unknown): Attributes {\n return {\n \"error.type\": error instanceof Error ? error.name : \"Error\",\n \"error.message\": errorMessage(error),\n };\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction otlpAttributes(attributes: Attributes): Array<{ key: string; value: Record<string, string | number | boolean> }> {\n return Object.entries(cleanAttributes(attributes)).map(([key, value]) => ({\n key,\n value: otlpValue(value),\n }));\n}\n\nfunction otlpValue(value: string | number | boolean | null): Record<string, string | number | boolean> {\n if (typeof value === \"number\") {\n return Number.isInteger(value) ? { intValue: value } : { doubleValue: value };\n }\n if (typeof value === \"boolean\") {\n return { boolValue: value };\n }\n return { stringValue: value === null ? \"\" : value };\n}\n\nfunction millisToNanos(ms: number): string {\n return String(BigInt(Math.round(ms)) * 1_000_000n);\n}\n\nfunction randomHex(bytes: number): string {\n const values = crypto.getRandomValues(new Uint8Array(bytes));\n return Array.from(values, (value) => value.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport function parseHeaders(value: string): Record<string, string> {\n if (!value.trim()) {\n return {};\n }\n const entries: Array<[string, string]> = value.split(\",\").map((pair): [string, string] => {\n const separator = pair.indexOf(\"=\");\n if (separator === -1) {\n return [pair.trim(), \"\"];\n }\n return [pair.slice(0, separator).trim(), pair.slice(separator + 1).trim()];\n }).filter(([key]) => key.length > 0);\n return Object.fromEntries(entries);\n}\n\nasync function defaultExporter(url: string, body: unknown, headers: Record<string, string>): Promise<void> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...headers,\n },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n throw new Error(`OTLP endpoint returned HTTP ${response.status}`);\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { collectDefaultMetrics, Counter, Gauge, Histogram, Registry } from \"prom-client\";\nimport { SandboxBackend } from \"@opengeni/contracts\";\n\nexport type AttributeValue = string | number | boolean | null | undefined;\nexport type Attributes = Record<string, AttributeValue>;\n\nexport type ObservabilitySettings = {\n serviceName: string;\n environment: string;\n deploymentRevision?: string | undefined;\n observabilityStructuredLogs: boolean;\n observabilityMetricsEnabled: boolean;\n observabilityOtlpEndpoint?: string | undefined;\n observabilityOtlpHeaders: string;\n};\n\nexport type ObservabilityOptions = {\n component: string;\n now?: () => number;\n exporter?: (url: string, body: unknown, headers: Record<string, string>) => Promise<void>;\n};\n\nexport type Span = {\n traceId: string;\n spanId: string;\n end: (input?: { attributes?: Attributes; error?: unknown }) => void;\n};\n\nexport type MetricLabels = Record<string, AttributeValue>;\n\nconst httpHistogramBuckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];\nconst durationHistogramBuckets = [\n 0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60, 120, 300, 900, 1800, 3600,\n];\n\nconst SANDBOX_OPERATION_BACKENDS = new Set<string>([...SandboxBackend.options, \"unprovisioned\"]);\n\nconst SANDBOX_OPERATION_NAMES = new Set([\n \"desktopInput\",\n \"screenshot\",\n \"exec\",\n \"execCommand\",\n \"writeStdin\",\n \"cancelExecCommand\",\n \"readFile\",\n \"writeFile\",\n \"listDir\",\n \"pathExists\",\n \"viewImage\",\n \"materializeEntry\",\n \"editor.createFile\",\n \"editor.updateFile\",\n \"editor.deleteFile\",\n \"resolveExposedPort\",\n \"serializeSessionState\",\n]);\n\nexport type SandboxOperationMetricObservation = {\n backend: string;\n op: string;\n outcome: \"ok\" | \"failed\";\n durationMs: number;\n};\n\nexport function createObservability(\n settings: ObservabilitySettings,\n options: ObservabilityOptions,\n): Observability {\n return new Observability(settings, options);\n}\n\ntype MetricRegistration = {\n kind: \"counter\" | \"gauge\" | \"histogram\";\n labelNames: string[];\n};\n\nexport class Observability {\n private readonly registry = new Registry();\n private readonly counters = new Map<string, Counter<string>>();\n private readonly gauges = new Map<string, Gauge<string>>();\n private readonly histograms = new Map<string, Histogram<string>>();\n private readonly registrations = new Map<string, MetricRegistration>();\n private readonly now: () => number;\n private readonly exporter: (\n url: string,\n body: unknown,\n headers: Record<string, string>,\n ) => Promise<void>;\n private readonly resourceAttributes: Attributes;\n\n constructor(\n private readonly settings: ObservabilitySettings,\n private readonly options: ObservabilityOptions,\n ) {\n this.now = options.now ?? Date.now;\n this.exporter = options.exporter ?? defaultExporter;\n this.resourceAttributes = {\n \"service.name\": settings.serviceName,\n \"deployment.environment\": settings.environment,\n \"opengeni.component\": options.component,\n };\n this.registry.setDefaultLabels({\n service: settings.serviceName,\n environment: settings.environment,\n component: options.component,\n });\n if (settings.observabilityMetricsEnabled) {\n collectDefaultMetrics({ register: this.registry, prefix: \"opengeni_\" });\n this.setGauge({\n name: \"opengeni_build_info\",\n help: \"OpenGeni build information.\",\n labels: {\n version: buildVersion(),\n revision: settings.deploymentRevision ?? \"dev\",\n },\n value: 1,\n });\n }\n }\n\n debug(message: string, attributes: Attributes = {}): void {\n this.log(\"debug\", message, attributes);\n }\n\n info(message: string, attributes: Attributes = {}): void {\n this.log(\"info\", message, attributes);\n }\n\n warn(message: string, attributes: Attributes = {}): void {\n this.log(\"warn\", message, attributes);\n }\n\n error(message: string, attributes: Attributes = {}): void {\n this.log(\"error\", message, attributes);\n }\n\n log(\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n attributes: Attributes = {},\n ): void {\n if (!this.settings.observabilityStructuredLogs) {\n const line = attributes.error ? `${message}: ${String(attributes.error)}` : message;\n if (level === \"warn\") {\n console.warn(line);\n } else if (level === \"error\") {\n console.error(line);\n } else {\n console.log(line);\n }\n return;\n }\n const record = {\n timestamp: new Date(this.now()).toISOString(),\n level,\n message,\n service: this.settings.serviceName,\n environment: this.settings.environment,\n component: this.options.component,\n ...cleanAttributes(attributes),\n };\n const serialized = JSON.stringify(record);\n if (level === \"warn\") {\n console.warn(serialized);\n } else if (level === \"error\") {\n console.error(serialized);\n } else {\n console.log(serialized);\n }\n }\n\n startSpan(name: string, attributes: Attributes = {}): Span {\n const traceId = randomHex(16);\n const spanId = randomHex(8);\n const startMs = this.now();\n let ended = false;\n return {\n traceId,\n spanId,\n end: (input = {}) => {\n if (ended) {\n return;\n }\n ended = true;\n const sanitizedError =\n input.error !== undefined && input.error !== null\n ? sanitizeSpanError(input.error)\n : undefined;\n const errorAttributes = sanitizedError ? errorToAttributes(sanitizedError) : {};\n this.exportSpan({\n traceId,\n spanId,\n name,\n startMs,\n endMs: this.now(),\n attributes: {\n ...attributes,\n ...input.attributes,\n ...errorAttributes,\n },\n ...(sanitizedError ? { error: sanitizedError } : {}),\n });\n },\n };\n }\n\n recordHttpRequest(input: {\n method: string;\n route: string;\n status: number;\n durationSeconds: number;\n }): void {\n this.incrementCounter({\n name: \"opengeni_http_requests_total\",\n help: \"Total HTTP requests handled by OpenGeni.\",\n labels: {\n method: input.method,\n route: input.route,\n status: String(input.status),\n component: this.options.component,\n },\n });\n this.observeHistogram({\n name: \"opengeni_http_request_duration_seconds\",\n help: \"HTTP request duration in seconds.\",\n buckets: httpHistogramBuckets,\n value: input.durationSeconds,\n labels: {\n method: input.method,\n route: input.route,\n component: this.options.component,\n },\n });\n }\n\n recordWorkerActivity(input: { activity: string; status: string; durationSeconds: number }): void {\n this.incrementCounter({\n name: \"opengeni_worker_activity_runs_total\",\n help: \"Total worker activity executions.\",\n labels: {\n activity: input.activity,\n status: input.status,\n component: this.options.component,\n },\n });\n this.observeHistogram({\n name: \"opengeni_worker_activity_duration_seconds\",\n help: \"Worker activity duration in seconds.\",\n buckets: durationHistogramBuckets,\n value: input.durationSeconds,\n labels: {\n activity: input.activity,\n component: this.options.component,\n },\n });\n }\n\n incrementCounter(input: {\n name: string;\n help?: string;\n labels?: MetricLabels;\n amount?: number;\n }): void {\n if (!this.settings.observabilityMetricsEnabled) {\n return;\n }\n const labels = normalizeLabels(input.labels);\n const counter = this.counter(\n input.name,\n input.help ?? `${input.name} counter.`,\n Object.keys(labels),\n );\n counter.inc(labels as never, input.amount ?? 1);\n }\n\n setGauge(input: { name: string; help?: string; labels?: MetricLabels; value: number }): void {\n if (!this.settings.observabilityMetricsEnabled) {\n return;\n }\n const labels = normalizeLabels(input.labels);\n const gauge = this.gauge(input.name, input.help ?? `${input.name} gauge.`, Object.keys(labels));\n gauge.set(labels as never, input.value);\n }\n\n incrementGauge(input: {\n name: string;\n help?: string;\n labels?: MetricLabels;\n amount?: number;\n }): void {\n if (!this.settings.observabilityMetricsEnabled) {\n return;\n }\n const labels = normalizeLabels(input.labels);\n const gauge = this.gauge(input.name, input.help ?? `${input.name} gauge.`, Object.keys(labels));\n gauge.inc(labels as never, input.amount ?? 1);\n }\n\n observeHistogram(input: {\n name: string;\n help?: string;\n labels?: MetricLabels;\n value: number;\n buckets?: number[];\n }): void {\n if (!this.settings.observabilityMetricsEnabled) {\n return;\n }\n const labels = normalizeLabels(input.labels);\n const histogram = this.histogram(\n input.name,\n input.help ?? `${input.name} histogram.`,\n Object.keys(labels),\n input.buckets ?? durationHistogramBuckets,\n );\n histogram.observe(labels as never, input.value);\n }\n\n async prometheusMetrics(): Promise<string> {\n if (!this.settings.observabilityMetricsEnabled) {\n return \"\";\n }\n return await this.registry.metrics();\n }\n\n private counter(name: string, help: string, labelNames: string[]): Counter<string> {\n const existing = this.counters.get(name);\n if (existing) {\n this.assertRegistration(name, \"counter\", labelNames);\n return existing;\n }\n this.register(name, \"counter\", labelNames);\n const metric = new Counter({ name, help, labelNames, registers: [this.registry] });\n this.counters.set(name, metric);\n return metric;\n }\n\n private gauge(name: string, help: string, labelNames: string[]): Gauge<string> {\n const existing = this.gauges.get(name);\n if (existing) {\n this.assertRegistration(name, \"gauge\", labelNames);\n return existing;\n }\n this.register(name, \"gauge\", labelNames);\n const metric = new Gauge({ name, help, labelNames, registers: [this.registry] });\n this.gauges.set(name, metric);\n return metric;\n }\n\n private histogram(\n name: string,\n help: string,\n labelNames: string[],\n buckets: number[],\n ): Histogram<string> {\n const existing = this.histograms.get(name);\n if (existing) {\n this.assertRegistration(name, \"histogram\", labelNames);\n return existing;\n }\n this.register(name, \"histogram\", labelNames);\n const metric = new Histogram({ name, help, labelNames, buckets, registers: [this.registry] });\n this.histograms.set(name, metric);\n return metric;\n }\n\n private register(name: string, kind: MetricRegistration[\"kind\"], labelNames: string[]): void {\n const sorted = [...labelNames].sort();\n this.registrations.set(name, { kind, labelNames: sorted });\n }\n\n private assertRegistration(\n name: string,\n kind: MetricRegistration[\"kind\"],\n labelNames: string[],\n ): void {\n const registration = this.registrations.get(name);\n const sorted = [...labelNames].sort();\n if (\n !registration ||\n registration.kind !== kind ||\n registration.labelNames.length !== sorted.length ||\n registration.labelNames.some((label, index) => label !== sorted[index])\n ) {\n throw new Error(\n `Metric ${name} was already registered as ${registration?.kind ?? \"unknown\"} ` +\n `with labels [${registration?.labelNames.join(\",\") ?? \"\"}], not ${kind} [${sorted.join(\",\")}]`,\n );\n }\n }\n\n private exportSpan(span: {\n traceId: string;\n spanId: string;\n name: string;\n startMs: number;\n endMs: number;\n attributes: Attributes;\n error?: SanitizedSpanError;\n }): void {\n if (!this.settings.observabilityOtlpEndpoint) {\n return;\n }\n const endpoint = `${this.settings.observabilityOtlpEndpoint.replace(/\\/$/, \"\")}/v1/traces`;\n const body = {\n resourceSpans: [\n {\n resource: {\n attributes: otlpAttributes(this.resourceAttributes),\n },\n scopeSpans: [\n {\n scope: {\n name: \"@opengeni/observability\",\n version: \"0.1.0\",\n },\n spans: [\n {\n traceId: span.traceId,\n spanId: span.spanId,\n name: span.name,\n kind: 1,\n startTimeUnixNano: millisToNanos(span.startMs),\n endTimeUnixNano: millisToNanos(span.endMs),\n attributes: otlpAttributes(span.attributes),\n status: span.error ? { code: 2, message: span.error.statusMessage } : { code: 1 },\n },\n ],\n },\n ],\n },\n ],\n };\n void this.exporter(endpoint, body, parseHeaders(this.settings.observabilityOtlpHeaders)).catch(\n (error) => {\n this.warn(\"OTLP span export failed\", { error: errorMessage(error), endpoint });\n },\n );\n }\n}\n\n/**\n * Convert routed sandbox-provider observations into one bounded Prometheus\n * contract shared by API-direct and worker-turn execution. Provider/session\n * identifiers, commands, paths, and other request data can never become labels:\n * a future backend or operation is deliberately collapsed to `unknown` until\n * this allowlist is reviewed.\n *\n * The returned callback is fail-safe. Metrics must never change the provider\n * operation's result; a registration/exporter error is counted best-effort and\n * then swallowed at this telemetry boundary.\n */\nexport function sandboxOperationMetricObserver(\n observability: Observability,\n): (observation: SandboxOperationMetricObservation) => void {\n return (observation) => {\n const backend = SANDBOX_OPERATION_BACKENDS.has(observation.backend)\n ? observation.backend\n : \"unknown\";\n const op = SANDBOX_OPERATION_NAMES.has(observation.op) ? observation.op : \"unknown\";\n try {\n observability.incrementCounter({\n name: \"opengeni_sandbox_operations_total\",\n help: \"Physical routed sandbox provider operations by backend, operation, and outcome.\",\n labels: { backend, op, outcome: observation.outcome },\n });\n observability.observeHistogram({\n name: \"opengeni_sandbox_operation_duration_seconds\",\n help: \"Physical routed sandbox provider-operation duration in seconds.\",\n labels: { backend, op },\n value: Math.max(0, observation.durationMs) / 1_000,\n });\n } catch {\n try {\n observability.incrementCounter({\n name: \"opengeni_observability_observer_errors_total\",\n help: \"Observability observer failures isolated from product execution.\",\n labels: { observer: \"sandbox_operation\" },\n });\n } catch {\n // The metrics registry itself is unhealthy. Product execution remains\n // authoritative and must not inherit an observability failure.\n }\n }\n };\n}\n\nexport type StartupDependencyRetryEvent = {\n label: string;\n attempt: number;\n attempts: number;\n delayMs: number;\n error: unknown;\n};\n\nexport function logStartupDependencyRetry(\n observability: Observability,\n event: StartupDependencyRetryEvent,\n): void {\n const message = event.error instanceof Error ? event.error.message : String(event.error);\n observability.warn(\"Startup dependency connection failed; retrying\", {\n dependency: event.label,\n attempt: event.attempt,\n attempts: event.attempts,\n delayMs: event.delayMs,\n error: message,\n });\n}\n\nfunction normalizeLabels(labels: MetricLabels = {}): Record<string, string> {\n return Object.fromEntries(\n Object.entries(labels)\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([key, value]): [string, string] => [key, String(value)])\n .sort((left, right) => left[0].localeCompare(right[0])),\n );\n}\n\nfunction buildVersion(): string {\n return process.env.OPENGENI_VERSION ?? process.env.npm_package_version ?? \"dev\";\n}\n\nfunction cleanAttributes(attributes: Attributes): Record<string, string | number | boolean | null> {\n return Object.fromEntries(\n Object.entries(attributes).filter(([, value]) => value !== undefined),\n ) as Record<string, string | number | boolean | null>;\n}\n\ntype SanitizedSpanError = {\n type: string;\n statusCode?: number;\n statusMessage: string;\n};\n\nfunction sanitizeSpanError(error: unknown): SanitizedSpanError {\n const type =\n error instanceof Error && /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/.test(error.name)\n ? error.name\n : \"Error\";\n const statusCode = errorStatusCode(error);\n return {\n type,\n ...(statusCode === undefined ? {} : { statusCode }),\n statusMessage: statusCode === undefined ? \"operation failed\" : `HTTP ${statusCode}`,\n };\n}\n\nfunction errorStatusCode(error: unknown): number | undefined {\n if (typeof error !== \"object\" || error === null) return undefined;\n const value = (error as { status?: unknown; statusCode?: unknown }).status;\n const statusCode =\n Number.isInteger(value) && typeof value === \"number\"\n ? value\n : (error as { statusCode?: unknown }).statusCode;\n return Number.isInteger(statusCode) &&\n typeof statusCode === \"number\" &&\n statusCode >= 100 &&\n statusCode <= 599\n ? statusCode\n : undefined;\n}\n\nfunction errorToAttributes(error: SanitizedSpanError): Attributes {\n return {\n \"error.type\": error.type,\n ...(error.statusCode === undefined ? {} : { \"error.status_code\": error.statusCode }),\n };\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction otlpAttributes(\n attributes: Attributes,\n): Array<{ key: string; value: Record<string, string | number | boolean> }> {\n return Object.entries(cleanAttributes(attributes)).map(([key, value]) => ({\n key,\n value: otlpValue(value),\n }));\n}\n\nfunction otlpValue(\n value: string | number | boolean | null,\n): Record<string, string | number | boolean> {\n if (typeof value === \"number\") {\n return Number.isInteger(value) ? { intValue: value } : { doubleValue: value };\n }\n if (typeof value === \"boolean\") {\n return { boolValue: value };\n }\n return { stringValue: value === null ? \"\" : boundedOtlpString(value) };\n}\n\nfunction boundedOtlpString(value: string): string {\n const bytes = new TextEncoder().encode(value);\n if (bytes.byteLength <= 512) return value;\n return `${new TextDecoder().decode(bytes.slice(0, 509))}…`;\n}\n\nfunction millisToNanos(ms: number): string {\n return String(BigInt(Math.round(ms)) * 1_000_000n);\n}\n\nfunction randomHex(bytes: number): string {\n const values = crypto.getRandomValues(new Uint8Array(bytes));\n return Array.from(values, (value) => value.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport function parseHeaders(value: string): Record<string, string> {\n if (!value.trim()) {\n return {};\n }\n const entries: Array<[string, string]> = value\n .split(\",\")\n .map((pair): [string, string] => {\n const separator = pair.indexOf(\"=\");\n if (separator === -1) {\n return [pair.trim(), \"\"];\n }\n return [pair.slice(0, separator).trim(), pair.slice(separator + 1).trim()];\n })\n .filter(([key]) => key.length > 0);\n return Object.fromEntries(entries);\n}\n\nasync function defaultExporter(\n url: string,\n body: unknown,\n headers: Record<string, string>,\n): Promise<void> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...headers,\n },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n throw new Error(`OTLP endpoint returned HTTP ${response.status}`);\n }\n}\n"],"mappings":"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","names":[]}
|