@cat-factory/observability-otel 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +208 -0
- package/dist/index.js.map +1 -0
- package/dist/mapping.d.ts +133 -0
- package/dist/mapping.d.ts.map +1 -0
- package/dist/mapping.js +304 -0
- package/dist/mapping.js.map +1 -0
- package/dist/node.d.ts +39 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +131 -0
- package/dist/node.js.map +1 -0
- package/dist/otlp.d.ts +39 -0
- package/dist/otlp.d.ts.map +1 -0
- package/dist/otlp.js +46 -0
- package/dist/otlp.js.map +1 -0
- package/dist/platform.d.ts +37 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +73 -0
- package/dist/platform.js.map +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Igor Savin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# @cat-factory/observability-otel
|
|
2
|
+
|
|
3
|
+
Opt-in [OpenTelemetry](https://opentelemetry.io) (OTLP) trace + metrics publisher for the
|
|
4
|
+
Agent Architecture Board.
|
|
5
|
+
|
|
6
|
+
It implements the runtime-neutral `LlmTraceSink` port from `@cat-factory/kernel`, so when
|
|
7
|
+
wired into a facade every LLM call — container-agent calls (through the LLM proxy) **and**
|
|
8
|
+
inline calls (requirements review, document planner, fragment selector, inline agent) —
|
|
9
|
+
is exported to any **OTLP/HTTP** backend (Grafana Tempo/Mimir, Honeycomb, Datadog OTLP,
|
|
10
|
+
Jaeger, an OpenTelemetry Collector, …) as:
|
|
11
|
+
|
|
12
|
+
- **a trace span per generation**, plus a span per container tool call, all grouped under a
|
|
13
|
+
shared per-run trace id (they are sibling spans sharing the trace, not parent/child —
|
|
14
|
+
generations and tool calls arrive as independent, stateless emissions); and
|
|
15
|
+
- **metrics** — a `gen_ai.client.token.usage` counter (input/output) and a
|
|
16
|
+
`gen_ai.client.operation.duration` histogram — following the OpenTelemetry GenAI
|
|
17
|
+
semantic conventions.
|
|
18
|
+
|
|
19
|
+
## Two transports, one behaviour
|
|
20
|
+
|
|
21
|
+
The Cloudflare Worker runtime (workerd) cannot run the official `@opentelemetry/*` SDK
|
|
22
|
+
(it relies on Node-only APIs), so this package ships **two exporters** behind the same
|
|
23
|
+
port:
|
|
24
|
+
|
|
25
|
+
| Entry | Export | Transport | Used by |
|
|
26
|
+
| -------------------------------------- | -------------------- | ------------------------------------------- | ----------------- |
|
|
27
|
+
| `@cat-factory/observability-otel` | `createOtelSink` | hand-rolled **OTLP/HTTP JSON over `fetch`** | Cloudflare Worker |
|
|
28
|
+
| `@cat-factory/observability-otel/node` | `createNodeOtelSink` | the official **`@opentelemetry/*` SDK** | Node / local |
|
|
29
|
+
|
|
30
|
+
Both map events through the **same** `src/mapping.ts` layer, so they emit identical span
|
|
31
|
+
names, attributes, trace-id grouping and metric names/units. `src/conformity.test.ts`
|
|
32
|
+
feeds the same events through both and asserts the emitted telemetry matches — the guard
|
|
33
|
+
that the transports never drift.
|
|
34
|
+
|
|
35
|
+
The Worker entry (`.`) never imports `@opentelemetry/*`, so the SDK is kept out of the
|
|
36
|
+
workerd bundle; it depends only on the `fetch`/`crypto` globals.
|
|
37
|
+
|
|
38
|
+
## Behaviour
|
|
39
|
+
|
|
40
|
+
- Never throws into the caller: every method swallows its own errors (logging at most a
|
|
41
|
+
warning). Observability must never break agent work.
|
|
42
|
+
- Honours the same `LLM_RECORD_PROMPTS` privacy switch as the local metric store: when
|
|
43
|
+
prompt recording is off, spans carry usage/timing/attributes but no prompt or response
|
|
44
|
+
bodies.
|
|
45
|
+
- Composes **alongside** the Langfuse sink (`@cat-factory/observability-langfuse`) via the
|
|
46
|
+
kernel `composeTraceSinks` fan-out — a deployment can export to both at once.
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// Cloudflare Worker (workerd-safe fetch exporter)
|
|
52
|
+
import { createOtelSink } from '@cat-factory/observability-otel'
|
|
53
|
+
|
|
54
|
+
const sink = createOtelSink({
|
|
55
|
+
endpoint: env.OTEL_EXPORTER_OTLP_ENDPOINT, // e.g. http://collector:4318
|
|
56
|
+
headers: { 'x-api-key': env.OTEL_KEY }, // optional
|
|
57
|
+
serviceName: 'cat-factory', // optional; defaults to 'cat-factory'
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
// Node / local (official @opentelemetry/* SDK exporter)
|
|
63
|
+
import { createNodeOtelSink } from '@cat-factory/observability-otel/node'
|
|
64
|
+
|
|
65
|
+
const sink = createNodeOtelSink({ endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT! })
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Wired into a facade via its container's `buildTraceSink(config)`; absent config (no
|
|
69
|
+
`OTEL_ENABLED=true` + endpoint) ⇒ the sink is never built and there is no external
|
|
70
|
+
emission or behaviour change.
|
|
71
|
+
|
|
72
|
+
## Platform-operator metrics (deployment health)
|
|
73
|
+
|
|
74
|
+
The per-call sink above answers "what did THIS run do". The **`PlatformMetricsOtelExporter`**
|
|
75
|
+
(`createPlatformMetricsOtelExporter`, the `.` entry) answers "how is the WHOLE deployment
|
|
76
|
+
doing": a periodic sweep (Worker `scheduled` cron ⇄ Node interval, runtime-symmetric) computes
|
|
77
|
+
the platform-observability projection per account and this exporter pushes it to the same
|
|
78
|
+
OTLP endpoint as OpenTelemetry **gauge** metrics — so an operator watches deployment health in
|
|
79
|
+
their own metrics backend, the dual of the `post-release-health` gate that watches the
|
|
80
|
+
_user's_ release.
|
|
81
|
+
|
|
82
|
+
Metrics (`cat_factory.platform.*`, all gauges — the OTel backend trends the series over time):
|
|
83
|
+
|
|
84
|
+
| Metric | Unit | Split dimension |
|
|
85
|
+
| --------------------------------------- | ------- | --------------------------- |
|
|
86
|
+
| `cat_factory.platform.runs` | `{run}` | `cat_factory.run_status` |
|
|
87
|
+
| `cat_factory.platform.run_success_rate` | `1` | — |
|
|
88
|
+
| `cat_factory.platform.run_failures` | `{run}` | `cat_factory.failure_kind` |
|
|
89
|
+
| `cat_factory.platform.live_runs` | `{run}` | `cat_factory.run_state` |
|
|
90
|
+
| `cat_factory.platform.run_duration` | `s` | `cat_factory.duration_stat` |
|
|
91
|
+
|
|
92
|
+
Every point carries `cat_factory.account_id` (the bounded tenant scope — safe on a metric,
|
|
93
|
+
unlike the unbounded workspace id excluded from the per-call metrics); the windowed gauges
|
|
94
|
+
also carry `cat_factory.window`. Null aggregates (a success rate / percentiles with no
|
|
95
|
+
terminal runs) are omitted rather than emitted as a misleading zero.
|
|
96
|
+
|
|
97
|
+
Unlike the per-call LLM path, the platform exporter is the **fetch transport on both
|
|
98
|
+
runtimes** (there is no SDK counterpart): the push is a stateless, low-frequency snapshot
|
|
99
|
+
POST with no need for the SDK's async instruments / periodic reader, so one workerd-safe
|
|
100
|
+
exporter serves both facades and is tested once.
|
|
101
|
+
|
|
102
|
+
**Opt-in on top of the base exporter** (it adds recurring DB rollup load): off unless
|
|
103
|
+
`OTEL_ENABLED=true` + an endpoint AND `OTEL_PLATFORM_METRICS=true`. `OTEL_PLATFORM_METRICS_WINDOW`
|
|
104
|
+
(`1h`/`24h`/`7d`, default `1h`) sets the trailing window; on Node
|
|
105
|
+
`OTEL_PLATFORM_METRICS_INTERVAL_MS` (default 60s) sets the sweep cadence (the Worker is
|
|
106
|
+
cron-driven). The runtime-neutral `sweepPlatformMetrics` driver + `distinctAccountIds`
|
|
107
|
+
account enumeration live in `@cat-factory/orchestration`.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { LlmGenerationEvent, LlmToolSpan, LlmToolSpanContext, LlmTraceSink } from '@cat-factory/kernel';
|
|
2
|
+
/** Minimal structured logger (pino-compatible); optional. */
|
|
3
|
+
export interface OtelLogger {
|
|
4
|
+
warn(obj: Record<string, unknown>, msg?: string): void;
|
|
5
|
+
}
|
|
6
|
+
export interface OtelSinkConfig {
|
|
7
|
+
/** OTLP/HTTP base URL, e.g. `http://collector:4318` (the `/v1/*` paths are appended). */
|
|
8
|
+
endpoint: string;
|
|
9
|
+
/** Extra headers merged onto every request (auth tokens, tenant ids, …). */
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
/** OTLP resource `service.name`; defaults to `cat-factory`. */
|
|
12
|
+
serviceName?: string;
|
|
13
|
+
/** Optional logger for swallowed errors. */
|
|
14
|
+
logger?: OtelLogger;
|
|
15
|
+
/** Injectable fetch (tests); defaults to the global `fetch`. */
|
|
16
|
+
fetchImpl?: typeof fetch;
|
|
17
|
+
}
|
|
18
|
+
export declare class OtelTraceSink implements LlmTraceSink {
|
|
19
|
+
private readonly tracesEndpoint;
|
|
20
|
+
private readonly metricsEndpoint;
|
|
21
|
+
private readonly headers;
|
|
22
|
+
private readonly serviceName;
|
|
23
|
+
private readonly logger?;
|
|
24
|
+
private readonly fetchImpl;
|
|
25
|
+
constructor(config: OtelSinkConfig);
|
|
26
|
+
private resourceAttributes;
|
|
27
|
+
recordGeneration(event: LlmGenerationEvent): Promise<void>;
|
|
28
|
+
recordToolSpans(context: LlmToolSpanContext, spans: LlmToolSpan[]): Promise<void>;
|
|
29
|
+
private sendSpans;
|
|
30
|
+
private sendMetrics;
|
|
31
|
+
private send;
|
|
32
|
+
}
|
|
33
|
+
/** Build a fetch-based {@link OtelTraceSink}. The workerd-safe opt-in OTLP exporter. */
|
|
34
|
+
export declare function createOtelSink(config: OtelSinkConfig): OtelTraceSink;
|
|
35
|
+
export { PlatformMetricsOtelExporter, type PlatformMetricsOtelExporterConfig, createPlatformMetricsOtelExporter, } from './platform.js';
|
|
36
|
+
/**
|
|
37
|
+
* Parse the OTLP `OTEL_EXPORTER_OTLP_HEADERS` convention — comma-separated `key=value`
|
|
38
|
+
* pairs (e.g. `x-api-key=abc,x-tenant=42`) — into a header map, or undefined when
|
|
39
|
+
* unset/empty. Shared by every facade so the two transports read headers identically.
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseOtlpHeaders(raw: string | undefined): Record<string, string> | undefined;
|
|
42
|
+
/** Default platform-metrics sweep interval (Node timer); the Worker is cron-driven. */
|
|
43
|
+
export declare const PLATFORM_METRICS_DEFAULT_INTERVAL_MS = 60000;
|
|
44
|
+
/** The trailing windows the platform-metrics sweep may aggregate over. */
|
|
45
|
+
export declare const PLATFORM_METRICS_WINDOWS: readonly ['1h', '24h', '7d'];
|
|
46
|
+
export type PlatformMetricsWindow = (typeof PLATFORM_METRICS_WINDOWS)[number];
|
|
47
|
+
/**
|
|
48
|
+
* Parse `OTEL_PLATFORM_METRICS_INTERVAL_MS` into a positive integer ms, falling back to
|
|
49
|
+
* {@link PLATFORM_METRICS_DEFAULT_INTERVAL_MS} for unset / non-numeric / non-positive values.
|
|
50
|
+
* Shared by every facade so the sweep cadence is parsed identically.
|
|
51
|
+
*/
|
|
52
|
+
export declare function parsePlatformMetricsIntervalMs(raw: string | undefined): number;
|
|
53
|
+
/**
|
|
54
|
+
* Parse `OTEL_PLATFORM_METRICS_WINDOW` into a valid trailing window, defaulting to `1h`
|
|
55
|
+
* (the shortest, most operationally useful — the OTel backend builds longer trends from the
|
|
56
|
+
* gauge series). Shared so both facades default + validate identically.
|
|
57
|
+
*/
|
|
58
|
+
export declare function parsePlatformMetricsWindow(raw: string | undefined): PlatformMetricsWindow;
|
|
59
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,WAAW,EACX,kBAAkB,EAClB,YAAY,EACb,MAAM,qBAAqB,CAAA;AAyC5B,6DAA6D;AAC7D,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvD;AAED,MAAM,WAAW,cAAc;IAC7B,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,gEAAgE;IAChE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB;AAwBD,qBAAa,aAAc,YAAW,YAAY;IAChD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IACvC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAY;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IAExC,YAAY,MAAM,EAAE,cAAc,EAQjC;IAED,OAAO,CAAC,kBAAkB;IAIpB,gBAAgB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAK/D;IAEK,eAAe,CAAC,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAOtF;YAEa,SAAS;YAiBT,WAAW;YAuDX,IAAI;CASnB;AAED,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa,CAEpE;AAKD,OAAO,EACL,2BAA2B,EAC3B,KAAK,iCAAiC,EACtC,iCAAiC,GAClC,MAAM,eAAe,CAAA;AAEtB;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAY5F;AAID,uFAAuF;AACvF,eAAO,MAAM,oCAAoC,QAAS,CAAA;AAE1D,0EAA0E;AAC1E,eAAO,MAAM,wBAAwB,YAAI,IAAI,EAAE,KAAK,EAAE,IAAI,CAAU,CAAA;AACpE,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE7E;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAG9E;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,qBAAqB,CAGzF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { ATTR, DEFAULT_SERVICE_NAME, DURATION_UNIT, METRIC, SCOPE_NAME, TOKEN_UNIT, mapGeneration, mapGenerationMetrics, mapToolSpan, randomSpanId, toUnixNano, } from './mapping.js';
|
|
2
|
+
import { keyValues, postOtlp } from './otlp.js';
|
|
3
|
+
// A fetch-based OpenTelemetry exporter that speaks OTLP/HTTP with the **JSON** encoding
|
|
4
|
+
// (`POST {endpoint}/v1/traces` and `/v1/metrics`) using only the global `fetch`/`crypto`.
|
|
5
|
+
// It deliberately does NOT depend on `@opentelemetry/*` — the SDK relies on Node-only APIs
|
|
6
|
+
// unavailable on the Cloudflare Worker runtime (workerd), exactly as the Langfuse sink
|
|
7
|
+
// avoids them. This keeps the exporter byte-for-byte identical on the Worker and, if ever
|
|
8
|
+
// desired, Node; the Node facade instead uses the official-SDK exporter in `./node`, kept
|
|
9
|
+
// conformant with this one by the shared `./mapping` layer + `conformity.test.ts`.
|
|
10
|
+
//
|
|
11
|
+
// Contract (identical to the Langfuse sink): observability must never break the product,
|
|
12
|
+
// so every method swallows its own errors (logging at most a warning) and each POST is
|
|
13
|
+
// bounded by a timeout — a dropped batch is the documented best-effort worst case. One
|
|
14
|
+
// POST per call keeps the exporter stateless (no cross-request buffer to flush), which is
|
|
15
|
+
// the only shape that survives the Worker's per-request isolate lifecycle. Metrics are
|
|
16
|
+
// emitted with DELTA temporality so a single call is a valid, self-contained data point.
|
|
17
|
+
/** OTLP `AggregationTemporality.DELTA` (proto enum value). */
|
|
18
|
+
const TEMPORALITY_DELTA = 1;
|
|
19
|
+
/** OTLP span kind CLIENT / INTERNAL (proto enum values). */
|
|
20
|
+
const SPAN_KIND_CLIENT = 3;
|
|
21
|
+
const SPAN_KIND_INTERNAL = 1;
|
|
22
|
+
/** OTLP status codes: UNSET / ERROR. */
|
|
23
|
+
const STATUS_UNSET = 0;
|
|
24
|
+
const STATUS_ERROR = 2;
|
|
25
|
+
// ---- OTLP/JSON encoding helpers -------------------------------------------
|
|
26
|
+
function encodeSpan(span, kind) {
|
|
27
|
+
return {
|
|
28
|
+
traceId: span.traceId,
|
|
29
|
+
spanId: randomSpanId(),
|
|
30
|
+
name: span.name,
|
|
31
|
+
kind,
|
|
32
|
+
startTimeUnixNano: toUnixNano(span.startTimeMs),
|
|
33
|
+
endTimeUnixNano: toUnixNano(span.endTimeMs),
|
|
34
|
+
attributes: keyValues(span.attributes),
|
|
35
|
+
events: span.events.map((e) => ({
|
|
36
|
+
name: e.name,
|
|
37
|
+
timeUnixNano: toUnixNano(e.timeMs),
|
|
38
|
+
attributes: keyValues(e.attributes),
|
|
39
|
+
})),
|
|
40
|
+
status: span.ok
|
|
41
|
+
? { code: STATUS_UNSET }
|
|
42
|
+
: { code: STATUS_ERROR, ...(span.statusMessage ? { message: span.statusMessage } : {}) },
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export class OtelTraceSink {
|
|
46
|
+
tracesEndpoint;
|
|
47
|
+
metricsEndpoint;
|
|
48
|
+
headers;
|
|
49
|
+
serviceName;
|
|
50
|
+
logger;
|
|
51
|
+
fetchImpl;
|
|
52
|
+
constructor(config) {
|
|
53
|
+
const base = config.endpoint.replace(/\/+$/, '');
|
|
54
|
+
this.tracesEndpoint = `${base}/v1/traces`;
|
|
55
|
+
this.metricsEndpoint = `${base}/v1/metrics`;
|
|
56
|
+
this.headers = { 'content-type': 'application/json', ...config.headers };
|
|
57
|
+
this.serviceName = config.serviceName || DEFAULT_SERVICE_NAME;
|
|
58
|
+
this.logger = config.logger;
|
|
59
|
+
this.fetchImpl = config.fetchImpl ?? fetch;
|
|
60
|
+
}
|
|
61
|
+
resourceAttributes() {
|
|
62
|
+
return [{ key: ATTR.serviceName, value: { stringValue: this.serviceName } }];
|
|
63
|
+
}
|
|
64
|
+
async recordGeneration(event) {
|
|
65
|
+
const span = mapGeneration(event);
|
|
66
|
+
const metrics = mapGenerationMetrics(event);
|
|
67
|
+
// Traces and metrics go to distinct OTLP endpoints (two POSTs), each best-effort.
|
|
68
|
+
await Promise.all([this.sendSpans([span], SPAN_KIND_CLIENT), this.sendMetrics(metrics)]);
|
|
69
|
+
}
|
|
70
|
+
async recordToolSpans(context, spans) {
|
|
71
|
+
// Tool spans are only meaningful as children of a run's trace.
|
|
72
|
+
if (!context.executionId || spans.length === 0)
|
|
73
|
+
return;
|
|
74
|
+
await this.sendSpans(spans.map((span) => mapToolSpan(context, span)), SPAN_KIND_INTERNAL);
|
|
75
|
+
}
|
|
76
|
+
async sendSpans(spans, kind) {
|
|
77
|
+
const payload = {
|
|
78
|
+
resourceSpans: [
|
|
79
|
+
{
|
|
80
|
+
resource: { attributes: this.resourceAttributes() },
|
|
81
|
+
scopeSpans: [
|
|
82
|
+
{
|
|
83
|
+
scope: { name: SCOPE_NAME },
|
|
84
|
+
spans: spans.map((span) => encodeSpan(span, kind)),
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
await this.send(this.tracesEndpoint, payload);
|
|
91
|
+
}
|
|
92
|
+
async sendMetrics(metrics) {
|
|
93
|
+
const startNano = toUnixNano(metrics.startTimeMs);
|
|
94
|
+
const timeNano = toUnixNano(metrics.endTimeMs);
|
|
95
|
+
const payload = {
|
|
96
|
+
resourceMetrics: [
|
|
97
|
+
{
|
|
98
|
+
resource: { attributes: this.resourceAttributes() },
|
|
99
|
+
scopeMetrics: [
|
|
100
|
+
{
|
|
101
|
+
scope: { name: SCOPE_NAME },
|
|
102
|
+
metrics: [
|
|
103
|
+
{
|
|
104
|
+
name: METRIC.tokenUsage,
|
|
105
|
+
unit: TOKEN_UNIT,
|
|
106
|
+
sum: {
|
|
107
|
+
aggregationTemporality: TEMPORALITY_DELTA,
|
|
108
|
+
isMonotonic: true,
|
|
109
|
+
dataPoints: metrics.tokenUsage.map((point) => ({
|
|
110
|
+
attributes: keyValues(point.attributes),
|
|
111
|
+
startTimeUnixNano: startNano,
|
|
112
|
+
timeUnixNano: timeNano,
|
|
113
|
+
asInt: String(point.value),
|
|
114
|
+
})),
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: METRIC.duration,
|
|
119
|
+
unit: DURATION_UNIT,
|
|
120
|
+
histogram: {
|
|
121
|
+
aggregationTemporality: TEMPORALITY_DELTA,
|
|
122
|
+
dataPoints: [
|
|
123
|
+
{
|
|
124
|
+
attributes: keyValues(metrics.durationAttributes),
|
|
125
|
+
startTimeUnixNano: startNano,
|
|
126
|
+
timeUnixNano: timeNano,
|
|
127
|
+
count: '1',
|
|
128
|
+
sum: metrics.durationSeconds,
|
|
129
|
+
// A single observation: one implicit bucket, no explicit bounds.
|
|
130
|
+
bucketCounts: ['1'],
|
|
131
|
+
explicitBounds: [],
|
|
132
|
+
min: metrics.durationSeconds,
|
|
133
|
+
max: metrics.durationSeconds,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
await this.send(this.metricsEndpoint, payload);
|
|
145
|
+
}
|
|
146
|
+
async send(endpoint, payload) {
|
|
147
|
+
await postOtlp({
|
|
148
|
+
fetchImpl: this.fetchImpl,
|
|
149
|
+
endpoint,
|
|
150
|
+
headers: this.headers,
|
|
151
|
+
payload,
|
|
152
|
+
logger: this.logger,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** Build a fetch-based {@link OtelTraceSink}. The workerd-safe opt-in OTLP exporter. */
|
|
157
|
+
export function createOtelSink(config) {
|
|
158
|
+
return new OtelTraceSink(config);
|
|
159
|
+
}
|
|
160
|
+
// The deployment-level (platform-operator) metrics exporter — the dual of the per-run LLM
|
|
161
|
+
// sink above. Fetch-based on both runtimes (see `./platform`), so it lives in this
|
|
162
|
+
// workerd-safe entry rather than the SDK `./node` one.
|
|
163
|
+
export { PlatformMetricsOtelExporter, createPlatformMetricsOtelExporter, } from './platform.js';
|
|
164
|
+
/**
|
|
165
|
+
* Parse the OTLP `OTEL_EXPORTER_OTLP_HEADERS` convention — comma-separated `key=value`
|
|
166
|
+
* pairs (e.g. `x-api-key=abc,x-tenant=42`) — into a header map, or undefined when
|
|
167
|
+
* unset/empty. Shared by every facade so the two transports read headers identically.
|
|
168
|
+
*/
|
|
169
|
+
export function parseOtlpHeaders(raw) {
|
|
170
|
+
const trimmed = raw?.trim();
|
|
171
|
+
if (!trimmed)
|
|
172
|
+
return undefined;
|
|
173
|
+
const headers = {};
|
|
174
|
+
for (const pair of trimmed.split(',')) {
|
|
175
|
+
const eq = pair.indexOf('=');
|
|
176
|
+
if (eq <= 0)
|
|
177
|
+
continue;
|
|
178
|
+
const key = pair.slice(0, eq).trim();
|
|
179
|
+
const value = pair.slice(eq + 1).trim();
|
|
180
|
+
if (key)
|
|
181
|
+
headers[key] = value;
|
|
182
|
+
}
|
|
183
|
+
return Object.keys(headers).length ? headers : undefined;
|
|
184
|
+
}
|
|
185
|
+
// ---- platform-metrics sweep config parsing (shared by both facades) --------
|
|
186
|
+
/** Default platform-metrics sweep interval (Node timer); the Worker is cron-driven. */
|
|
187
|
+
export const PLATFORM_METRICS_DEFAULT_INTERVAL_MS = 60_000;
|
|
188
|
+
/** The trailing windows the platform-metrics sweep may aggregate over. */
|
|
189
|
+
export const PLATFORM_METRICS_WINDOWS = ['1h', '24h', '7d'];
|
|
190
|
+
/**
|
|
191
|
+
* Parse `OTEL_PLATFORM_METRICS_INTERVAL_MS` into a positive integer ms, falling back to
|
|
192
|
+
* {@link PLATFORM_METRICS_DEFAULT_INTERVAL_MS} for unset / non-numeric / non-positive values.
|
|
193
|
+
* Shared by every facade so the sweep cadence is parsed identically.
|
|
194
|
+
*/
|
|
195
|
+
export function parsePlatformMetricsIntervalMs(raw) {
|
|
196
|
+
const n = Number(raw?.trim());
|
|
197
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : PLATFORM_METRICS_DEFAULT_INTERVAL_MS;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Parse `OTEL_PLATFORM_METRICS_WINDOW` into a valid trailing window, defaulting to `1h`
|
|
201
|
+
* (the shortest, most operationally useful — the OTel backend builds longer trends from the
|
|
202
|
+
* gauge series). Shared so both facades default + validate identically.
|
|
203
|
+
*/
|
|
204
|
+
export function parsePlatformMetricsWindow(raw) {
|
|
205
|
+
const w = raw?.trim();
|
|
206
|
+
return w === '24h' || w === '7d' ? w : '1h';
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { LlmGenerationEvent, LlmToolSpan, LlmToolSpanContext } from '@cat-factory/kernel';
|
|
2
|
+
import type { PlatformObservability } from '@cat-factory/contracts';
|
|
3
|
+
/** Default OTLP resource `service.name`; overridable via `OTEL_SERVICE_NAME`. */
|
|
4
|
+
export declare const DEFAULT_SERVICE_NAME = "cat-factory";
|
|
5
|
+
/** The instrumentation scope name stamped on every emitted span/metric. */
|
|
6
|
+
export declare const SCOPE_NAME = "@cat-factory/observability-otel";
|
|
7
|
+
/**
|
|
8
|
+
* Attribute keys, following the OpenTelemetry GenAI semantic conventions where they exist
|
|
9
|
+
* (`gen_ai.*`) plus a small `cat_factory.*` namespace for our own dimensions. Centralised
|
|
10
|
+
* so the two transports can never disagree on a key.
|
|
11
|
+
*/
|
|
12
|
+
export declare const ATTR: {
|
|
13
|
+
readonly system: 'gen_ai.system';
|
|
14
|
+
readonly requestModel: 'gen_ai.request.model';
|
|
15
|
+
readonly inputTokens: 'gen_ai.usage.input_tokens';
|
|
16
|
+
readonly outputTokens: 'gen_ai.usage.output_tokens';
|
|
17
|
+
readonly finishReasons: 'gen_ai.response.finish_reasons';
|
|
18
|
+
readonly tokenType: 'gen_ai.token.type';
|
|
19
|
+
readonly workspaceId: 'cat_factory.workspace_id';
|
|
20
|
+
readonly agentKind: 'cat_factory.agent_kind';
|
|
21
|
+
readonly serviceName: 'service.name';
|
|
22
|
+
};
|
|
23
|
+
/** Metric names + units (OTel GenAI client metrics). */
|
|
24
|
+
export declare const METRIC: {
|
|
25
|
+
readonly tokenUsage: 'gen_ai.client.token.usage';
|
|
26
|
+
readonly duration: 'gen_ai.client.operation.duration';
|
|
27
|
+
};
|
|
28
|
+
export declare const TOKEN_UNIT = "{token}";
|
|
29
|
+
export declare const DURATION_UNIT = "s";
|
|
30
|
+
/** A neutral attribute value both transports understand (string / number / string list). */
|
|
31
|
+
export type AttributeValue = string | number | string[];
|
|
32
|
+
export type AttributeMap = Record<string, AttributeValue>;
|
|
33
|
+
interface MappedEvent {
|
|
34
|
+
name: string;
|
|
35
|
+
/** Epoch ms. */
|
|
36
|
+
timeMs: number;
|
|
37
|
+
attributes: AttributeMap;
|
|
38
|
+
}
|
|
39
|
+
/** A transport-neutral span, ready to encode as OTLP JSON or feed the SDK tracer. */
|
|
40
|
+
export interface MappedSpan {
|
|
41
|
+
/** 32-hex trace id (a run's calls share one; standalone calls get a random one). */
|
|
42
|
+
traceId: string;
|
|
43
|
+
name: string;
|
|
44
|
+
/** Epoch ms. */
|
|
45
|
+
startTimeMs: number;
|
|
46
|
+
/** Epoch ms. */
|
|
47
|
+
endTimeMs: number;
|
|
48
|
+
/** false ⇒ the span carries ERROR status + {@link statusMessage}. */
|
|
49
|
+
ok: boolean;
|
|
50
|
+
statusMessage?: string;
|
|
51
|
+
attributes: AttributeMap;
|
|
52
|
+
events: MappedEvent[];
|
|
53
|
+
}
|
|
54
|
+
/** One token-usage counter data point (one per {@link ATTR.tokenType}). */
|
|
55
|
+
interface MappedTokenUsage {
|
|
56
|
+
value: number;
|
|
57
|
+
attributes: AttributeMap;
|
|
58
|
+
}
|
|
59
|
+
/** The metrics derived from one generation. */
|
|
60
|
+
export interface MappedMetrics {
|
|
61
|
+
tokenUsage: MappedTokenUsage[];
|
|
62
|
+
/** Request duration in seconds (histogram value). */
|
|
63
|
+
durationSeconds: number;
|
|
64
|
+
durationAttributes: AttributeMap;
|
|
65
|
+
/** Epoch ms bounds for the (delta) data points. */
|
|
66
|
+
startTimeMs: number;
|
|
67
|
+
endTimeMs: number;
|
|
68
|
+
}
|
|
69
|
+
export declare function randomTraceId(): string;
|
|
70
|
+
export declare function randomSpanId(): string;
|
|
71
|
+
/** Epoch ms → OTLP unix-nano string (string arithmetic avoids float precision loss). */
|
|
72
|
+
export declare function toUnixNano(ms: number): string;
|
|
73
|
+
/** Map one completed LLM call to a neutral span. */
|
|
74
|
+
export declare function mapGeneration(event: LlmGenerationEvent): MappedSpan;
|
|
75
|
+
/** Map one completed LLM call to its token-usage + duration metrics. */
|
|
76
|
+
export declare function mapGenerationMetrics(event: LlmGenerationEvent): MappedMetrics;
|
|
77
|
+
/** Metric names for the deployment-level platform observability gauges. */
|
|
78
|
+
export declare const PLATFORM_METRIC: {
|
|
79
|
+
/** Windowed run count, split by run status (done/failed/running/…). */
|
|
80
|
+
readonly runs: 'cat_factory.platform.runs';
|
|
81
|
+
/** Windowed `done / (done + failed)` success ratio (0..1). */
|
|
82
|
+
readonly runSuccessRate: 'cat_factory.platform.run_success_rate';
|
|
83
|
+
/** Windowed failed-run count, split by failure kind. */
|
|
84
|
+
readonly runFailures: 'cat_factory.platform.run_failures';
|
|
85
|
+
/** Current live/parked run count (a snapshot, not windowed), split by lifecycle state. */
|
|
86
|
+
readonly liveRuns: 'cat_factory.platform.live_runs';
|
|
87
|
+
/** Windowed wall-clock run duration (seconds), split by statistic (avg/min/max/pNN). */
|
|
88
|
+
readonly runDuration: 'cat_factory.platform.run_duration';
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Attribute keys for the platform metrics. `account_id` is the tenant scope (bounded — the
|
|
92
|
+
* billing entity, far fewer than workspaces, so safe on a metric time series, unlike the
|
|
93
|
+
* workspace id excluded from the per-call metrics); `window` labels the trailing aggregation
|
|
94
|
+
* window. The remaining keys are the bounded split dimensions of each gauge.
|
|
95
|
+
*/
|
|
96
|
+
export declare const PLATFORM_ATTR: {
|
|
97
|
+
readonly accountId: 'cat_factory.account_id';
|
|
98
|
+
readonly window: 'cat_factory.window';
|
|
99
|
+
readonly runStatus: 'cat_factory.run_status';
|
|
100
|
+
readonly runState: 'cat_factory.run_state';
|
|
101
|
+
readonly failureKind: 'cat_factory.failure_kind';
|
|
102
|
+
readonly durationStat: 'cat_factory.duration_stat';
|
|
103
|
+
};
|
|
104
|
+
/** Metric units: a dimensionless run count, a dimensionless ratio, and seconds. */
|
|
105
|
+
export declare const RUN_UNIT = "{run}";
|
|
106
|
+
export declare const RATIO_UNIT = "1";
|
|
107
|
+
/** One gauge data point: its dimensions, value, and whether to encode as int or double. */
|
|
108
|
+
export interface MappedGaugePoint {
|
|
109
|
+
attributes: AttributeMap;
|
|
110
|
+
value: number;
|
|
111
|
+
/** true ⇒ encode as an integer (counts); false ⇒ a double (ratios/durations). */
|
|
112
|
+
isInt: boolean;
|
|
113
|
+
}
|
|
114
|
+
/** A gauge metric ready to encode as OTLP or feed the SDK meter. */
|
|
115
|
+
export interface MappedGauge {
|
|
116
|
+
name: string;
|
|
117
|
+
unit: string;
|
|
118
|
+
points: MappedGaugePoint[];
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Map a {@link PlatformObservability} projection to the OpenTelemetry gauge metrics. All are
|
|
122
|
+
* point-in-time gauges (the OTel backend builds trends from the series over time), stamped
|
|
123
|
+
* with the projection's `generatedAt`. Every point carries the `account_id`; the windowed
|
|
124
|
+
* gauges additionally carry the `window` label. Null/absent aggregates (e.g. a success rate
|
|
125
|
+
* or percentiles with no terminal runs) are omitted rather than emitted as a misleading zero.
|
|
126
|
+
*/
|
|
127
|
+
export declare function mapPlatformMetrics(snapshot: PlatformObservability, dims: {
|
|
128
|
+
accountId: string;
|
|
129
|
+
}): MappedGauge[];
|
|
130
|
+
/** Map one container tool call to a neutral span under its run's trace. */
|
|
131
|
+
export declare function mapToolSpan(context: LlmToolSpanContext, span: LlmToolSpan): MappedSpan;
|
|
132
|
+
export {};
|
|
133
|
+
//# sourceMappingURL=mapping.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../src/mapping.ts"],"names":[],"mappings":"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"}
|