@opengeni/observability 0.5.0 → 0.6.2
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.d.ts +9 -1
- package/dist/index.js +267 -25
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +338 -41
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,14 @@ export type Span = {
|
|
|
23
23
|
}) => void;
|
|
24
24
|
};
|
|
25
25
|
export type MetricLabels = Record<string, AttributeValue>;
|
|
26
|
+
/**
|
|
27
|
+
* Stable, non-reversible correlation key for one logical sandbox lease. Public
|
|
28
|
+
* telemetry intentionally drops workspace/group identifiers; this key lets an
|
|
29
|
+
* operator correlate API, worker, and reaper failures without publishing
|
|
30
|
+
* either UUID. The domain separator prevents reuse as a generic identifier
|
|
31
|
+
* digest.
|
|
32
|
+
*/
|
|
33
|
+
export declare function sandboxLeaseTelemetryKey(workspaceId: string, sandboxGroupId: string): string;
|
|
26
34
|
/**
|
|
27
35
|
* Stable selectors shared by OpenGeni's runtime metrics and optional
|
|
28
36
|
* Prometheus/Grafana distribution. Operators can use these values for custom
|
|
@@ -37,7 +45,7 @@ export declare const OPENGENI_OBSERVABILITY_DISTRIBUTION: {
|
|
|
37
45
|
export type SandboxOperationMetricObservation = {
|
|
38
46
|
backend: string;
|
|
39
47
|
op: string;
|
|
40
|
-
outcome: "ok" | "failed";
|
|
48
|
+
outcome: "ok" | "not_found" | "failed";
|
|
41
49
|
durationMs: number;
|
|
42
50
|
};
|
|
43
51
|
export declare function createObservability(settings: ObservabilitySettings, options: ObservabilityOptions): Observability;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import { createHash } from "crypto";
|
|
2
3
|
import { collectDefaultMetrics, Counter, Gauge, Histogram, Registry } from "prom-client";
|
|
3
4
|
import { SandboxBackend } from "@opengeni/contracts";
|
|
5
|
+
function sandboxLeaseTelemetryKey(workspaceId, sandboxGroupId) {
|
|
6
|
+
return `slk_${createHash("sha256").update("opengeni:sandbox-lease-telemetry:v1\0").update(workspaceId).update("\0").update(sandboxGroupId).digest("hex").slice(0, 32)}`;
|
|
7
|
+
}
|
|
4
8
|
var OPENGENI_OBSERVABILITY_DISTRIBUTION = {
|
|
5
9
|
monitoringNamespaceLabel: "opengeni.ai/monitoring",
|
|
6
10
|
monitoringNamespaceLabelValue: "enabled",
|
|
@@ -46,6 +50,190 @@ var SANDBOX_OPERATION_NAMES = /* @__PURE__ */ new Set([
|
|
|
46
50
|
"resolveExposedPort",
|
|
47
51
|
"serializeSessionState"
|
|
48
52
|
]);
|
|
53
|
+
var PUBLIC_TELEMETRY_ATTRIBUTE_KEYS = /* @__PURE__ */ new Set([
|
|
54
|
+
"http.request.method",
|
|
55
|
+
"http.response.status_code",
|
|
56
|
+
"opengeni.route",
|
|
57
|
+
"opengeni.duration_ms",
|
|
58
|
+
"opengeni.finalization_duration_ms",
|
|
59
|
+
"opengeni.trigger_kind",
|
|
60
|
+
"opengeni.status",
|
|
61
|
+
"error.type",
|
|
62
|
+
"error.status_code",
|
|
63
|
+
"method",
|
|
64
|
+
"route",
|
|
65
|
+
"status",
|
|
66
|
+
"durationMs",
|
|
67
|
+
"attempt",
|
|
68
|
+
"attempts",
|
|
69
|
+
"delayMs",
|
|
70
|
+
"provider",
|
|
71
|
+
"providerApi",
|
|
72
|
+
"model",
|
|
73
|
+
"inputTokens",
|
|
74
|
+
"outputTokens",
|
|
75
|
+
"cachedTokens",
|
|
76
|
+
"cacheWriteTokens",
|
|
77
|
+
"reasoningTokens",
|
|
78
|
+
"accountChangedFromPrevCall",
|
|
79
|
+
"rejectedFields",
|
|
80
|
+
"dependency",
|
|
81
|
+
"activity",
|
|
82
|
+
"backend",
|
|
83
|
+
"op",
|
|
84
|
+
"outcome",
|
|
85
|
+
"eventType",
|
|
86
|
+
"surface",
|
|
87
|
+
"reason",
|
|
88
|
+
"originalBytes",
|
|
89
|
+
"deliveredBytes",
|
|
90
|
+
"estimatedOriginalTokens",
|
|
91
|
+
"estimatedDeliveredTokens",
|
|
92
|
+
"fullEvidenceAvailable",
|
|
93
|
+
"retainedOutputKind"
|
|
94
|
+
]);
|
|
95
|
+
var PUBLIC_TELEMETRY_OPAQUE_ATTRIBUTE_PATTERNS = /* @__PURE__ */ new Map([
|
|
96
|
+
["sandboxLeaseKey", /^slk_[0-9a-f]{32}$/]
|
|
97
|
+
]);
|
|
98
|
+
var PUBLIC_CHANNEL_A_OPERATIONS = /* @__PURE__ */ new Set([
|
|
99
|
+
"fs.list",
|
|
100
|
+
"fs.list-batch",
|
|
101
|
+
"fs.read",
|
|
102
|
+
"fs.write",
|
|
103
|
+
"fs.delete",
|
|
104
|
+
"fs.move",
|
|
105
|
+
"fs.mkdir",
|
|
106
|
+
"git.status",
|
|
107
|
+
"git.diff",
|
|
108
|
+
"git.read-batch",
|
|
109
|
+
"git.log",
|
|
110
|
+
"git.show",
|
|
111
|
+
"terminal.exec",
|
|
112
|
+
"terminal.pty.open",
|
|
113
|
+
"terminal.pty.write",
|
|
114
|
+
"terminal.pty.resize",
|
|
115
|
+
"terminal.pty.close",
|
|
116
|
+
"read",
|
|
117
|
+
"mutation"
|
|
118
|
+
]);
|
|
119
|
+
var PUBLIC_CHANNEL_A_FAILURE_REASONS = /* @__PURE__ */ new Set([
|
|
120
|
+
"request_cancelled",
|
|
121
|
+
"provider_read_busy",
|
|
122
|
+
"provider_unavailable",
|
|
123
|
+
"lifecycle_conflict",
|
|
124
|
+
"request_rejected",
|
|
125
|
+
"unexpected"
|
|
126
|
+
]);
|
|
127
|
+
var PUBLIC_TELEMETRY_ERROR_CLASSES = /* @__PURE__ */ new Set([
|
|
128
|
+
"OperationError",
|
|
129
|
+
"CodexCheckpointOperationError",
|
|
130
|
+
"CodexFleetShadowOperationError",
|
|
131
|
+
"ComputerActionTimeoutError",
|
|
132
|
+
"ComputerUnavailableError",
|
|
133
|
+
"CredentialRenewalOperationError",
|
|
134
|
+
"CredentialReadOperationError",
|
|
135
|
+
"EventPublishOperationError",
|
|
136
|
+
"GitCredentialRenewalOperationError",
|
|
137
|
+
"HostExportOperationError",
|
|
138
|
+
"HttpOperationError",
|
|
139
|
+
"McpLifecycleError",
|
|
140
|
+
"McpOperationError",
|
|
141
|
+
"MemoryEmbeddingOperationError",
|
|
142
|
+
"MemorySearchOperationError",
|
|
143
|
+
"NatsAuthCalloutOperationError",
|
|
144
|
+
"OAuthOperationError",
|
|
145
|
+
"RunCredentialRenewalOperationError",
|
|
146
|
+
"RunStateCompatibilityError",
|
|
147
|
+
"SandboxChannelAOperationError",
|
|
148
|
+
"SnapshotOperationError",
|
|
149
|
+
"StartupDependencyError",
|
|
150
|
+
"TelemetryExportError",
|
|
151
|
+
"CodemodeOperationError",
|
|
152
|
+
"CodemodeTokenRenewalOperationError",
|
|
153
|
+
"WorkerLifecycleOperation",
|
|
154
|
+
"WorkerLifecycleOperationError",
|
|
155
|
+
"WorkerOperationError",
|
|
156
|
+
"WorkflowWakeOperationError"
|
|
157
|
+
]);
|
|
158
|
+
var PUBLIC_TELEMETRY_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
159
|
+
"agent_command_wake_failed",
|
|
160
|
+
"artifact_materializer_native_input_framing_failed",
|
|
161
|
+
"artifact_materializer_native_snapshot_open_failed",
|
|
162
|
+
"artifact_materializer_native_state_mismatch",
|
|
163
|
+
"artifact_materializer_source_content_type_mismatch",
|
|
164
|
+
"artifact_materializer_source_open_failed",
|
|
165
|
+
"artifact_materializer_source_revalidation_failed",
|
|
166
|
+
"artifact_materializer_source_stream_identity_mismatch",
|
|
167
|
+
"cleared_goal_live_publish_failed",
|
|
168
|
+
"codex_failover_checkpoint_failed",
|
|
169
|
+
"codex_fleet_shadow_failed",
|
|
170
|
+
"codex_lease_loss_checkpoint_failed",
|
|
171
|
+
"codex_active_credential_read_failed",
|
|
172
|
+
"command_yield_timeout",
|
|
173
|
+
"conflict",
|
|
174
|
+
"control_wake_dispatch_failed",
|
|
175
|
+
"fenced_event_live_publish_failed",
|
|
176
|
+
"forbidden",
|
|
177
|
+
"host_export_batch_delivery_failed",
|
|
178
|
+
"host_export_failure_settlement_stale",
|
|
179
|
+
"host_export_pump_iteration_failed",
|
|
180
|
+
"host_export_retention_prune_failed",
|
|
181
|
+
"idempotency_conflict",
|
|
182
|
+
"incompatible_exposed_ports",
|
|
183
|
+
"internal_error",
|
|
184
|
+
"limit_exceeded",
|
|
185
|
+
"mcp_close_failed",
|
|
186
|
+
"mcp_connect_failed",
|
|
187
|
+
"mcp_tool_call_failed",
|
|
188
|
+
"mcp_tools_list_failed",
|
|
189
|
+
"mcp_transport_failed",
|
|
190
|
+
"memory_edit_embedding_failed",
|
|
191
|
+
"memory_hybrid_vector_failed",
|
|
192
|
+
"memory_save_embedding_failed",
|
|
193
|
+
"nats_auth_callout_start_failed",
|
|
194
|
+
"nested_agent_depth_exceeded",
|
|
195
|
+
"nested_agent_depth_override_forbidden",
|
|
196
|
+
"not_found",
|
|
197
|
+
"oauth_operation_failed",
|
|
198
|
+
"otlp_export_failed",
|
|
199
|
+
"payment_required",
|
|
200
|
+
"provider_verification_failed",
|
|
201
|
+
"sandbox_channel_a_cancelled",
|
|
202
|
+
"sandbox_channel_a_lifecycle_conflict",
|
|
203
|
+
"sandbox_channel_a_operation_failed",
|
|
204
|
+
"sandbox_channel_a_provider_busy",
|
|
205
|
+
"sandbox_channel_a_provider_unavailable",
|
|
206
|
+
"screenshot_capture_failed",
|
|
207
|
+
"session_event_live_publish_failed",
|
|
208
|
+
"session_workflow_wake_failed",
|
|
209
|
+
"snapshot_operation_failed",
|
|
210
|
+
"startup_dependency_retry",
|
|
211
|
+
"tool_list_too_large",
|
|
212
|
+
"tool_result_too_large",
|
|
213
|
+
"codemode_operation_failed",
|
|
214
|
+
"unauthenticated",
|
|
215
|
+
"upstream_unavailable",
|
|
216
|
+
"validation_failed",
|
|
217
|
+
"worker_draining",
|
|
218
|
+
"worker_operation_failed",
|
|
219
|
+
"worker_shutdown_request_failed",
|
|
220
|
+
"workspace_control_live_publish_failed"
|
|
221
|
+
]);
|
|
222
|
+
var PUBLIC_TELEMETRY_ERROR_ORIGINS = /* @__PURE__ */ new Set([
|
|
223
|
+
"api",
|
|
224
|
+
"core",
|
|
225
|
+
"db",
|
|
226
|
+
"events",
|
|
227
|
+
"host-export",
|
|
228
|
+
"oauth",
|
|
229
|
+
"observability",
|
|
230
|
+
"runtime",
|
|
231
|
+
"sandbox-computer",
|
|
232
|
+
"sandbox-resume",
|
|
233
|
+
"codemode",
|
|
234
|
+
"worker",
|
|
235
|
+
"worker-lifecycle"
|
|
236
|
+
]);
|
|
49
237
|
function createObservability(settings, options) {
|
|
50
238
|
return new Observability(settings, options);
|
|
51
239
|
}
|
|
@@ -63,7 +251,8 @@ var Observability = class {
|
|
|
63
251
|
this.registry.setDefaultLabels({
|
|
64
252
|
service: settings.serviceName,
|
|
65
253
|
environment: settings.environment,
|
|
66
|
-
component: options.component
|
|
254
|
+
component: options.component,
|
|
255
|
+
deployment_revision: settings.deploymentRevision ?? "dev"
|
|
67
256
|
});
|
|
68
257
|
if (settings.observabilityMetricsEnabled) {
|
|
69
258
|
collectDefaultMetrics({ register: this.registry, prefix: "opengeni_" });
|
|
@@ -99,14 +288,14 @@ var Observability = class {
|
|
|
99
288
|
this.log("error", message, attributes);
|
|
100
289
|
}
|
|
101
290
|
log(level, message, attributes = {}) {
|
|
291
|
+
const publicAttributes = projectPublicTelemetryAttributes(attributes);
|
|
102
292
|
if (!this.settings.observabilityStructuredLogs) {
|
|
103
|
-
const line = attributes.error ? `${message}: ${String(attributes.error)}` : message;
|
|
104
293
|
if (level === "warn") {
|
|
105
|
-
console.warn(
|
|
294
|
+
console.warn(message);
|
|
106
295
|
} else if (level === "error") {
|
|
107
|
-
console.error(
|
|
296
|
+
console.error(message);
|
|
108
297
|
} else {
|
|
109
|
-
console.log(
|
|
298
|
+
console.log(message);
|
|
110
299
|
}
|
|
111
300
|
return;
|
|
112
301
|
}
|
|
@@ -117,7 +306,7 @@ var Observability = class {
|
|
|
117
306
|
service: this.settings.serviceName,
|
|
118
307
|
environment: this.settings.environment,
|
|
119
308
|
component: this.options.component,
|
|
120
|
-
...cleanAttributes(
|
|
309
|
+
...cleanAttributes(publicAttributes)
|
|
121
310
|
};
|
|
122
311
|
const serialized = JSON.stringify(record);
|
|
123
312
|
if (level === "warn") {
|
|
@@ -141,8 +330,8 @@ var Observability = class {
|
|
|
141
330
|
return;
|
|
142
331
|
}
|
|
143
332
|
ended = true;
|
|
144
|
-
const
|
|
145
|
-
const errorAttributes =
|
|
333
|
+
const telemetryError = input.error !== void 0 && input.error !== null ? projectSpanErrorForTelemetry(input.error) : void 0;
|
|
334
|
+
const errorAttributes = telemetryError ? errorToAttributes(telemetryError) : {};
|
|
146
335
|
this.exportSpan({
|
|
147
336
|
traceId,
|
|
148
337
|
spanId,
|
|
@@ -150,11 +339,13 @@ var Observability = class {
|
|
|
150
339
|
startMs,
|
|
151
340
|
endMs: this.now(),
|
|
152
341
|
attributes: {
|
|
153
|
-
...
|
|
154
|
-
|
|
155
|
-
|
|
342
|
+
...projectPublicTelemetryAttributes({
|
|
343
|
+
...attributes,
|
|
344
|
+
...input.attributes,
|
|
345
|
+
...errorAttributes
|
|
346
|
+
})
|
|
156
347
|
},
|
|
157
|
-
...
|
|
348
|
+
...telemetryError ? { error: telemetryError } : {}
|
|
158
349
|
});
|
|
159
350
|
}
|
|
160
351
|
};
|
|
@@ -331,8 +522,12 @@ var Observability = class {
|
|
|
331
522
|
]
|
|
332
523
|
};
|
|
333
524
|
void this.exporter(endpoint, body, parseHeaders(this.settings.observabilityOtlpHeaders)).catch(
|
|
334
|
-
(
|
|
335
|
-
this.warn("OTLP span export failed", {
|
|
525
|
+
() => {
|
|
526
|
+
this.warn("OTLP span export failed", {
|
|
527
|
+
errorClass: "TelemetryExportError",
|
|
528
|
+
errorCode: "otlp_export_failed",
|
|
529
|
+
origin: "observability"
|
|
530
|
+
});
|
|
336
531
|
}
|
|
337
532
|
);
|
|
338
533
|
}
|
|
@@ -366,13 +561,14 @@ function sandboxOperationMetricObserver(observability) {
|
|
|
366
561
|
};
|
|
367
562
|
}
|
|
368
563
|
function logStartupDependencyRetry(observability, event) {
|
|
369
|
-
const message = event.error instanceof Error ? event.error.message : String(event.error);
|
|
370
564
|
observability.warn("Startup dependency connection failed; retrying", {
|
|
371
565
|
dependency: event.label,
|
|
372
566
|
attempt: event.attempt,
|
|
373
567
|
attempts: event.attempts,
|
|
374
568
|
delayMs: event.delayMs,
|
|
375
|
-
|
|
569
|
+
errorClass: "StartupDependencyError",
|
|
570
|
+
errorCode: "startup_dependency_retry",
|
|
571
|
+
origin: "observability"
|
|
376
572
|
});
|
|
377
573
|
}
|
|
378
574
|
function normalizeLabels(labels = {}) {
|
|
@@ -388,20 +584,68 @@ function cleanAttributes(attributes) {
|
|
|
388
584
|
Object.entries(attributes).filter(([, value]) => value !== void 0)
|
|
389
585
|
);
|
|
390
586
|
}
|
|
391
|
-
function
|
|
392
|
-
|
|
587
|
+
function projectPublicTelemetryAttributes(attributes) {
|
|
588
|
+
if ("errorClass" in attributes || "errorCode" in attributes) {
|
|
589
|
+
return {
|
|
590
|
+
...projectPublicChannelADiagnosticAttributes(attributes),
|
|
591
|
+
...projectPublicDiagnosticAttributes(attributes)
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
return Object.fromEntries(
|
|
595
|
+
Object.entries(attributes).filter(([key, value]) => {
|
|
596
|
+
if (value === void 0) return false;
|
|
597
|
+
if (PUBLIC_TELEMETRY_ATTRIBUTE_KEYS.has(key)) return true;
|
|
598
|
+
const pattern = PUBLIC_TELEMETRY_OPAQUE_ATTRIBUTE_PATTERNS.get(key);
|
|
599
|
+
return typeof value === "string" && pattern?.test(value) === true;
|
|
600
|
+
})
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
function projectPublicChannelADiagnosticAttributes(attributes) {
|
|
604
|
+
if (attributes.errorClass !== "SandboxChannelAOperationError") return {};
|
|
605
|
+
const backend = attributes.backend;
|
|
606
|
+
const op = attributes.op;
|
|
607
|
+
const outcome = attributes.outcome;
|
|
608
|
+
const reason = attributes.reason;
|
|
609
|
+
const durationMs = attributes.durationMs;
|
|
610
|
+
const sandboxLeaseKey = attributes.sandboxLeaseKey;
|
|
611
|
+
return {
|
|
612
|
+
...typeof backend === "string" && SANDBOX_OPERATION_BACKENDS.has(backend) ? { backend } : {},
|
|
613
|
+
...typeof op === "string" && PUBLIC_CHANNEL_A_OPERATIONS.has(op) ? { op } : {},
|
|
614
|
+
...outcome === "failed" ? { outcome } : {},
|
|
615
|
+
...typeof reason === "string" && PUBLIC_CHANNEL_A_FAILURE_REASONS.has(reason) ? { reason } : {},
|
|
616
|
+
...typeof durationMs === "number" && Number.isFinite(durationMs) && durationMs >= 0 ? { durationMs } : {},
|
|
617
|
+
...typeof sandboxLeaseKey === "string" && PUBLIC_TELEMETRY_OPAQUE_ATTRIBUTE_PATTERNS.get("sandboxLeaseKey")?.test(sandboxLeaseKey) ? { sandboxLeaseKey } : {}
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
function projectPublicDiagnosticAttributes(attributes) {
|
|
621
|
+
const errorClass = attributes.errorClass;
|
|
622
|
+
const errorCode = attributes.errorCode;
|
|
623
|
+
const status = attributes.status;
|
|
624
|
+
const origin = attributes.origin;
|
|
625
|
+
return {
|
|
626
|
+
errorClass: typeof errorClass === "string" && PUBLIC_TELEMETRY_ERROR_CLASSES.has(errorClass) ? errorClass : "OperationError",
|
|
627
|
+
...typeof errorCode === "string" && PUBLIC_TELEMETRY_ERROR_CODES.has(errorCode) ? { errorCode } : {},
|
|
628
|
+
...typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599 ? { status } : {},
|
|
629
|
+
...typeof origin === "string" && PUBLIC_TELEMETRY_ERROR_ORIGINS.has(origin) ? { origin } : {}
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function projectSpanErrorForTelemetry(error) {
|
|
393
633
|
const statusCode = errorStatusCode(error);
|
|
394
634
|
return {
|
|
395
|
-
type,
|
|
635
|
+
type: "OperationError",
|
|
396
636
|
...statusCode === void 0 ? {} : { statusCode },
|
|
397
637
|
statusMessage: statusCode === void 0 ? "operation failed" : `HTTP ${statusCode}`
|
|
398
638
|
};
|
|
399
639
|
}
|
|
400
640
|
function errorStatusCode(error) {
|
|
401
641
|
if (typeof error !== "object" || error === null) return void 0;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
642
|
+
try {
|
|
643
|
+
const value = error.status;
|
|
644
|
+
const statusCode = Number.isInteger(value) && typeof value === "number" ? value : error.statusCode;
|
|
645
|
+
return Number.isInteger(statusCode) && typeof statusCode === "number" && statusCode >= 100 && statusCode <= 599 ? statusCode : void 0;
|
|
646
|
+
} catch {
|
|
647
|
+
return void 0;
|
|
648
|
+
}
|
|
405
649
|
}
|
|
406
650
|
function errorToAttributes(error) {
|
|
407
651
|
return {
|
|
@@ -409,9 +653,6 @@ function errorToAttributes(error) {
|
|
|
409
653
|
...error.statusCode === void 0 ? {} : { "error.status_code": error.statusCode }
|
|
410
654
|
};
|
|
411
655
|
}
|
|
412
|
-
function errorMessage(error) {
|
|
413
|
-
return error instanceof Error ? error.message : String(error);
|
|
414
|
-
}
|
|
415
656
|
function otlpAttributes(attributes) {
|
|
416
657
|
return Object.entries(cleanAttributes(attributes)).map(([key, value]) => ({
|
|
417
658
|
key,
|
|
@@ -471,6 +712,7 @@ export {
|
|
|
471
712
|
createObservability,
|
|
472
713
|
logStartupDependencyRetry,
|
|
473
714
|
parseHeaders,
|
|
715
|
+
sandboxLeaseTelemetryKey,
|
|
474
716
|
sandboxOperationMetricObserver
|
|
475
717
|
};
|
|
476
718
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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\n/**\n * Stable selectors shared by OpenGeni's runtime metrics and optional\n * Prometheus/Grafana distribution. Operators can use these values for custom\n * namespaces and dashboard ConfigMaps without duplicating chart internals.\n */\nexport const OPENGENI_OBSERVABILITY_DISTRIBUTION = {\n monitoringNamespaceLabel: \"opengeni.ai/monitoring\",\n monitoringNamespaceLabelValue: \"enabled\",\n grafanaDashboardLabel: \"grafana_dashboard\",\n grafanaDashboardLabelValue: \"1\",\n} as const;\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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { 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\n/**\n * Stable, non-reversible correlation key for one logical sandbox lease. Public\n * telemetry intentionally drops workspace/group identifiers; this key lets an\n * operator correlate API, worker, and reaper failures without publishing\n * either UUID. The domain separator prevents reuse as a generic identifier\n * digest.\n */\nexport function sandboxLeaseTelemetryKey(workspaceId: string, sandboxGroupId: string): string {\n return `slk_${createHash(\"sha256\")\n .update(\"opengeni:sandbox-lease-telemetry:v1\\0\")\n .update(workspaceId)\n .update(\"\\0\")\n .update(sandboxGroupId)\n .digest(\"hex\")\n .slice(0, 32)}`;\n}\n\n/**\n * Stable selectors shared by OpenGeni's runtime metrics and optional\n * Prometheus/Grafana distribution. Operators can use these values for custom\n * namespaces and dashboard ConfigMaps without duplicating chart internals.\n */\nexport const OPENGENI_OBSERVABILITY_DISTRIBUTION = {\n monitoringNamespaceLabel: \"opengeni.ai/monitoring\",\n monitoringNamespaceLabelValue: \"enabled\",\n grafanaDashboardLabel: \"grafana_dashboard\",\n grafanaDashboardLabelValue: \"1\",\n} as const;\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\n/**\n * External logs and OTLP are public/third-party projections, not canonical\n * OpenGeni storage. Only this reviewed closed set of operational fields may\n * cross that boundary. Unknown keys are omitted regardless of their value, so\n * a new diagnostic, identifier, command, response, or provider field cannot\n * become public by accident. This is schema projection, never value inspection\n * or rewriting.\n */\nconst PUBLIC_TELEMETRY_ATTRIBUTE_KEYS = new Set([\n \"http.request.method\",\n \"http.response.status_code\",\n \"opengeni.route\",\n \"opengeni.duration_ms\",\n \"opengeni.finalization_duration_ms\",\n \"opengeni.trigger_kind\",\n \"opengeni.status\",\n \"error.type\",\n \"error.status_code\",\n \"method\",\n \"route\",\n \"status\",\n \"durationMs\",\n \"attempt\",\n \"attempts\",\n \"delayMs\",\n \"provider\",\n \"providerApi\",\n \"model\",\n \"inputTokens\",\n \"outputTokens\",\n \"cachedTokens\",\n \"cacheWriteTokens\",\n \"reasoningTokens\",\n \"accountChangedFromPrevCall\",\n \"rejectedFields\",\n \"dependency\",\n \"activity\",\n \"backend\",\n \"op\",\n \"outcome\",\n \"eventType\",\n \"surface\",\n \"reason\",\n \"originalBytes\",\n \"deliveredBytes\",\n \"estimatedOriginalTokens\",\n \"estimatedDeliveredTokens\",\n \"fullEvidenceAvailable\",\n \"retainedOutputKind\",\n]);\n\n/** Opaque correlation fields require both a reviewed name and a closed value\n * grammar. Merely adding one to the ordinary allow-list would let an unrelated\n * caller accidentally publish a raw identifier under that name. */\nconst PUBLIC_TELEMETRY_OPAQUE_ATTRIBUTE_PATTERNS = new Map<string, RegExp>([\n [\"sandboxLeaseKey\", /^slk_[0-9a-f]{32}$/],\n]);\n\nconst PUBLIC_CHANNEL_A_OPERATIONS = new Set([\n \"fs.list\",\n \"fs.list-batch\",\n \"fs.read\",\n \"fs.write\",\n \"fs.delete\",\n \"fs.move\",\n \"fs.mkdir\",\n \"git.status\",\n \"git.diff\",\n \"git.read-batch\",\n \"git.log\",\n \"git.show\",\n \"terminal.exec\",\n \"terminal.pty.open\",\n \"terminal.pty.write\",\n \"terminal.pty.resize\",\n \"terminal.pty.close\",\n \"read\",\n \"mutation\",\n]);\n\nconst PUBLIC_CHANNEL_A_FAILURE_REASONS = new Set([\n \"request_cancelled\",\n \"provider_read_busy\",\n \"provider_unavailable\",\n \"lifecycle_conflict\",\n \"request_rejected\",\n \"unexpected\",\n]);\n\n/**\n * Public diagnostic values are protocol constants, never values inferred from\n * an exception's name, constructor, code, message, or enumerable properties.\n * Unknown classes collapse to one fixed fallback; unknown codes and origins\n * are omitted rather than copied through based on syntax.\n */\nconst PUBLIC_TELEMETRY_ERROR_CLASSES = new Set([\n \"OperationError\",\n \"CodexCheckpointOperationError\",\n \"CodexFleetShadowOperationError\",\n \"ComputerActionTimeoutError\",\n \"ComputerUnavailableError\",\n \"CredentialRenewalOperationError\",\n \"CredentialReadOperationError\",\n \"EventPublishOperationError\",\n \"GitCredentialRenewalOperationError\",\n \"HostExportOperationError\",\n \"HttpOperationError\",\n \"McpLifecycleError\",\n \"McpOperationError\",\n \"MemoryEmbeddingOperationError\",\n \"MemorySearchOperationError\",\n \"NatsAuthCalloutOperationError\",\n \"OAuthOperationError\",\n \"RunCredentialRenewalOperationError\",\n \"RunStateCompatibilityError\",\n \"SandboxChannelAOperationError\",\n \"SnapshotOperationError\",\n \"StartupDependencyError\",\n \"TelemetryExportError\",\n \"CodemodeOperationError\",\n \"CodemodeTokenRenewalOperationError\",\n \"WorkerLifecycleOperation\",\n \"WorkerLifecycleOperationError\",\n \"WorkerOperationError\",\n \"WorkflowWakeOperationError\",\n]);\n\nconst PUBLIC_TELEMETRY_ERROR_CODES = new Set([\n \"agent_command_wake_failed\",\n \"artifact_materializer_native_input_framing_failed\",\n \"artifact_materializer_native_snapshot_open_failed\",\n \"artifact_materializer_native_state_mismatch\",\n \"artifact_materializer_source_content_type_mismatch\",\n \"artifact_materializer_source_open_failed\",\n \"artifact_materializer_source_revalidation_failed\",\n \"artifact_materializer_source_stream_identity_mismatch\",\n \"cleared_goal_live_publish_failed\",\n \"codex_failover_checkpoint_failed\",\n \"codex_fleet_shadow_failed\",\n \"codex_lease_loss_checkpoint_failed\",\n \"codex_active_credential_read_failed\",\n \"command_yield_timeout\",\n \"conflict\",\n \"control_wake_dispatch_failed\",\n \"fenced_event_live_publish_failed\",\n \"forbidden\",\n \"host_export_batch_delivery_failed\",\n \"host_export_failure_settlement_stale\",\n \"host_export_pump_iteration_failed\",\n \"host_export_retention_prune_failed\",\n \"idempotency_conflict\",\n \"incompatible_exposed_ports\",\n \"internal_error\",\n \"limit_exceeded\",\n \"mcp_close_failed\",\n \"mcp_connect_failed\",\n \"mcp_tool_call_failed\",\n \"mcp_tools_list_failed\",\n \"mcp_transport_failed\",\n \"memory_edit_embedding_failed\",\n \"memory_hybrid_vector_failed\",\n \"memory_save_embedding_failed\",\n \"nats_auth_callout_start_failed\",\n \"nested_agent_depth_exceeded\",\n \"nested_agent_depth_override_forbidden\",\n \"not_found\",\n \"oauth_operation_failed\",\n \"otlp_export_failed\",\n \"payment_required\",\n \"provider_verification_failed\",\n \"sandbox_channel_a_cancelled\",\n \"sandbox_channel_a_lifecycle_conflict\",\n \"sandbox_channel_a_operation_failed\",\n \"sandbox_channel_a_provider_busy\",\n \"sandbox_channel_a_provider_unavailable\",\n \"screenshot_capture_failed\",\n \"session_event_live_publish_failed\",\n \"session_workflow_wake_failed\",\n \"snapshot_operation_failed\",\n \"startup_dependency_retry\",\n \"tool_list_too_large\",\n \"tool_result_too_large\",\n \"codemode_operation_failed\",\n \"unauthenticated\",\n \"upstream_unavailable\",\n \"validation_failed\",\n \"worker_draining\",\n \"worker_operation_failed\",\n \"worker_shutdown_request_failed\",\n \"workspace_control_live_publish_failed\",\n]);\n\nconst PUBLIC_TELEMETRY_ERROR_ORIGINS = new Set([\n \"api\",\n \"core\",\n \"db\",\n \"events\",\n \"host-export\",\n \"oauth\",\n \"observability\",\n \"runtime\",\n \"sandbox-computer\",\n \"sandbox-resume\",\n \"codemode\",\n \"worker\",\n \"worker-lifecycle\",\n]);\n\nexport type SandboxOperationMetricObservation = {\n backend: string;\n op: string;\n outcome: \"ok\" | \"not_found\" | \"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 deployment_revision: settings.deploymentRevision ?? \"dev\",\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 const publicAttributes = projectPublicTelemetryAttributes(attributes);\n if (!this.settings.observabilityStructuredLogs) {\n if (level === \"warn\") {\n console.warn(message);\n } else if (level === \"error\") {\n console.error(message);\n } else {\n console.log(message);\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(publicAttributes),\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 telemetryError =\n input.error !== undefined && input.error !== null\n ? projectSpanErrorForTelemetry(input.error)\n : undefined;\n const errorAttributes = telemetryError ? errorToAttributes(telemetryError) : {};\n this.exportSpan({\n traceId,\n spanId,\n name,\n startMs,\n endMs: this.now(),\n attributes: {\n ...projectPublicTelemetryAttributes({\n ...attributes,\n ...input.attributes,\n ...errorAttributes,\n }),\n },\n ...(telemetryError ? { error: telemetryError } : {}),\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?: TelemetrySpanError;\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 () => {\n this.warn(\"OTLP span export failed\", {\n errorClass: \"TelemetryExportError\",\n errorCode: \"otlp_export_failed\",\n origin: \"observability\",\n });\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 observability.warn(\"Startup dependency connection failed; retrying\", {\n dependency: event.label,\n attempt: event.attempt,\n attempts: event.attempts,\n delayMs: event.delayMs,\n errorClass: \"StartupDependencyError\",\n errorCode: \"startup_dependency_retry\",\n origin: \"observability\",\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\nfunction projectPublicTelemetryAttributes(attributes: Attributes): Attributes {\n if (\"errorClass\" in attributes || \"errorCode\" in attributes) {\n return {\n ...projectPublicChannelADiagnosticAttributes(attributes),\n ...projectPublicDiagnosticAttributes(attributes),\n };\n }\n return Object.fromEntries(\n Object.entries(attributes).filter(([key, value]) => {\n if (value === undefined) return false;\n if (PUBLIC_TELEMETRY_ATTRIBUTE_KEYS.has(key)) return true;\n const pattern = PUBLIC_TELEMETRY_OPAQUE_ATTRIBUTE_PATTERNS.get(key);\n return typeof value === \"string\" && pattern?.test(value) === true;\n }),\n );\n}\n\nfunction projectPublicChannelADiagnosticAttributes(attributes: Attributes): Attributes {\n if (attributes.errorClass !== \"SandboxChannelAOperationError\") return {};\n const backend = attributes.backend;\n const op = attributes.op;\n const outcome = attributes.outcome;\n const reason = attributes.reason;\n const durationMs = attributes.durationMs;\n const sandboxLeaseKey = attributes.sandboxLeaseKey;\n return {\n ...(typeof backend === \"string\" && SANDBOX_OPERATION_BACKENDS.has(backend) ? { backend } : {}),\n ...(typeof op === \"string\" && PUBLIC_CHANNEL_A_OPERATIONS.has(op) ? { op } : {}),\n ...(outcome === \"failed\" ? { outcome } : {}),\n ...(typeof reason === \"string\" && PUBLIC_CHANNEL_A_FAILURE_REASONS.has(reason)\n ? { reason }\n : {}),\n ...(typeof durationMs === \"number\" && Number.isFinite(durationMs) && durationMs >= 0\n ? { durationMs }\n : {}),\n ...(typeof sandboxLeaseKey === \"string\" &&\n PUBLIC_TELEMETRY_OPAQUE_ATTRIBUTE_PATTERNS.get(\"sandboxLeaseKey\")?.test(sandboxLeaseKey)\n ? { sandboxLeaseKey }\n : {}),\n };\n}\n\nfunction projectPublicDiagnosticAttributes(attributes: Attributes): Attributes {\n const errorClass = attributes.errorClass;\n const errorCode = attributes.errorCode;\n const status = attributes.status;\n const origin = attributes.origin;\n return {\n errorClass:\n typeof errorClass === \"string\" && PUBLIC_TELEMETRY_ERROR_CLASSES.has(errorClass)\n ? errorClass\n : \"OperationError\",\n ...(typeof errorCode === \"string\" && PUBLIC_TELEMETRY_ERROR_CODES.has(errorCode)\n ? { errorCode }\n : {}),\n ...(typeof status === \"number\" && Number.isInteger(status) && status >= 100 && status <= 599\n ? { status }\n : {}),\n ...(typeof origin === \"string\" && PUBLIC_TELEMETRY_ERROR_ORIGINS.has(origin) ? { origin } : {}),\n };\n}\n\ntype TelemetrySpanError = {\n type: string;\n statusCode?: number;\n statusMessage: string;\n};\n\n/**\n * External OTLP projection. It intentionally exports only error class/status\n * metadata and never mutates canonical OpenGeni errors, events, or history.\n */\nfunction projectSpanErrorForTelemetry(error: unknown): TelemetrySpanError {\n const statusCode = errorStatusCode(error);\n return {\n type: \"OperationError\",\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 try {\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 } catch {\n // OTLP projection must never mask the exact internal failure.\n return undefined;\n }\n}\n\nfunction errorToAttributes(error: TelemetrySpanError): Attributes {\n return {\n \"error.type\": error.type,\n ...(error.statusCode === undefined ? {} : { \"error.status_code\": error.statusCode }),\n };\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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/observability",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@opengeni/contracts": "^0.
|
|
34
|
+
"@opengeni/contracts": "^0.44.1",
|
|
35
35
|
"prom-client": "^15.1.3"
|
|
36
36
|
}
|
|
37
37
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { collectDefaultMetrics, Counter, Gauge, Histogram, Registry } from "prom-client";
|
|
2
3
|
import { SandboxBackend } from "@opengeni/contracts";
|
|
3
4
|
|
|
@@ -28,6 +29,23 @@ export type Span = {
|
|
|
28
29
|
|
|
29
30
|
export type MetricLabels = Record<string, AttributeValue>;
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Stable, non-reversible correlation key for one logical sandbox lease. Public
|
|
34
|
+
* telemetry intentionally drops workspace/group identifiers; this key lets an
|
|
35
|
+
* operator correlate API, worker, and reaper failures without publishing
|
|
36
|
+
* either UUID. The domain separator prevents reuse as a generic identifier
|
|
37
|
+
* digest.
|
|
38
|
+
*/
|
|
39
|
+
export function sandboxLeaseTelemetryKey(workspaceId: string, sandboxGroupId: string): string {
|
|
40
|
+
return `slk_${createHash("sha256")
|
|
41
|
+
.update("opengeni:sandbox-lease-telemetry:v1\0")
|
|
42
|
+
.update(workspaceId)
|
|
43
|
+
.update("\0")
|
|
44
|
+
.update(sandboxGroupId)
|
|
45
|
+
.digest("hex")
|
|
46
|
+
.slice(0, 32)}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
31
49
|
/**
|
|
32
50
|
* Stable selectors shared by OpenGeni's runtime metrics and optional
|
|
33
51
|
* Prometheus/Grafana distribution. Operators can use these values for custom
|
|
@@ -67,10 +85,218 @@ const SANDBOX_OPERATION_NAMES = new Set([
|
|
|
67
85
|
"serializeSessionState",
|
|
68
86
|
]);
|
|
69
87
|
|
|
88
|
+
/**
|
|
89
|
+
* External logs and OTLP are public/third-party projections, not canonical
|
|
90
|
+
* OpenGeni storage. Only this reviewed closed set of operational fields may
|
|
91
|
+
* cross that boundary. Unknown keys are omitted regardless of their value, so
|
|
92
|
+
* a new diagnostic, identifier, command, response, or provider field cannot
|
|
93
|
+
* become public by accident. This is schema projection, never value inspection
|
|
94
|
+
* or rewriting.
|
|
95
|
+
*/
|
|
96
|
+
const PUBLIC_TELEMETRY_ATTRIBUTE_KEYS = new Set([
|
|
97
|
+
"http.request.method",
|
|
98
|
+
"http.response.status_code",
|
|
99
|
+
"opengeni.route",
|
|
100
|
+
"opengeni.duration_ms",
|
|
101
|
+
"opengeni.finalization_duration_ms",
|
|
102
|
+
"opengeni.trigger_kind",
|
|
103
|
+
"opengeni.status",
|
|
104
|
+
"error.type",
|
|
105
|
+
"error.status_code",
|
|
106
|
+
"method",
|
|
107
|
+
"route",
|
|
108
|
+
"status",
|
|
109
|
+
"durationMs",
|
|
110
|
+
"attempt",
|
|
111
|
+
"attempts",
|
|
112
|
+
"delayMs",
|
|
113
|
+
"provider",
|
|
114
|
+
"providerApi",
|
|
115
|
+
"model",
|
|
116
|
+
"inputTokens",
|
|
117
|
+
"outputTokens",
|
|
118
|
+
"cachedTokens",
|
|
119
|
+
"cacheWriteTokens",
|
|
120
|
+
"reasoningTokens",
|
|
121
|
+
"accountChangedFromPrevCall",
|
|
122
|
+
"rejectedFields",
|
|
123
|
+
"dependency",
|
|
124
|
+
"activity",
|
|
125
|
+
"backend",
|
|
126
|
+
"op",
|
|
127
|
+
"outcome",
|
|
128
|
+
"eventType",
|
|
129
|
+
"surface",
|
|
130
|
+
"reason",
|
|
131
|
+
"originalBytes",
|
|
132
|
+
"deliveredBytes",
|
|
133
|
+
"estimatedOriginalTokens",
|
|
134
|
+
"estimatedDeliveredTokens",
|
|
135
|
+
"fullEvidenceAvailable",
|
|
136
|
+
"retainedOutputKind",
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
/** Opaque correlation fields require both a reviewed name and a closed value
|
|
140
|
+
* grammar. Merely adding one to the ordinary allow-list would let an unrelated
|
|
141
|
+
* caller accidentally publish a raw identifier under that name. */
|
|
142
|
+
const PUBLIC_TELEMETRY_OPAQUE_ATTRIBUTE_PATTERNS = new Map<string, RegExp>([
|
|
143
|
+
["sandboxLeaseKey", /^slk_[0-9a-f]{32}$/],
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
const PUBLIC_CHANNEL_A_OPERATIONS = new Set([
|
|
147
|
+
"fs.list",
|
|
148
|
+
"fs.list-batch",
|
|
149
|
+
"fs.read",
|
|
150
|
+
"fs.write",
|
|
151
|
+
"fs.delete",
|
|
152
|
+
"fs.move",
|
|
153
|
+
"fs.mkdir",
|
|
154
|
+
"git.status",
|
|
155
|
+
"git.diff",
|
|
156
|
+
"git.read-batch",
|
|
157
|
+
"git.log",
|
|
158
|
+
"git.show",
|
|
159
|
+
"terminal.exec",
|
|
160
|
+
"terminal.pty.open",
|
|
161
|
+
"terminal.pty.write",
|
|
162
|
+
"terminal.pty.resize",
|
|
163
|
+
"terminal.pty.close",
|
|
164
|
+
"read",
|
|
165
|
+
"mutation",
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
const PUBLIC_CHANNEL_A_FAILURE_REASONS = new Set([
|
|
169
|
+
"request_cancelled",
|
|
170
|
+
"provider_read_busy",
|
|
171
|
+
"provider_unavailable",
|
|
172
|
+
"lifecycle_conflict",
|
|
173
|
+
"request_rejected",
|
|
174
|
+
"unexpected",
|
|
175
|
+
]);
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Public diagnostic values are protocol constants, never values inferred from
|
|
179
|
+
* an exception's name, constructor, code, message, or enumerable properties.
|
|
180
|
+
* Unknown classes collapse to one fixed fallback; unknown codes and origins
|
|
181
|
+
* are omitted rather than copied through based on syntax.
|
|
182
|
+
*/
|
|
183
|
+
const PUBLIC_TELEMETRY_ERROR_CLASSES = new Set([
|
|
184
|
+
"OperationError",
|
|
185
|
+
"CodexCheckpointOperationError",
|
|
186
|
+
"CodexFleetShadowOperationError",
|
|
187
|
+
"ComputerActionTimeoutError",
|
|
188
|
+
"ComputerUnavailableError",
|
|
189
|
+
"CredentialRenewalOperationError",
|
|
190
|
+
"CredentialReadOperationError",
|
|
191
|
+
"EventPublishOperationError",
|
|
192
|
+
"GitCredentialRenewalOperationError",
|
|
193
|
+
"HostExportOperationError",
|
|
194
|
+
"HttpOperationError",
|
|
195
|
+
"McpLifecycleError",
|
|
196
|
+
"McpOperationError",
|
|
197
|
+
"MemoryEmbeddingOperationError",
|
|
198
|
+
"MemorySearchOperationError",
|
|
199
|
+
"NatsAuthCalloutOperationError",
|
|
200
|
+
"OAuthOperationError",
|
|
201
|
+
"RunCredentialRenewalOperationError",
|
|
202
|
+
"RunStateCompatibilityError",
|
|
203
|
+
"SandboxChannelAOperationError",
|
|
204
|
+
"SnapshotOperationError",
|
|
205
|
+
"StartupDependencyError",
|
|
206
|
+
"TelemetryExportError",
|
|
207
|
+
"CodemodeOperationError",
|
|
208
|
+
"CodemodeTokenRenewalOperationError",
|
|
209
|
+
"WorkerLifecycleOperation",
|
|
210
|
+
"WorkerLifecycleOperationError",
|
|
211
|
+
"WorkerOperationError",
|
|
212
|
+
"WorkflowWakeOperationError",
|
|
213
|
+
]);
|
|
214
|
+
|
|
215
|
+
const PUBLIC_TELEMETRY_ERROR_CODES = new Set([
|
|
216
|
+
"agent_command_wake_failed",
|
|
217
|
+
"artifact_materializer_native_input_framing_failed",
|
|
218
|
+
"artifact_materializer_native_snapshot_open_failed",
|
|
219
|
+
"artifact_materializer_native_state_mismatch",
|
|
220
|
+
"artifact_materializer_source_content_type_mismatch",
|
|
221
|
+
"artifact_materializer_source_open_failed",
|
|
222
|
+
"artifact_materializer_source_revalidation_failed",
|
|
223
|
+
"artifact_materializer_source_stream_identity_mismatch",
|
|
224
|
+
"cleared_goal_live_publish_failed",
|
|
225
|
+
"codex_failover_checkpoint_failed",
|
|
226
|
+
"codex_fleet_shadow_failed",
|
|
227
|
+
"codex_lease_loss_checkpoint_failed",
|
|
228
|
+
"codex_active_credential_read_failed",
|
|
229
|
+
"command_yield_timeout",
|
|
230
|
+
"conflict",
|
|
231
|
+
"control_wake_dispatch_failed",
|
|
232
|
+
"fenced_event_live_publish_failed",
|
|
233
|
+
"forbidden",
|
|
234
|
+
"host_export_batch_delivery_failed",
|
|
235
|
+
"host_export_failure_settlement_stale",
|
|
236
|
+
"host_export_pump_iteration_failed",
|
|
237
|
+
"host_export_retention_prune_failed",
|
|
238
|
+
"idempotency_conflict",
|
|
239
|
+
"incompatible_exposed_ports",
|
|
240
|
+
"internal_error",
|
|
241
|
+
"limit_exceeded",
|
|
242
|
+
"mcp_close_failed",
|
|
243
|
+
"mcp_connect_failed",
|
|
244
|
+
"mcp_tool_call_failed",
|
|
245
|
+
"mcp_tools_list_failed",
|
|
246
|
+
"mcp_transport_failed",
|
|
247
|
+
"memory_edit_embedding_failed",
|
|
248
|
+
"memory_hybrid_vector_failed",
|
|
249
|
+
"memory_save_embedding_failed",
|
|
250
|
+
"nats_auth_callout_start_failed",
|
|
251
|
+
"nested_agent_depth_exceeded",
|
|
252
|
+
"nested_agent_depth_override_forbidden",
|
|
253
|
+
"not_found",
|
|
254
|
+
"oauth_operation_failed",
|
|
255
|
+
"otlp_export_failed",
|
|
256
|
+
"payment_required",
|
|
257
|
+
"provider_verification_failed",
|
|
258
|
+
"sandbox_channel_a_cancelled",
|
|
259
|
+
"sandbox_channel_a_lifecycle_conflict",
|
|
260
|
+
"sandbox_channel_a_operation_failed",
|
|
261
|
+
"sandbox_channel_a_provider_busy",
|
|
262
|
+
"sandbox_channel_a_provider_unavailable",
|
|
263
|
+
"screenshot_capture_failed",
|
|
264
|
+
"session_event_live_publish_failed",
|
|
265
|
+
"session_workflow_wake_failed",
|
|
266
|
+
"snapshot_operation_failed",
|
|
267
|
+
"startup_dependency_retry",
|
|
268
|
+
"tool_list_too_large",
|
|
269
|
+
"tool_result_too_large",
|
|
270
|
+
"codemode_operation_failed",
|
|
271
|
+
"unauthenticated",
|
|
272
|
+
"upstream_unavailable",
|
|
273
|
+
"validation_failed",
|
|
274
|
+
"worker_draining",
|
|
275
|
+
"worker_operation_failed",
|
|
276
|
+
"worker_shutdown_request_failed",
|
|
277
|
+
"workspace_control_live_publish_failed",
|
|
278
|
+
]);
|
|
279
|
+
|
|
280
|
+
const PUBLIC_TELEMETRY_ERROR_ORIGINS = new Set([
|
|
281
|
+
"api",
|
|
282
|
+
"core",
|
|
283
|
+
"db",
|
|
284
|
+
"events",
|
|
285
|
+
"host-export",
|
|
286
|
+
"oauth",
|
|
287
|
+
"observability",
|
|
288
|
+
"runtime",
|
|
289
|
+
"sandbox-computer",
|
|
290
|
+
"sandbox-resume",
|
|
291
|
+
"codemode",
|
|
292
|
+
"worker",
|
|
293
|
+
"worker-lifecycle",
|
|
294
|
+
]);
|
|
295
|
+
|
|
70
296
|
export type SandboxOperationMetricObservation = {
|
|
71
297
|
backend: string;
|
|
72
298
|
op: string;
|
|
73
|
-
outcome: "ok" | "failed";
|
|
299
|
+
outcome: "ok" | "not_found" | "failed";
|
|
74
300
|
durationMs: number;
|
|
75
301
|
};
|
|
76
302
|
|
|
@@ -115,6 +341,7 @@ export class Observability {
|
|
|
115
341
|
service: settings.serviceName,
|
|
116
342
|
environment: settings.environment,
|
|
117
343
|
component: options.component,
|
|
344
|
+
deployment_revision: settings.deploymentRevision ?? "dev",
|
|
118
345
|
});
|
|
119
346
|
if (settings.observabilityMetricsEnabled) {
|
|
120
347
|
collectDefaultMetrics({ register: this.registry, prefix: "opengeni_" });
|
|
@@ -151,14 +378,14 @@ export class Observability {
|
|
|
151
378
|
message: string,
|
|
152
379
|
attributes: Attributes = {},
|
|
153
380
|
): void {
|
|
381
|
+
const publicAttributes = projectPublicTelemetryAttributes(attributes);
|
|
154
382
|
if (!this.settings.observabilityStructuredLogs) {
|
|
155
|
-
const line = attributes.error ? `${message}: ${String(attributes.error)}` : message;
|
|
156
383
|
if (level === "warn") {
|
|
157
|
-
console.warn(
|
|
384
|
+
console.warn(message);
|
|
158
385
|
} else if (level === "error") {
|
|
159
|
-
console.error(
|
|
386
|
+
console.error(message);
|
|
160
387
|
} else {
|
|
161
|
-
console.log(
|
|
388
|
+
console.log(message);
|
|
162
389
|
}
|
|
163
390
|
return;
|
|
164
391
|
}
|
|
@@ -169,7 +396,7 @@ export class Observability {
|
|
|
169
396
|
service: this.settings.serviceName,
|
|
170
397
|
environment: this.settings.environment,
|
|
171
398
|
component: this.options.component,
|
|
172
|
-
...cleanAttributes(
|
|
399
|
+
...cleanAttributes(publicAttributes),
|
|
173
400
|
};
|
|
174
401
|
const serialized = JSON.stringify(record);
|
|
175
402
|
if (level === "warn") {
|
|
@@ -194,11 +421,11 @@ export class Observability {
|
|
|
194
421
|
return;
|
|
195
422
|
}
|
|
196
423
|
ended = true;
|
|
197
|
-
const
|
|
424
|
+
const telemetryError =
|
|
198
425
|
input.error !== undefined && input.error !== null
|
|
199
|
-
?
|
|
426
|
+
? projectSpanErrorForTelemetry(input.error)
|
|
200
427
|
: undefined;
|
|
201
|
-
const errorAttributes =
|
|
428
|
+
const errorAttributes = telemetryError ? errorToAttributes(telemetryError) : {};
|
|
202
429
|
this.exportSpan({
|
|
203
430
|
traceId,
|
|
204
431
|
spanId,
|
|
@@ -206,11 +433,13 @@ export class Observability {
|
|
|
206
433
|
startMs,
|
|
207
434
|
endMs: this.now(),
|
|
208
435
|
attributes: {
|
|
209
|
-
...
|
|
210
|
-
|
|
211
|
-
|
|
436
|
+
...projectPublicTelemetryAttributes({
|
|
437
|
+
...attributes,
|
|
438
|
+
...input.attributes,
|
|
439
|
+
...errorAttributes,
|
|
440
|
+
}),
|
|
212
441
|
},
|
|
213
|
-
...(
|
|
442
|
+
...(telemetryError ? { error: telemetryError } : {}),
|
|
214
443
|
});
|
|
215
444
|
},
|
|
216
445
|
};
|
|
@@ -408,7 +637,7 @@ export class Observability {
|
|
|
408
637
|
startMs: number;
|
|
409
638
|
endMs: number;
|
|
410
639
|
attributes: Attributes;
|
|
411
|
-
error?:
|
|
640
|
+
error?: TelemetrySpanError;
|
|
412
641
|
}): void {
|
|
413
642
|
if (!this.settings.observabilityOtlpEndpoint) {
|
|
414
643
|
return;
|
|
@@ -444,8 +673,12 @@ export class Observability {
|
|
|
444
673
|
],
|
|
445
674
|
};
|
|
446
675
|
void this.exporter(endpoint, body, parseHeaders(this.settings.observabilityOtlpHeaders)).catch(
|
|
447
|
-
(
|
|
448
|
-
this.warn("OTLP span export failed", {
|
|
676
|
+
() => {
|
|
677
|
+
this.warn("OTLP span export failed", {
|
|
678
|
+
errorClass: "TelemetryExportError",
|
|
679
|
+
errorCode: "otlp_export_failed",
|
|
680
|
+
origin: "observability",
|
|
681
|
+
});
|
|
449
682
|
},
|
|
450
683
|
);
|
|
451
684
|
}
|
|
@@ -509,13 +742,14 @@ export function logStartupDependencyRetry(
|
|
|
509
742
|
observability: Observability,
|
|
510
743
|
event: StartupDependencyRetryEvent,
|
|
511
744
|
): void {
|
|
512
|
-
const message = event.error instanceof Error ? event.error.message : String(event.error);
|
|
513
745
|
observability.warn("Startup dependency connection failed; retrying", {
|
|
514
746
|
dependency: event.label,
|
|
515
747
|
attempt: event.attempt,
|
|
516
748
|
attempts: event.attempts,
|
|
517
749
|
delayMs: event.delayMs,
|
|
518
|
-
|
|
750
|
+
errorClass: "StartupDependencyError",
|
|
751
|
+
errorCode: "startup_dependency_retry",
|
|
752
|
+
origin: "observability",
|
|
519
753
|
});
|
|
520
754
|
}
|
|
521
755
|
|
|
@@ -538,20 +772,82 @@ function cleanAttributes(attributes: Attributes): Record<string, string | number
|
|
|
538
772
|
) as Record<string, string | number | boolean | null>;
|
|
539
773
|
}
|
|
540
774
|
|
|
541
|
-
|
|
775
|
+
function projectPublicTelemetryAttributes(attributes: Attributes): Attributes {
|
|
776
|
+
if ("errorClass" in attributes || "errorCode" in attributes) {
|
|
777
|
+
return {
|
|
778
|
+
...projectPublicChannelADiagnosticAttributes(attributes),
|
|
779
|
+
...projectPublicDiagnosticAttributes(attributes),
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
return Object.fromEntries(
|
|
783
|
+
Object.entries(attributes).filter(([key, value]) => {
|
|
784
|
+
if (value === undefined) return false;
|
|
785
|
+
if (PUBLIC_TELEMETRY_ATTRIBUTE_KEYS.has(key)) return true;
|
|
786
|
+
const pattern = PUBLIC_TELEMETRY_OPAQUE_ATTRIBUTE_PATTERNS.get(key);
|
|
787
|
+
return typeof value === "string" && pattern?.test(value) === true;
|
|
788
|
+
}),
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function projectPublicChannelADiagnosticAttributes(attributes: Attributes): Attributes {
|
|
793
|
+
if (attributes.errorClass !== "SandboxChannelAOperationError") return {};
|
|
794
|
+
const backend = attributes.backend;
|
|
795
|
+
const op = attributes.op;
|
|
796
|
+
const outcome = attributes.outcome;
|
|
797
|
+
const reason = attributes.reason;
|
|
798
|
+
const durationMs = attributes.durationMs;
|
|
799
|
+
const sandboxLeaseKey = attributes.sandboxLeaseKey;
|
|
800
|
+
return {
|
|
801
|
+
...(typeof backend === "string" && SANDBOX_OPERATION_BACKENDS.has(backend) ? { backend } : {}),
|
|
802
|
+
...(typeof op === "string" && PUBLIC_CHANNEL_A_OPERATIONS.has(op) ? { op } : {}),
|
|
803
|
+
...(outcome === "failed" ? { outcome } : {}),
|
|
804
|
+
...(typeof reason === "string" && PUBLIC_CHANNEL_A_FAILURE_REASONS.has(reason)
|
|
805
|
+
? { reason }
|
|
806
|
+
: {}),
|
|
807
|
+
...(typeof durationMs === "number" && Number.isFinite(durationMs) && durationMs >= 0
|
|
808
|
+
? { durationMs }
|
|
809
|
+
: {}),
|
|
810
|
+
...(typeof sandboxLeaseKey === "string" &&
|
|
811
|
+
PUBLIC_TELEMETRY_OPAQUE_ATTRIBUTE_PATTERNS.get("sandboxLeaseKey")?.test(sandboxLeaseKey)
|
|
812
|
+
? { sandboxLeaseKey }
|
|
813
|
+
: {}),
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
function projectPublicDiagnosticAttributes(attributes: Attributes): Attributes {
|
|
818
|
+
const errorClass = attributes.errorClass;
|
|
819
|
+
const errorCode = attributes.errorCode;
|
|
820
|
+
const status = attributes.status;
|
|
821
|
+
const origin = attributes.origin;
|
|
822
|
+
return {
|
|
823
|
+
errorClass:
|
|
824
|
+
typeof errorClass === "string" && PUBLIC_TELEMETRY_ERROR_CLASSES.has(errorClass)
|
|
825
|
+
? errorClass
|
|
826
|
+
: "OperationError",
|
|
827
|
+
...(typeof errorCode === "string" && PUBLIC_TELEMETRY_ERROR_CODES.has(errorCode)
|
|
828
|
+
? { errorCode }
|
|
829
|
+
: {}),
|
|
830
|
+
...(typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599
|
|
831
|
+
? { status }
|
|
832
|
+
: {}),
|
|
833
|
+
...(typeof origin === "string" && PUBLIC_TELEMETRY_ERROR_ORIGINS.has(origin) ? { origin } : {}),
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
type TelemetrySpanError = {
|
|
542
838
|
type: string;
|
|
543
839
|
statusCode?: number;
|
|
544
840
|
statusMessage: string;
|
|
545
841
|
};
|
|
546
842
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
843
|
+
/**
|
|
844
|
+
* External OTLP projection. It intentionally exports only error class/status
|
|
845
|
+
* metadata and never mutates canonical OpenGeni errors, events, or history.
|
|
846
|
+
*/
|
|
847
|
+
function projectSpanErrorForTelemetry(error: unknown): TelemetrySpanError {
|
|
552
848
|
const statusCode = errorStatusCode(error);
|
|
553
849
|
return {
|
|
554
|
-
type,
|
|
850
|
+
type: "OperationError",
|
|
555
851
|
...(statusCode === undefined ? {} : { statusCode }),
|
|
556
852
|
statusMessage: statusCode === undefined ? "operation failed" : `HTTP ${statusCode}`,
|
|
557
853
|
};
|
|
@@ -559,30 +855,31 @@ function sanitizeSpanError(error: unknown): SanitizedSpanError {
|
|
|
559
855
|
|
|
560
856
|
function errorStatusCode(error: unknown): number | undefined {
|
|
561
857
|
if (typeof error !== "object" || error === null) return undefined;
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
858
|
+
try {
|
|
859
|
+
const value = (error as { status?: unknown; statusCode?: unknown }).status;
|
|
860
|
+
const statusCode =
|
|
861
|
+
Number.isInteger(value) && typeof value === "number"
|
|
862
|
+
? value
|
|
863
|
+
: (error as { statusCode?: unknown }).statusCode;
|
|
864
|
+
return Number.isInteger(statusCode) &&
|
|
865
|
+
typeof statusCode === "number" &&
|
|
866
|
+
statusCode >= 100 &&
|
|
867
|
+
statusCode <= 599
|
|
868
|
+
? statusCode
|
|
869
|
+
: undefined;
|
|
870
|
+
} catch {
|
|
871
|
+
// OTLP projection must never mask the exact internal failure.
|
|
872
|
+
return undefined;
|
|
873
|
+
}
|
|
573
874
|
}
|
|
574
875
|
|
|
575
|
-
function errorToAttributes(error:
|
|
876
|
+
function errorToAttributes(error: TelemetrySpanError): Attributes {
|
|
576
877
|
return {
|
|
577
878
|
"error.type": error.type,
|
|
578
879
|
...(error.statusCode === undefined ? {} : { "error.status_code": error.statusCode }),
|
|
579
880
|
};
|
|
580
881
|
}
|
|
581
882
|
|
|
582
|
-
function errorMessage(error: unknown): string {
|
|
583
|
-
return error instanceof Error ? error.message : String(error);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
883
|
function otlpAttributes(
|
|
587
884
|
attributes: Attributes,
|
|
588
885
|
): Array<{ key: string; value: Record<string, string | number | boolean> }> {
|