@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/dist/index.js CHANGED
@@ -1,5 +1,45 @@
1
1
  // src/index.ts
2
- var histogramBuckets = [5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];
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
- settings;
19
- options;
20
- metrics = new MetricsRegistry();
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 errorAttributes = input.error ? errorToAttributes(input.error) : {};
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: input.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
- method: input.method,
99
- route: input.route,
100
- status: String(input.status),
101
- component: this.options.component
102
- };
103
- this.metrics.increment("opengeni_http_requests_total", labels);
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
- recordWorkerActivity(input) {
212
+ setGauge(input) {
111
213
  if (!this.settings.observabilityMetricsEnabled) {
112
214
  return;
113
215
  }
114
- const labels = {
115
- activity: input.activity,
116
- status: input.status,
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
- prometheusMetrics() {
126
- return this.metrics.toPrometheus({
127
- service: this.settings.serviceName,
128
- environment: this.settings.environment,
129
- component: this.options.component
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
- resource: {
140
- attributes: otlpAttributes(this.resourceAttributes)
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
- spans: [{
148
- traceId: span.traceId,
149
- spanId: span.spanId,
150
- name: span.name,
151
- kind: 1,
152
- startTimeUnixNano: millisToNanos(span.startMs),
153
- endTimeUnixNano: millisToNanos(span.endMs),
154
- attributes: otlpAttributes(span.attributes),
155
- status: span.error ? { code: 2, message: errorMessage(span.error) } : { code: 1 }
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((error) => {
161
- this.warn("OTLP span export failed", { error: errorMessage(error), endpoint });
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
- var MetricsRegistry = class {
176
- counters = /* @__PURE__ */ new Map();
177
- histograms = /* @__PURE__ */ new Map();
178
- increment(name, labels, amount = 1) {
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 parseMetricKey(key) {
237
- return JSON.parse(key);
377
+ function buildVersion() {
378
+ return process.env.OPENGENI_VERSION ?? process.env.npm_package_version ?? "dev";
238
379
  }
239
- function formatLabels(labels) {
240
- const entries = Object.entries(labels).filter(([, value]) => value.length > 0);
241
- if (entries.length === 0) {
242
- return "";
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 escapeMetricLabel(value) {
247
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
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 cleanAttributes(attributes) {
250
- return Object.fromEntries(Object.entries(attributes).filter(([, value]) => value !== void 0));
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 instanceof Error ? error.name : "Error",
255
- "error.message": errorMessage(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":[]}