@odla-ai/o11y 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/index.cjs +100 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +100 -14
- package/dist/index.js.map +1 -1
- package/llms.txt +27 -2
- package/package.json +5 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cory Ondrejka
|
|
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
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# @odla-ai/o11y
|
|
2
2
|
|
|
3
|
+
> ⚠️ **Experimental — an agentic-coding experiment.** This package is built and
|
|
4
|
+
> operated by autonomous coding agents as an experiment in agentic loops. APIs
|
|
5
|
+
> change without notice and nothing here is production-hardened. **Use at your
|
|
6
|
+
> own risk.**
|
|
7
|
+
|
|
3
8
|
Official observability client for [odla](https://github.com/) Cloudflare Workers.
|
|
4
9
|
Wrap your Worker once and get OpenTelemetry **traces**, **metrics**, structured
|
|
5
10
|
**errors**, and **LLM cost** — exported over OTLP to the odla-o11y collector.
|
|
@@ -44,6 +49,16 @@ Config is read from env vars (override per call via the second `withObservabilit
|
|
|
44
49
|
|
|
45
50
|
`wrangler.jsonc` needs `"compatibility_flags": ["nodejs_compat"]` (for AsyncLocalStorage).
|
|
46
51
|
|
|
52
|
+
### Private ingest via a service binding
|
|
53
|
+
|
|
54
|
+
By default all signals export over `fetch` to `ODLA_O11Y_ENDPOINT`. If the Worker
|
|
55
|
+
is bound to the collector (a service binding named `ODLA_O11Y_COLLECTOR`, or one
|
|
56
|
+
passed as `opts.fetcher`), the SDK routes **traces, metrics, and logs** through
|
|
57
|
+
that binding instead — private worker-to-worker, no public hop, and the endpoint
|
|
58
|
+
host becomes irrelevant. This is the transport odla's first-party hosting uses;
|
|
59
|
+
the binding and its credential are injected at deploy time, so instrumented code
|
|
60
|
+
stays `export default withObservability(handler)` with nothing else to set.
|
|
61
|
+
|
|
47
62
|
Keep `ODLA_O11Y_ENDPOINT` and `ODLA_O11Y_SERVICE` set as plain vars in every
|
|
48
63
|
environment and treat only `ODLA_O11Y_TOKEN` as an optional secret: the
|
|
49
64
|
metrics/logs sink no-ops without an endpoint, but the trace exporter builds
|
package/dist/index.cjs
CHANGED
|
@@ -89,6 +89,29 @@ function encodeLogsRequest(resource, scopeName, records) {
|
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// ../o11y-core/src/encode-trace.ts
|
|
93
|
+
function encodeTraceRequest(resource, scopeName, spans) {
|
|
94
|
+
const otlpSpans = spans.map((s) => ({
|
|
95
|
+
traceId: s.traceId,
|
|
96
|
+
spanId: s.spanId,
|
|
97
|
+
...s.parentSpanId ? { parentSpanId: s.parentSpanId } : {},
|
|
98
|
+
name: s.name,
|
|
99
|
+
kind: s.kind,
|
|
100
|
+
startTimeUnixNano: s.startUnixNano,
|
|
101
|
+
endTimeUnixNano: s.endUnixNano,
|
|
102
|
+
attributes: attrsToKvList(s.attributes),
|
|
103
|
+
status: { code: s.statusCode }
|
|
104
|
+
}));
|
|
105
|
+
return {
|
|
106
|
+
resourceSpans: [
|
|
107
|
+
{
|
|
108
|
+
resource: { attributes: attrsToKvList(resource) },
|
|
109
|
+
scopeSpans: [{ scope: { name: scopeName }, spans: otlpSpans }]
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
92
115
|
// ../o11y-core/src/pricing.ts
|
|
93
116
|
var MODEL_PRICING = {
|
|
94
117
|
"claude-haiku-4-5": { inputPerMTok: 1, outputPerMTok: 5 },
|
|
@@ -107,19 +130,25 @@ function costUsd(model, inputTokens, outputTokens, override) {
|
|
|
107
130
|
|
|
108
131
|
// src/context.ts
|
|
109
132
|
var SCOPE = "@odla-ai/o11y";
|
|
133
|
+
function asBinding(x) {
|
|
134
|
+
return x && typeof x.fetch === "function" ? x : void 0;
|
|
135
|
+
}
|
|
110
136
|
function evalOpts(opts, env) {
|
|
111
137
|
return typeof opts === "function" ? opts(env) : opts ?? {};
|
|
112
138
|
}
|
|
113
139
|
function resolveConfig(env, opts) {
|
|
114
140
|
const o = evalOpts(opts, env);
|
|
115
141
|
const e = env;
|
|
142
|
+
const fetcher = o.fetcher ?? asBinding(env["ODLA_O11Y_COLLECTOR"]);
|
|
143
|
+
const endpoint = (o.endpoint ?? e["ODLA_O11Y_ENDPOINT"] ?? (fetcher ? "https://svc.internal" : "")).replace(/\/$/, "");
|
|
116
144
|
return {
|
|
117
145
|
service: o.service ?? e["ODLA_O11Y_SERVICE"] ?? "unknown",
|
|
118
|
-
endpoint
|
|
146
|
+
endpoint,
|
|
119
147
|
token: o.token ?? e["ODLA_O11Y_TOKEN"],
|
|
120
148
|
version: o.version ?? e["ODLA_O11Y_VERSION"] ?? "0.0.0",
|
|
121
149
|
attributes: o.attributes ?? {},
|
|
122
|
-
sampleRatio: o.sampleRatio
|
|
150
|
+
sampleRatio: o.sampleRatio,
|
|
151
|
+
fetcher
|
|
123
152
|
};
|
|
124
153
|
}
|
|
125
154
|
function nowNano() {
|
|
@@ -135,16 +164,17 @@ function runWithSink(sink, fn) {
|
|
|
135
164
|
function resourceAttrs(config) {
|
|
136
165
|
return { "service.name": config.service, "service.version": config.version, ...config.attributes };
|
|
137
166
|
}
|
|
138
|
-
async function post(url, body, token) {
|
|
167
|
+
async function post(url, body, token, fetcher) {
|
|
168
|
+
const init = {
|
|
169
|
+
method: "POST",
|
|
170
|
+
headers: {
|
|
171
|
+
"content-type": "application/json",
|
|
172
|
+
...token ? { authorization: `Bearer ${token}` } : {}
|
|
173
|
+
},
|
|
174
|
+
body: JSON.stringify(body)
|
|
175
|
+
};
|
|
139
176
|
try {
|
|
140
|
-
await fetch(url,
|
|
141
|
-
method: "POST",
|
|
142
|
-
headers: {
|
|
143
|
-
"content-type": "application/json",
|
|
144
|
-
...token ? { authorization: `Bearer ${token}` } : {}
|
|
145
|
-
},
|
|
146
|
-
body: JSON.stringify(body)
|
|
147
|
-
});
|
|
177
|
+
await (fetcher ? fetcher.fetch(url, init) : fetch(url, init));
|
|
148
178
|
} catch {
|
|
149
179
|
}
|
|
150
180
|
}
|
|
@@ -161,15 +191,16 @@ function createSink(config) {
|
|
|
161
191
|
}
|
|
162
192
|
const resource = resourceAttrs(this.config);
|
|
163
193
|
const jobs = [];
|
|
194
|
+
const { token, fetcher } = this.config;
|
|
164
195
|
if (this.metrics.length) {
|
|
165
196
|
jobs.push(
|
|
166
|
-
post(`${this.config.endpoint}/v1/metrics`, encodeMetricsRequest(resource, SCOPE, this.metrics),
|
|
197
|
+
post(`${this.config.endpoint}/v1/metrics`, encodeMetricsRequest(resource, SCOPE, this.metrics), token, fetcher)
|
|
167
198
|
);
|
|
168
199
|
this.metrics = [];
|
|
169
200
|
}
|
|
170
201
|
if (this.logs.length) {
|
|
171
202
|
jobs.push(
|
|
172
|
-
post(`${this.config.endpoint}/v1/logs`, encodeLogsRequest(resource, SCOPE, this.logs),
|
|
203
|
+
post(`${this.config.endpoint}/v1/logs`, encodeLogsRequest(resource, SCOPE, this.logs), token, fetcher)
|
|
173
204
|
);
|
|
174
205
|
this.logs = [];
|
|
175
206
|
}
|
|
@@ -178,13 +209,68 @@ function createSink(config) {
|
|
|
178
209
|
};
|
|
179
210
|
}
|
|
180
211
|
|
|
212
|
+
// src/trace-export.ts
|
|
213
|
+
var SUCCESS = 0;
|
|
214
|
+
var FAILED = 1;
|
|
215
|
+
var OTLP_KIND_OFFSET = 1;
|
|
216
|
+
function hrToNano([seconds, nanos]) {
|
|
217
|
+
return (BigInt(seconds) * 1000000000n + BigInt(nanos)).toString();
|
|
218
|
+
}
|
|
219
|
+
function toSpanInput(span2) {
|
|
220
|
+
const ctx = span2.spanContext();
|
|
221
|
+
const attributes = {};
|
|
222
|
+
for (const [key, value] of Object.entries(span2.attributes)) {
|
|
223
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
224
|
+
attributes[key] = value;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
traceId: ctx.traceId,
|
|
229
|
+
spanId: ctx.spanId,
|
|
230
|
+
parentSpanId: span2.parentSpanContext?.spanId ?? "",
|
|
231
|
+
name: span2.name,
|
|
232
|
+
kind: span2.kind + OTLP_KIND_OFFSET,
|
|
233
|
+
statusCode: span2.status.code,
|
|
234
|
+
startUnixNano: hrToNano(span2.startTime),
|
|
235
|
+
endUnixNano: hrToNano(span2.endTime),
|
|
236
|
+
attributes
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
var BindingSpanExporter = class {
|
|
240
|
+
constructor(binding, url, headers, resource, scope) {
|
|
241
|
+
this.binding = binding;
|
|
242
|
+
this.url = url;
|
|
243
|
+
this.headers = headers;
|
|
244
|
+
this.resource = resource;
|
|
245
|
+
this.scope = scope;
|
|
246
|
+
}
|
|
247
|
+
binding;
|
|
248
|
+
url;
|
|
249
|
+
headers;
|
|
250
|
+
resource;
|
|
251
|
+
scope;
|
|
252
|
+
export(spans, resultCallback) {
|
|
253
|
+
const body = encodeTraceRequest(this.resource, this.scope, spans.map(toSpanInput));
|
|
254
|
+
this.binding.fetch(this.url, {
|
|
255
|
+
method: "POST",
|
|
256
|
+
headers: { "content-type": "application/json", ...this.headers },
|
|
257
|
+
body: JSON.stringify(body)
|
|
258
|
+
}).then(() => resultCallback({ code: SUCCESS })).catch((error) => resultCallback({ code: FAILED, error }));
|
|
259
|
+
}
|
|
260
|
+
async shutdown() {
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
181
264
|
// src/instrument.ts
|
|
182
265
|
function traceConfig(env, opts) {
|
|
183
266
|
const c = resolveConfig(env, opts);
|
|
184
267
|
const headers = {};
|
|
185
268
|
if (c.token) headers.authorization = `Bearer ${c.token}`;
|
|
269
|
+
const url = `${c.endpoint}/v1/traces`;
|
|
186
270
|
const config = {
|
|
187
|
-
exporter
|
|
271
|
+
// A service binding needs a custom exporter (the stock OTLP exporter posts
|
|
272
|
+
// over the global fetch); without one, fall back to the public URL exporter.
|
|
273
|
+
exporter: c.fetcher ? new BindingSpanExporter(c.fetcher, url, headers, resourceAttrs(c), SCOPE) : { url, headers },
|
|
188
274
|
service: { name: c.service, version: c.version }
|
|
189
275
|
};
|
|
190
276
|
if (c.sampleRatio != null) config.sampling = { headSampler: { ratio: c.sampleRatio } };
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/instrument.ts","../src/context.ts","../../o11y-core/src/encode.ts","../../o11y-core/src/pricing.ts","../src/span.ts","../src/metrics.ts","../src/logs.ts","../src/llm.ts"],"sourcesContent":["export { instrumentDurableObject, withObservability } from \"./instrument.js\";\nexport { span } from \"./span.js\";\nexport { count, metrics } from \"./metrics.js\";\nexport { recordError } from \"./logs.js\";\nexport { recordLlmUsage } from \"./llm.js\";\n\nexport type { ObservabilityOptions, OptsFn } from \"./context.js\";\nexport type { SpanKindName, SpanOpts } from \"./span.js\";\nexport type { Metrics } from \"./metrics.js\";\nexport type { ErrorReport } from \"./logs.js\";\nexport type { LlmCostOpts, LlmUsage } from \"./llm.js\";\nexport type { Attrs } from \"@odla/o11y-core\";\n","import { instrument, instrumentDO, type ResolveConfigFn, type TraceConfig } from \"@microlabs/otel-cf-workers\";\nimport { createSink, resolveConfig, runWithSink, type OptsFn } from \"./context.js\";\n\n/** Build the otel-cf-workers trace config (exporter + service) from env/opts. */\nfunction traceConfig<E>(env: E, opts?: OptsFn<E>): TraceConfig {\n const c = resolveConfig(env, opts);\n const headers: Record<string, string> = {};\n if (c.token) headers.authorization = `Bearer ${c.token}`;\n const config: TraceConfig = {\n exporter: { url: `${c.endpoint}/v1/traces`, headers },\n service: { name: c.service, version: c.version },\n };\n if (c.sampleRatio != null) config.sampling = { headSampler: { ratio: c.sampleRatio } };\n return config;\n}\n\n/** Wrap each handler entrypoint so an ALS metrics/logs sink is live for the\n * duration of the invocation and flushed via ctx.waitUntil at the end. */\nfunction wrapHandler<E>(handler: ExportedHandler<E>, opts?: OptsFn<E>): ExportedHandler<E> {\n const wrapped: ExportedHandler<E> = { ...handler };\n\n const fetchImpl = handler.fetch;\n if (fetchImpl) {\n wrapped.fetch = (req, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await fetchImpl(req, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n const scheduledImpl = handler.scheduled;\n if (scheduledImpl) {\n wrapped.scheduled = (controller, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await scheduledImpl(controller, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n return wrapped;\n}\n\n/** Wrap a Worker handler with tracing (otel-cf-workers) + the metrics/logs sink.\n * Config is read from env vars ODLA_O11Y_{ENDPOINT,SERVICE,TOKEN,VERSION} unless\n * overridden by `opts`. */\nexport function withObservability<E = unknown>(\n handler: ExportedHandler<E>,\n opts?: OptsFn<E>,\n): ExportedHandler<E> {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env as E, opts);\n // instrument constrains E to its own Env type; we support any service env, so\n // cross the boundary with `any` and restore the caller's E on the way out.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrument(wrapHandler(handler, opts) as ExportedHandler<any>, configFn) as ExportedHandler<E>;\n}\n\n/** Wrap a Durable Object class with tracing. (Establishing the metrics/logs sink\n * inside DO methods is added in P1 when odla-db's usage counters are bridged.) */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function instrumentDurableObject<T extends abstract new (...args: any[]) => object>(\n cls: T,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n opts?: OptsFn<any>,\n): T {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env, opts);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrumentDO(cls as any, configFn) as unknown as T;\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport {\n encodeLogsRequest,\n encodeMetricsRequest,\n type Attrs,\n type LogRecord,\n type MetricPoint,\n} from \"@odla/o11y-core\";\n\n/** The instrumentation scope name stamped on every signal. */\nexport const SCOPE = \"@odla-ai/o11y\";\n\nexport interface ObservabilityOptions {\n /** Service name (default: env.ODLA_O11Y_SERVICE). */\n service?: string;\n /** Collector base URL (default: env.ODLA_O11Y_ENDPOINT). */\n endpoint?: string;\n /** Per-service bearer token (default: env.ODLA_O11Y_TOKEN). */\n token?: string;\n /** Release/version tag (default: env.ODLA_O11Y_VERSION ?? \"0.0.0\"). */\n version?: string;\n /** Head-sampling ratio 0..1 for traces (default: 1). */\n sampleRatio?: number;\n /** Static resource attributes merged onto every signal. */\n attributes?: Attrs;\n}\n\nexport type OptsFn<E> = ObservabilityOptions | ((env: E) => ObservabilityOptions);\n\nexport interface ResolvedConfig {\n service: string;\n endpoint: string;\n token?: string;\n version: string;\n attributes: Attrs;\n sampleRatio?: number;\n}\n\nfunction evalOpts<E>(opts: OptsFn<E> | undefined, env: E): ObservabilityOptions {\n return typeof opts === \"function\" ? opts(env) : (opts ?? {});\n}\n\n/** Resolve options against the service's env vars. */\nexport function resolveConfig<E>(env: E, opts?: OptsFn<E>): ResolvedConfig {\n const o = evalOpts(opts, env);\n const e = env as Record<string, string | undefined>;\n return {\n service: o.service ?? e[\"ODLA_O11Y_SERVICE\"] ?? \"unknown\",\n endpoint: (o.endpoint ?? e[\"ODLA_O11Y_ENDPOINT\"] ?? \"\").replace(/\\/$/, \"\"),\n token: o.token ?? e[\"ODLA_O11Y_TOKEN\"],\n version: o.version ?? e[\"ODLA_O11Y_VERSION\"] ?? \"0.0.0\",\n attributes: o.attributes ?? {},\n sampleRatio: o.sampleRatio,\n };\n}\n\n/** Nanoseconds since epoch as a decimal string (OTLP timestamp format). */\nexport function nowNano(): string {\n return `${Date.now()}000000`;\n}\n\nexport interface Sink {\n config: ResolvedConfig;\n metrics: MetricPoint[];\n logs: LogRecord[];\n flush(): Promise<void>;\n}\n\nconst als = new AsyncLocalStorage<Sink>();\n\nexport function currentSink(): Sink | undefined {\n return als.getStore();\n}\n\nexport function runWithSink<T>(sink: Sink, fn: () => Promise<T>): Promise<T> {\n return als.run(sink, fn);\n}\n\nfunction resourceAttrs(config: ResolvedConfig): Attrs {\n return { \"service.name\": config.service, \"service.version\": config.version, ...config.attributes };\n}\n\nasync function post(url: string, body: unknown, token?: string): Promise<void> {\n try {\n await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(token ? { authorization: `Bearer ${token}` } : {}),\n },\n body: JSON.stringify(body),\n });\n } catch {\n // Telemetry export is best-effort — never surface into the app.\n }\n}\n\n/** A per-invocation sink: accumulates metric/log records and flushes them to the\n * collector as OTLP/JSON (traces are exported separately by otel-cf-workers). */\nexport function createSink(config: ResolvedConfig): Sink {\n return {\n config,\n metrics: [],\n logs: [],\n async flush(): Promise<void> {\n if (!this.config.endpoint) {\n this.metrics = [];\n this.logs = [];\n return;\n }\n const resource = resourceAttrs(this.config);\n const jobs: Promise<void>[] = [];\n if (this.metrics.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/metrics`, encodeMetricsRequest(resource, SCOPE, this.metrics), this.config.token),\n );\n this.metrics = [];\n }\n if (this.logs.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/logs`, encodeLogsRequest(resource, SCOPE, this.logs), this.config.token),\n );\n this.logs = [];\n }\n await Promise.all(jobs);\n },\n };\n}\n","// OTLP/JSON encoders — the emit side of the wire contract (the SDK produces\n// these; the collector decodes them in later phases). Traces are produced by\n// @microlabs/otel-cf-workers; metrics and logs are hand-rolled here because that\n// library is traces-only and the Node OTel metrics SDK doesn't fit the isolate\n// model.\n\nimport type { Attrs, OtlpKeyValue, OtlpResource, OtlpScope } from \"./otlp.js\";\n\n/** Inverse of kvListToAttrs: a flat attr map → OTLP KeyValue[]. */\nexport function attrsToKvList(attrs: Attrs): OtlpKeyValue[] {\n const out: OtlpKeyValue[] = [];\n for (const [key, value] of Object.entries(attrs)) {\n if (typeof value === \"string\") out.push({ key, value: { stringValue: value } });\n else if (typeof value === \"boolean\") out.push({ key, value: { boolValue: value } });\n else if (Number.isInteger(value)) out.push({ key, value: { intValue: String(value) } });\n else out.push({ key, value: { doubleValue: value } });\n }\n return out;\n}\n\n// ---- Metrics ----\n\nexport type MetricKind = \"sum\" | \"gauge\";\n\nexport interface MetricPoint {\n name: string;\n unit?: string;\n kind: MetricKind;\n value: number;\n attrs: Attrs;\n startUnixNano: string;\n timeUnixNano: string;\n}\n\ninterface OtlpNumberDataPoint {\n attributes?: OtlpKeyValue[];\n startTimeUnixNano?: string;\n timeUnixNano: string;\n asDouble: number;\n}\n\ninterface OtlpMetric {\n name: string;\n unit?: string;\n sum?: { aggregationTemporality: number; isMonotonic: boolean; dataPoints: OtlpNumberDataPoint[] };\n gauge?: { dataPoints: OtlpNumberDataPoint[] };\n}\n\nexport interface ExportMetricsServiceRequest {\n resourceMetrics: Array<{\n resource: OtlpResource;\n scopeMetrics: Array<{ scope: OtlpScope; metrics: OtlpMetric[] }>;\n }>;\n}\n\n// OTLP AggregationTemporality: 1 = DELTA. Each ephemeral isolate reports its own\n// delta; the collector aggregates across invocations into Analytics Engine.\nconst DELTA = 1;\n\nexport function encodeMetricsRequest(\n resource: Attrs,\n scopeName: string,\n points: MetricPoint[],\n): ExportMetricsServiceRequest {\n const metrics: OtlpMetric[] = points.map((p) => {\n const dp: OtlpNumberDataPoint = {\n attributes: attrsToKvList(p.attrs),\n startTimeUnixNano: p.startUnixNano,\n timeUnixNano: p.timeUnixNano,\n asDouble: p.value,\n };\n return p.kind === \"gauge\"\n ? { name: p.name, unit: p.unit, gauge: { dataPoints: [dp] } }\n : { name: p.name, unit: p.unit, sum: { aggregationTemporality: DELTA, isMonotonic: true, dataPoints: [dp] } };\n });\n return {\n resourceMetrics: [\n { resource: { attributes: attrsToKvList(resource) }, scopeMetrics: [{ scope: { name: scopeName }, metrics }] },\n ],\n };\n}\n\n// ---- Logs (errors ride the logs signal) ----\n\nexport interface LogRecord {\n timeUnixNano: string;\n severityNumber: number; // OTel: 17 = ERROR, 9 = INFO\n severityText: string;\n body: string;\n attrs: Attrs;\n traceId?: string;\n spanId?: string;\n}\n\nexport interface ExportLogsServiceRequest {\n resourceLogs: Array<{\n resource: OtlpResource;\n scopeLogs: Array<{\n scope: OtlpScope;\n logRecords: Array<{\n timeUnixNano: string;\n observedTimeUnixNano: string;\n severityNumber: number;\n severityText: string;\n body: { stringValue: string };\n attributes: OtlpKeyValue[];\n traceId?: string;\n spanId?: string;\n }>;\n }>;\n }>;\n}\n\nexport function encodeLogsRequest(\n resource: Attrs,\n scopeName: string,\n records: LogRecord[],\n): ExportLogsServiceRequest {\n return {\n resourceLogs: [\n {\n resource: { attributes: attrsToKvList(resource) },\n scopeLogs: [\n {\n scope: { name: scopeName },\n logRecords: records.map((r) => ({\n timeUnixNano: r.timeUnixNano,\n observedTimeUnixNano: r.timeUnixNano,\n severityNumber: r.severityNumber,\n severityText: r.severityText,\n body: { stringValue: r.body },\n attributes: attrsToKvList(r.attrs),\n ...(r.traceId ? { traceId: r.traceId } : {}),\n ...(r.spanId ? { spanId: r.spanId } : {}),\n })),\n },\n ],\n },\n ],\n };\n}\n","// LLM price table + cost computation. OTel GenAI semconv has no cost attribute,\n// so we compute cost downstream from raw token counts × model price. Keep this\n// table in sync with current model pricing (verify against the claude-api\n// reference); unknown models cost 0 and should be added here.\n\nexport interface ModelPrice {\n /** USD per million input tokens. */\n inputPerMTok: number;\n /** USD per million output tokens. */\n outputPerMTok: number;\n}\n\n/** Prices in USD per million tokens (MTok). */\nexport const MODEL_PRICING: Record<string, ModelPrice> = {\n \"claude-haiku-4-5\": { inputPerMTok: 1, outputPerMTok: 5 },\n \"claude-sonnet-4-6\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-sonnet-5\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-opus-4-6\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-7\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-8\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-fable-5\": { inputPerMTok: 10, outputPerMTok: 50 },\n};\n\n/** Compute USD cost for a call. Pass `override` to price a model not in the\n * table (or to apply promotional pricing). Returns 0 for unknown models. */\nexport function costUsd(\n model: string,\n inputTokens: number,\n outputTokens: number,\n override?: ModelPrice,\n): number {\n const price = override ?? MODEL_PRICING[model];\n if (!price) return 0;\n return (inputTokens / 1_000_000) * price.inputPerMTok + (outputTokens / 1_000_000) * price.outputPerMTok;\n}\n","import { SpanKind, SpanStatusCode, trace, type Span } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { SCOPE } from \"./context.js\";\n\nexport type SpanKindName = \"internal\" | \"server\" | \"client\" | \"producer\" | \"consumer\";\n\nexport interface SpanOpts {\n attributes?: Attrs;\n kind?: SpanKindName;\n}\n\nconst KIND: Record<SpanKindName, SpanKind> = {\n internal: SpanKind.INTERNAL,\n server: SpanKind.SERVER,\n client: SpanKind.CLIENT,\n producer: SpanKind.PRODUCER,\n consumer: SpanKind.CONSUMER,\n};\n\n/** Run `fn` inside a new active span. The span is ended automatically and its\n * status set from success/throw. Works whether or not tracing is exporting —\n * falls back to a no-op span if no tracer is installed. */\nexport async function span<T>(\n name: string,\n fn: (span: Span) => Promise<T> | T,\n opts?: SpanOpts,\n): Promise<T> {\n const tracer = trace.getTracer(SCOPE);\n return tracer.startActiveSpan(\n name,\n { attributes: opts?.attributes, kind: opts?.kind ? KIND[opts.kind] : undefined },\n async (s) => {\n try {\n const result = await fn(s);\n s.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (err) {\n s.setStatus({ code: SpanStatusCode.ERROR, message: err instanceof Error ? err.message : String(err) });\n throw err;\n } finally {\n s.end();\n }\n },\n );\n}\n","import type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface Metrics {\n /** Monotonic counter (delta). Formalizes odla-db's usage counters. */\n count(name: string, by?: number, attrs?: Attrs): void;\n /** Last-value gauge. */\n gauge(name: string, value: number, attrs?: Attrs): void;\n /** A single observation (recorded as a delta sum for now; histograms later). */\n histogram(name: string, value: number, attrs?: Attrs, unit?: string): void;\n}\n\nfunction record(kind: \"sum\" | \"gauge\", name: string, value: number, attrs?: Attrs, unit?: string): void {\n const sink = currentSink();\n if (!sink) return; // outside an instrumented invocation — no-op\n const ts = nowNano();\n sink.metrics.push({ name, unit, kind, value, attrs: attrs ?? {}, startUnixNano: ts, timeUnixNano: ts });\n}\n\n/** The ambient metrics API for the current invocation. */\nexport function metrics(): Metrics {\n return {\n count: (name, by = 1, attrs) => record(\"sum\", name, by, attrs),\n gauge: (name, value, attrs) => record(\"gauge\", name, value, attrs),\n histogram: (name, value, attrs, unit) => record(\"sum\", name, value, attrs, unit),\n };\n}\n\n/** Convenience: increment a monotonic counter by `by` (default 1). */\nexport function count(name: string, by = 1, attrs?: Attrs): void {\n record(\"sum\", name, by, attrs);\n}\n","import { SpanStatusCode, trace } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface ErrorReport {\n /** Stable, low-cardinality error code (e.g. \"unique_violation\"). */\n code?: string;\n /** Low-cardinality route/step where it happened. */\n route?: string;\n /** Extra low-cardinality attributes for the index. */\n attributes?: Attrs;\n /** Rich context for triage — kept only in the R2 artifact bundle. */\n artifacts?: Record<string, unknown>;\n /** Dedup key; the collector fingerprints by (type+code+route) if absent. */\n fingerprint?: string;\n}\n\n// OTel severity number for ERROR.\nconst SEVERITY_ERROR = 17;\n\n/** Record a structured error. Returns an artifact id. The message and stack ride\n * the log body + a denied attribute so the collector persists them ONLY in the\n * R2 artifact bundle, never in metrics or the queryable index. */\nexport function recordError(err: unknown, report?: ErrorReport): string {\n const error = err instanceof Error ? err : new Error(String(err));\n const artifactId = `${Date.now().toString(16)}${crypto.randomUUID().replace(/-/g, \"\")}`;\n const span = trace.getActiveSpan();\n\n const attrs: Attrs = {\n \"error.type\": error.name,\n \"odla.artifact_id\": artifactId,\n ...(report?.code ? { \"error.code\": report.code } : {}),\n ...(report?.route ? { \"odla.route\": report.route } : {}),\n ...(report?.fingerprint ? { \"odla.fingerprint\": report.fingerprint } : {}),\n ...(report?.attributes ?? {}),\n };\n\n if (span) {\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });\n span.setAttributes(attrs);\n }\n\n const sink = currentSink();\n if (sink) {\n const ctx = span?.spanContext();\n sink.logs.push({\n timeUnixNano: nowNano(),\n severityNumber: SEVERITY_ERROR,\n severityText: \"ERROR\",\n body: error.message, // full message → R2 bundle only (collector-enforced)\n attrs: {\n ...attrs,\n // Denied keys: stripped from metrics/index, retained in the R2 bundle.\n ...(error.stack ? { \"exception.stacktrace\": error.stack } : {}),\n ...(report?.artifacts ? { \"odla.artifacts\": JSON.stringify(report.artifacts) } : {}),\n },\n ...(ctx ? { traceId: ctx.traceId, spanId: ctx.spanId } : {}),\n });\n }\n\n return artifactId;\n}\n","import { trace } from \"@opentelemetry/api\";\nimport { costUsd, type Attrs, type ModelPrice } from \"@odla/o11y-core\";\nimport { count } from \"./metrics.js\";\n\n/** The token accounting odla-kg's Provider already returns (and drops today). */\nexport interface LlmUsage {\n calls: number;\n inputTokens: number;\n outputTokens: number;\n}\n\nexport interface LlmCostOpts {\n provider: string; // \"claude\" | \"openai\" | ...\n model: string; // e.g. \"claude-sonnet-4-6\"\n operation?: string; // \"extract\" | \"search\" | ...\n attributes?: Attrs;\n /** Override the built-in price table for this call. */\n price?: ModelPrice;\n}\n\n/** Turn LLM token usage into first-class cost. Emits cost/token/call counters\n * and stamps the active span with GenAI semconv attributes. */\nexport function recordLlmUsage(usage: LlmUsage, opts: LlmCostOpts): { costUsd: number } {\n const cost = costUsd(opts.model, usage.inputTokens, usage.outputTokens, opts.price);\n const base: Attrs = {\n \"gen_ai.provider.name\": opts.provider,\n \"gen_ai.request.model\": opts.model,\n ...(opts.operation ? { \"gen_ai.operation.name\": opts.operation } : {}),\n ...(opts.attributes ?? {}),\n };\n\n count(\"odla.llm.cost.usd\", cost, base);\n count(\"odla.llm.calls\", usage.calls, base);\n count(\"odla.llm.tokens\", usage.inputTokens, { ...base, \"gen_ai.token.type\": \"input\" });\n count(\"odla.llm.tokens\", usage.outputTokens, { ...base, \"gen_ai.token.type\": \"output\" });\n\n trace.getActiveSpan()?.setAttributes({\n ...base,\n \"gen_ai.usage.input_tokens\": usage.inputTokens,\n \"gen_ai.usage.output_tokens\": usage.outputTokens,\n \"odla.llm.cost.usd\": cost,\n });\n\n return { costUsd: cost };\n}\n"],"mappings":"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","names":["metrics","import_api","span","import_api"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/instrument.ts","../src/context.ts","../../o11y-core/src/encode.ts","../../o11y-core/src/encode-trace.ts","../../o11y-core/src/pricing.ts","../src/trace-export.ts","../src/span.ts","../src/metrics.ts","../src/logs.ts","../src/llm.ts"],"sourcesContent":["export { instrumentDurableObject, withObservability } from \"./instrument.js\";\nexport { span } from \"./span.js\";\nexport { count, metrics } from \"./metrics.js\";\nexport { recordError } from \"./logs.js\";\nexport { recordLlmUsage } from \"./llm.js\";\n\nexport type { CollectorBinding, ObservabilityOptions, OptsFn } from \"./context.js\";\nexport type { SpanKindName, SpanOpts } from \"./span.js\";\nexport type { Metrics } from \"./metrics.js\";\nexport type { ErrorReport } from \"./logs.js\";\nexport type { LlmCostOpts, LlmUsage } from \"./llm.js\";\nexport type { Attrs } from \"@odla/o11y-core\";\n","import { instrument, instrumentDO, type ResolveConfigFn, type TraceConfig } from \"@microlabs/otel-cf-workers\";\nimport { createSink, resolveConfig, resourceAttrs, runWithSink, SCOPE, type OptsFn } from \"./context.js\";\nimport { BindingSpanExporter } from \"./trace-export.js\";\n\n/** Build the otel-cf-workers trace config (exporter + service) from env/opts.\n * Exported for tests; not part of the package's public surface (see index.ts). */\nexport function traceConfig<E>(env: E, opts?: OptsFn<E>): TraceConfig {\n const c = resolveConfig(env, opts);\n const headers: Record<string, string> = {};\n if (c.token) headers.authorization = `Bearer ${c.token}`;\n const url = `${c.endpoint}/v1/traces`;\n const config: TraceConfig = {\n // A service binding needs a custom exporter (the stock OTLP exporter posts\n // over the global fetch); without one, fall back to the public URL exporter.\n exporter: c.fetcher\n ? new BindingSpanExporter(c.fetcher, url, headers, resourceAttrs(c), SCOPE)\n : { url, headers },\n service: { name: c.service, version: c.version },\n };\n if (c.sampleRatio != null) config.sampling = { headSampler: { ratio: c.sampleRatio } };\n return config;\n}\n\n/** Wrap each handler entrypoint so an ALS metrics/logs sink is live for the\n * duration of the invocation and flushed via ctx.waitUntil at the end.\n * Exported for tests; not part of the package's public surface (see index.ts). */\nexport function wrapHandler<E>(handler: ExportedHandler<E>, opts?: OptsFn<E>): ExportedHandler<E> {\n const wrapped: ExportedHandler<E> = { ...handler };\n\n const fetchImpl = handler.fetch;\n if (fetchImpl) {\n wrapped.fetch = (req, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await fetchImpl(req, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n const scheduledImpl = handler.scheduled;\n if (scheduledImpl) {\n wrapped.scheduled = (controller, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await scheduledImpl(controller, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n return wrapped;\n}\n\n/** Wrap a Worker handler with tracing (otel-cf-workers) + the metrics/logs sink.\n * Config is read from env vars ODLA_O11Y_{ENDPOINT,SERVICE,TOKEN,VERSION} unless\n * overridden by `opts`. */\nexport function withObservability<E = unknown>(\n handler: ExportedHandler<E>,\n opts?: OptsFn<E>,\n): ExportedHandler<E> {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env as E, opts);\n // instrument constrains E to its own Env type; we support any service env, so\n // cross the boundary with `any` and restore the caller's E on the way out.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrument(wrapHandler(handler, opts) as ExportedHandler<any>, configFn) as ExportedHandler<E>;\n}\n\n/** Wrap a Durable Object class with tracing. (Establishing the metrics/logs sink\n * inside DO methods is added in P1 when odla-db's usage counters are bridged.) */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function instrumentDurableObject<T extends abstract new (...args: any[]) => object>(\n cls: T,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n opts?: OptsFn<any>,\n): T {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env, opts);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrumentDO(cls as any, configFn) as unknown as T;\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport {\n encodeLogsRequest,\n encodeMetricsRequest,\n type Attrs,\n type LogRecord,\n type MetricPoint,\n} from \"@odla/o11y-core\";\n\n/** The instrumentation scope name stamped on every signal. */\nexport const SCOPE = \"@odla-ai/o11y\";\n\n/** A Cloudflare service binding to the collector Worker (structural — avoids a\n * hard dependency on `@cloudflare/workers-types`). When present the SDK exports\n * every signal through `binding.fetch` (private worker-to-worker) instead of\n * the public endpoint; first-party hosting injects it as `env.ODLA_O11Y_COLLECTOR`. */\nexport interface CollectorBinding {\n fetch: typeof fetch;\n}\n\nexport interface ObservabilityOptions {\n /** Service name (default: env.ODLA_O11Y_SERVICE). */\n service?: string;\n /** Collector base URL (default: env.ODLA_O11Y_ENDPOINT). */\n endpoint?: string;\n /** Per-service bearer token (default: env.ODLA_O11Y_TOKEN). */\n token?: string;\n /** Release/version tag (default: env.ODLA_O11Y_VERSION ?? \"0.0.0\"). */\n version?: string;\n /** Head-sampling ratio 0..1 for traces (default: 1). */\n sampleRatio?: number;\n /** Static resource attributes merged onto every signal. */\n attributes?: Attrs;\n /** Service binding to the collector. Defaults to an `ODLA_O11Y_COLLECTOR`\n * binding on env; when set, all signals export through it instead of `fetch`. */\n fetcher?: CollectorBinding;\n}\n\nexport type OptsFn<E> = ObservabilityOptions | ((env: E) => ObservabilityOptions);\n\nexport interface ResolvedConfig {\n service: string;\n endpoint: string;\n token?: string;\n version: string;\n attributes: Attrs;\n sampleRatio?: number;\n fetcher?: CollectorBinding;\n}\n\n/** Narrow an arbitrary env binding to a CollectorBinding (has a `fetch` method). */\nfunction asBinding(x: unknown): CollectorBinding | undefined {\n return x && typeof (x as CollectorBinding).fetch === \"function\" ? (x as CollectorBinding) : undefined;\n}\n\nfunction evalOpts<E>(opts: OptsFn<E> | undefined, env: E): ObservabilityOptions {\n return typeof opts === \"function\" ? opts(env) : (opts ?? {});\n}\n\n/** Resolve options against the service's env vars. */\nexport function resolveConfig<E>(env: E, opts?: OptsFn<E>): ResolvedConfig {\n const o = evalOpts(opts, env);\n const e = env as Record<string, string | undefined>;\n const fetcher = o.fetcher ?? asBinding((env as Record<string, unknown>)[\"ODLA_O11Y_COLLECTOR\"]);\n // A binding ignores the URL host (it routes to the bound Worker), but the SDK\n // still needs a well-formed base to build /v1/* paths — default it internally.\n const endpoint = (o.endpoint ?? e[\"ODLA_O11Y_ENDPOINT\"] ?? (fetcher ? \"https://svc.internal\" : \"\")).replace(/\\/$/, \"\");\n return {\n service: o.service ?? e[\"ODLA_O11Y_SERVICE\"] ?? \"unknown\",\n endpoint,\n token: o.token ?? e[\"ODLA_O11Y_TOKEN\"],\n version: o.version ?? e[\"ODLA_O11Y_VERSION\"] ?? \"0.0.0\",\n attributes: o.attributes ?? {},\n sampleRatio: o.sampleRatio,\n fetcher,\n };\n}\n\n/** Nanoseconds since epoch as a decimal string (OTLP timestamp format). */\nexport function nowNano(): string {\n return `${Date.now()}000000`;\n}\n\nexport interface Sink {\n config: ResolvedConfig;\n metrics: MetricPoint[];\n logs: LogRecord[];\n flush(): Promise<void>;\n}\n\nconst als = new AsyncLocalStorage<Sink>();\n\nexport function currentSink(): Sink | undefined {\n return als.getStore();\n}\n\nexport function runWithSink<T>(sink: Sink, fn: () => Promise<T>): Promise<T> {\n return als.run(sink, fn);\n}\n\n/** Resource attributes stamped on every signal. Exported so the trace exporter\n * builds the same resource the metrics/logs sink does. */\nexport function resourceAttrs(config: ResolvedConfig): Attrs {\n return { \"service.name\": config.service, \"service.version\": config.version, ...config.attributes };\n}\n\nasync function post(url: string, body: unknown, token?: string, fetcher?: CollectorBinding): Promise<void> {\n const init = {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(token ? { authorization: `Bearer ${token}` } : {}),\n },\n body: JSON.stringify(body),\n };\n try {\n await (fetcher ? fetcher.fetch(url, init) : fetch(url, init));\n } catch {\n // Telemetry export is best-effort — never surface into the app.\n }\n}\n\n/** A per-invocation sink: accumulates metric/log records and flushes them to the\n * collector as OTLP/JSON (traces are exported separately by otel-cf-workers). */\nexport function createSink(config: ResolvedConfig): Sink {\n return {\n config,\n metrics: [],\n logs: [],\n async flush(): Promise<void> {\n if (!this.config.endpoint) {\n this.metrics = [];\n this.logs = [];\n return;\n }\n const resource = resourceAttrs(this.config);\n const jobs: Promise<void>[] = [];\n const { token, fetcher } = this.config;\n if (this.metrics.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/metrics`, encodeMetricsRequest(resource, SCOPE, this.metrics), token, fetcher),\n );\n this.metrics = [];\n }\n if (this.logs.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/logs`, encodeLogsRequest(resource, SCOPE, this.logs), token, fetcher),\n );\n this.logs = [];\n }\n await Promise.all(jobs);\n },\n };\n}\n","// OTLP/JSON encoders — the emit side of the wire contract (the SDK produces\n// these; the collector decodes them in later phases). Traces are produced by\n// @microlabs/otel-cf-workers; metrics and logs are hand-rolled here because that\n// library is traces-only and the Node OTel metrics SDK doesn't fit the isolate\n// model.\n\nimport type { Attrs, OtlpKeyValue, OtlpResource, OtlpScope } from \"./otlp.js\";\n\n/** Inverse of kvListToAttrs: a flat attr map → OTLP KeyValue[]. */\nexport function attrsToKvList(attrs: Attrs): OtlpKeyValue[] {\n const out: OtlpKeyValue[] = [];\n for (const [key, value] of Object.entries(attrs)) {\n if (typeof value === \"string\") out.push({ key, value: { stringValue: value } });\n else if (typeof value === \"boolean\") out.push({ key, value: { boolValue: value } });\n else if (Number.isInteger(value)) out.push({ key, value: { intValue: String(value) } });\n else out.push({ key, value: { doubleValue: value } });\n }\n return out;\n}\n\n// ---- Metrics ----\n\nexport type MetricKind = \"sum\" | \"gauge\";\n\nexport interface MetricPoint {\n name: string;\n unit?: string;\n kind: MetricKind;\n value: number;\n attrs: Attrs;\n startUnixNano: string;\n timeUnixNano: string;\n}\n\ninterface OtlpNumberDataPoint {\n attributes?: OtlpKeyValue[];\n startTimeUnixNano?: string;\n timeUnixNano: string;\n asDouble: number;\n}\n\ninterface OtlpMetric {\n name: string;\n unit?: string;\n sum?: { aggregationTemporality: number; isMonotonic: boolean; dataPoints: OtlpNumberDataPoint[] };\n gauge?: { dataPoints: OtlpNumberDataPoint[] };\n}\n\nexport interface ExportMetricsServiceRequest {\n resourceMetrics: Array<{\n resource: OtlpResource;\n scopeMetrics: Array<{ scope: OtlpScope; metrics: OtlpMetric[] }>;\n }>;\n}\n\n// OTLP AggregationTemporality: 1 = DELTA. Each ephemeral isolate reports its own\n// delta; the collector aggregates across invocations into Analytics Engine.\nconst DELTA = 1;\n\nexport function encodeMetricsRequest(\n resource: Attrs,\n scopeName: string,\n points: MetricPoint[],\n): ExportMetricsServiceRequest {\n const metrics: OtlpMetric[] = points.map((p) => {\n const dp: OtlpNumberDataPoint = {\n attributes: attrsToKvList(p.attrs),\n startTimeUnixNano: p.startUnixNano,\n timeUnixNano: p.timeUnixNano,\n asDouble: p.value,\n };\n return p.kind === \"gauge\"\n ? { name: p.name, unit: p.unit, gauge: { dataPoints: [dp] } }\n : { name: p.name, unit: p.unit, sum: { aggregationTemporality: DELTA, isMonotonic: true, dataPoints: [dp] } };\n });\n return {\n resourceMetrics: [\n { resource: { attributes: attrsToKvList(resource) }, scopeMetrics: [{ scope: { name: scopeName }, metrics }] },\n ],\n };\n}\n\n// ---- Logs (errors ride the logs signal) ----\n\nexport interface LogRecord {\n timeUnixNano: string;\n severityNumber: number; // OTel: 17 = ERROR, 9 = INFO\n severityText: string;\n body: string;\n attrs: Attrs;\n traceId?: string;\n spanId?: string;\n}\n\nexport interface ExportLogsServiceRequest {\n resourceLogs: Array<{\n resource: OtlpResource;\n scopeLogs: Array<{\n scope: OtlpScope;\n logRecords: Array<{\n timeUnixNano: string;\n observedTimeUnixNano: string;\n severityNumber: number;\n severityText: string;\n body: { stringValue: string };\n attributes: OtlpKeyValue[];\n traceId?: string;\n spanId?: string;\n }>;\n }>;\n }>;\n}\n\nexport function encodeLogsRequest(\n resource: Attrs,\n scopeName: string,\n records: LogRecord[],\n): ExportLogsServiceRequest {\n return {\n resourceLogs: [\n {\n resource: { attributes: attrsToKvList(resource) },\n scopeLogs: [\n {\n scope: { name: scopeName },\n logRecords: records.map((r) => ({\n timeUnixNano: r.timeUnixNano,\n observedTimeUnixNano: r.timeUnixNano,\n severityNumber: r.severityNumber,\n severityText: r.severityText,\n body: { stringValue: r.body },\n attributes: attrsToKvList(r.attrs),\n ...(r.traceId ? { traceId: r.traceId } : {}),\n ...(r.spanId ? { spanId: r.spanId } : {}),\n })),\n },\n ],\n },\n ],\n };\n}\n","// OTLP/JSON trace encoder — the emit-side inverse of `decodeTraces` (otlp.ts).\n// The default SDK path produces spans via @microlabs/otel-cf-workers, but that\n// library's exporter posts over the *global* fetch and can't be pointed at a\n// Cloudflare service binding. When a Worker exports through a binding instead\n// (first-party hosting, private ingest), the SDK hand-encodes its spans here so\n// the whole batch rides the same private transport as metrics and logs.\n\nimport { attrsToKvList } from \"./encode.js\";\nimport type { Attrs, ExportTraceServiceRequest, OtlpSpan } from \"./otlp.js\";\n\n/** The flat span shape the SDK hands the encoder. Field semantics mirror the\n * OTLP wire fields decoded in `decodeTraces`: `kind` is the OTLP span kind\n * (1 INTERNAL .. 5 CONSUMER) and `statusCode` is 0 UNSET / 1 OK / 2 ERROR. */\nexport interface SpanInput {\n traceId: string;\n spanId: string;\n parentSpanId: string;\n name: string;\n kind: number;\n statusCode: number;\n startUnixNano: string;\n endUnixNano: string;\n attributes: Attrs;\n}\n\n/** Encode a batch of spans (one service + scope) as an OTLP/JSON\n * ExportTraceServiceRequest — the exact shape `decodeTraces` consumes. */\nexport function encodeTraceRequest(\n resource: Attrs,\n scopeName: string,\n spans: SpanInput[],\n): ExportTraceServiceRequest {\n const otlpSpans: OtlpSpan[] = spans.map((s) => ({\n traceId: s.traceId,\n spanId: s.spanId,\n ...(s.parentSpanId ? { parentSpanId: s.parentSpanId } : {}),\n name: s.name,\n kind: s.kind,\n startTimeUnixNano: s.startUnixNano,\n endTimeUnixNano: s.endUnixNano,\n attributes: attrsToKvList(s.attributes),\n status: { code: s.statusCode },\n }));\n return {\n resourceSpans: [\n {\n resource: { attributes: attrsToKvList(resource) },\n scopeSpans: [{ scope: { name: scopeName }, spans: otlpSpans }],\n },\n ],\n };\n}\n","// LLM price table + cost computation. OTel GenAI semconv has no cost attribute,\n// so we compute cost downstream from raw token counts × model price. Keep this\n// table in sync with current model pricing (verify against the claude-api\n// reference); unknown models cost 0 and should be added here.\n\nexport interface ModelPrice {\n /** USD per million input tokens. */\n inputPerMTok: number;\n /** USD per million output tokens. */\n outputPerMTok: number;\n}\n\n/** Prices in USD per million tokens (MTok). */\nexport const MODEL_PRICING: Record<string, ModelPrice> = {\n \"claude-haiku-4-5\": { inputPerMTok: 1, outputPerMTok: 5 },\n \"claude-sonnet-4-6\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-sonnet-5\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-opus-4-6\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-7\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-8\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-fable-5\": { inputPerMTok: 10, outputPerMTok: 50 },\n};\n\n/** Compute USD cost for a call. Pass `override` to price a model not in the\n * table (or to apply promotional pricing). Returns 0 for unknown models. */\nexport function costUsd(\n model: string,\n inputTokens: number,\n outputTokens: number,\n override?: ModelPrice,\n): number {\n const price = override ?? MODEL_PRICING[model];\n if (!price) return 0;\n return (inputTokens / 1_000_000) * price.inputPerMTok + (outputTokens / 1_000_000) * price.outputPerMTok;\n}\n","import type { ExportResult } from \"@opentelemetry/core\";\nimport type { ReadableSpan, SpanExporter } from \"@opentelemetry/sdk-trace-base\";\nimport { encodeTraceRequest, type Attrs, type SpanInput } from \"@odla/o11y-core\";\nimport type { CollectorBinding } from \"./context.js\";\n\n// ExportResultCode: 0 = SUCCESS, 1 = FAILED (@opentelemetry/core). Kept as\n// literals so we don't take a runtime dependency on the enum.\nconst SUCCESS = 0;\nconst FAILED = 1;\n\n// OTLP span kind is the OpenTelemetry SDK `SpanKind` (INTERNAL=0 .. CONSUMER=4)\n// shifted by one (OTLP INTERNAL=1 .. CONSUMER=5).\nconst OTLP_KIND_OFFSET = 1;\n\n/** OTel `HrTime` ([seconds, nanos]) → a decimal-nanosecond string (OTLP). */\nfunction hrToNano([seconds, nanos]: [number, number]): string {\n return (BigInt(seconds) * 1_000_000_000n + BigInt(nanos)).toString();\n}\n\n/** Project an OTel ReadableSpan onto the flat shape the core encoder wants,\n * dropping non-primitive attribute values (arrays/objects) the wire can't hold. */\nexport function toSpanInput(span: ReadableSpan): SpanInput {\n const ctx = span.spanContext();\n const attributes: Attrs = {};\n for (const [key, value] of Object.entries(span.attributes)) {\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n attributes[key] = value;\n }\n }\n return {\n traceId: ctx.traceId,\n spanId: ctx.spanId,\n parentSpanId: span.parentSpanContext?.spanId ?? \"\",\n name: span.name,\n kind: span.kind + OTLP_KIND_OFFSET,\n statusCode: span.status.code,\n startUnixNano: hrToNano(span.startTime),\n endUnixNano: hrToNano(span.endTime),\n attributes,\n };\n}\n\n/** A SpanExporter that ships OTLP/JSON spans through a Cloudflare service\n * binding instead of the global `fetch` the stock OTLP exporter uses — so\n * traces travel the same private worker-to-worker path as metrics and logs. */\nexport class BindingSpanExporter implements SpanExporter {\n constructor(\n private readonly binding: CollectorBinding,\n private readonly url: string,\n private readonly headers: Record<string, string>,\n private readonly resource: Attrs,\n private readonly scope: string,\n ) {}\n\n export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {\n const body = encodeTraceRequest(this.resource, this.scope, spans.map(toSpanInput));\n this.binding\n .fetch(this.url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", ...this.headers },\n body: JSON.stringify(body),\n })\n .then(() => resultCallback({ code: SUCCESS }))\n .catch((error: Error) => resultCallback({ code: FAILED, error }));\n }\n\n async shutdown(): Promise<void> {}\n}\n","import { SpanKind, SpanStatusCode, trace, type Span } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { SCOPE } from \"./context.js\";\n\nexport type SpanKindName = \"internal\" | \"server\" | \"client\" | \"producer\" | \"consumer\";\n\nexport interface SpanOpts {\n attributes?: Attrs;\n kind?: SpanKindName;\n}\n\nconst KIND: Record<SpanKindName, SpanKind> = {\n internal: SpanKind.INTERNAL,\n server: SpanKind.SERVER,\n client: SpanKind.CLIENT,\n producer: SpanKind.PRODUCER,\n consumer: SpanKind.CONSUMER,\n};\n\n/** Run `fn` inside a new active span. The span is ended automatically and its\n * status set from success/throw. Works whether or not tracing is exporting —\n * falls back to a no-op span if no tracer is installed. */\nexport async function span<T>(\n name: string,\n fn: (span: Span) => Promise<T> | T,\n opts?: SpanOpts,\n): Promise<T> {\n const tracer = trace.getTracer(SCOPE);\n return tracer.startActiveSpan(\n name,\n { attributes: opts?.attributes, kind: opts?.kind ? KIND[opts.kind] : undefined },\n async (s) => {\n try {\n const result = await fn(s);\n s.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (err) {\n s.setStatus({ code: SpanStatusCode.ERROR, message: err instanceof Error ? err.message : String(err) });\n throw err;\n } finally {\n s.end();\n }\n },\n );\n}\n","import type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface Metrics {\n /** Monotonic counter (delta). Formalizes odla-db's usage counters. */\n count(name: string, by?: number, attrs?: Attrs): void;\n /** Last-value gauge. */\n gauge(name: string, value: number, attrs?: Attrs): void;\n /** A single observation (recorded as a delta sum for now; histograms later). */\n histogram(name: string, value: number, attrs?: Attrs, unit?: string): void;\n}\n\nfunction record(kind: \"sum\" | \"gauge\", name: string, value: number, attrs?: Attrs, unit?: string): void {\n const sink = currentSink();\n if (!sink) return; // outside an instrumented invocation — no-op\n const ts = nowNano();\n sink.metrics.push({ name, unit, kind, value, attrs: attrs ?? {}, startUnixNano: ts, timeUnixNano: ts });\n}\n\n/** The ambient metrics API for the current invocation. */\nexport function metrics(): Metrics {\n return {\n count: (name, by = 1, attrs) => record(\"sum\", name, by, attrs),\n gauge: (name, value, attrs) => record(\"gauge\", name, value, attrs),\n histogram: (name, value, attrs, unit) => record(\"sum\", name, value, attrs, unit),\n };\n}\n\n/** Convenience: increment a monotonic counter by `by` (default 1). */\nexport function count(name: string, by = 1, attrs?: Attrs): void {\n record(\"sum\", name, by, attrs);\n}\n","import { SpanStatusCode, trace } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface ErrorReport {\n /** Stable, low-cardinality error code (e.g. \"unique_violation\"). */\n code?: string;\n /** Low-cardinality route/step where it happened. */\n route?: string;\n /** Extra low-cardinality attributes for the index. */\n attributes?: Attrs;\n /** Rich context for triage — kept only in the R2 artifact bundle. */\n artifacts?: Record<string, unknown>;\n /** Dedup key; the collector fingerprints by (type+code+route) if absent. */\n fingerprint?: string;\n}\n\n// OTel severity number for ERROR.\nconst SEVERITY_ERROR = 17;\n\n/** Record a structured error. Returns an artifact id. The message and stack ride\n * the log body + a denied attribute so the collector persists them ONLY in the\n * R2 artifact bundle, never in metrics or the queryable index. */\nexport function recordError(err: unknown, report?: ErrorReport): string {\n const error = err instanceof Error ? err : new Error(String(err));\n const artifactId = `${Date.now().toString(16)}${crypto.randomUUID().replace(/-/g, \"\")}`;\n const span = trace.getActiveSpan();\n\n const attrs: Attrs = {\n \"error.type\": error.name,\n \"odla.artifact_id\": artifactId,\n ...(report?.code ? { \"error.code\": report.code } : {}),\n ...(report?.route ? { \"odla.route\": report.route } : {}),\n ...(report?.fingerprint ? { \"odla.fingerprint\": report.fingerprint } : {}),\n ...(report?.attributes ?? {}),\n };\n\n if (span) {\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });\n span.setAttributes(attrs);\n }\n\n const sink = currentSink();\n if (sink) {\n const ctx = span?.spanContext();\n sink.logs.push({\n timeUnixNano: nowNano(),\n severityNumber: SEVERITY_ERROR,\n severityText: \"ERROR\",\n body: error.message, // full message → R2 bundle only (collector-enforced)\n attrs: {\n ...attrs,\n // Denied keys: stripped from metrics/index, retained in the R2 bundle.\n ...(error.stack ? { \"exception.stacktrace\": error.stack } : {}),\n ...(report?.artifacts ? { \"odla.artifacts\": JSON.stringify(report.artifacts) } : {}),\n },\n ...(ctx ? { traceId: ctx.traceId, spanId: ctx.spanId } : {}),\n });\n }\n\n return artifactId;\n}\n","import { trace } from \"@opentelemetry/api\";\nimport { costUsd, type Attrs, type ModelPrice } from \"@odla/o11y-core\";\nimport { count } from \"./metrics.js\";\n\n/** The token accounting odla-kg's Provider already returns (and drops today). */\nexport interface LlmUsage {\n calls: number;\n inputTokens: number;\n outputTokens: number;\n}\n\nexport interface LlmCostOpts {\n provider: string; // \"claude\" | \"openai\" | ...\n model: string; // e.g. \"claude-sonnet-4-6\"\n operation?: string; // \"extract\" | \"search\" | ...\n attributes?: Attrs;\n /** Override the built-in price table for this call. */\n price?: ModelPrice;\n}\n\n/** Turn LLM token usage into first-class cost. Emits cost/token/call counters\n * and stamps the active span with GenAI semconv attributes. */\nexport function recordLlmUsage(usage: LlmUsage, opts: LlmCostOpts): { costUsd: number } {\n const cost = costUsd(opts.model, usage.inputTokens, usage.outputTokens, opts.price);\n const base: Attrs = {\n \"gen_ai.provider.name\": opts.provider,\n \"gen_ai.request.model\": opts.model,\n ...(opts.operation ? { \"gen_ai.operation.name\": opts.operation } : {}),\n ...(opts.attributes ?? {}),\n };\n\n count(\"odla.llm.cost.usd\", cost, base);\n count(\"odla.llm.calls\", usage.calls, base);\n count(\"odla.llm.tokens\", usage.inputTokens, { ...base, \"gen_ai.token.type\": \"input\" });\n count(\"odla.llm.tokens\", usage.outputTokens, { ...base, \"gen_ai.token.type\": \"output\" });\n\n trace.getActiveSpan()?.setAttributes({\n ...base,\n \"gen_ai.usage.input_tokens\": usage.inputTokens,\n \"gen_ai.usage.output_tokens\": usage.outputTokens,\n \"odla.llm.cost.usd\": cost,\n });\n\n return { costUsd: cost };\n}\n"],"mappings":"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","names":["metrics","span","import_api","span","import_api"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -2,6 +2,13 @@ import { Attrs, ModelPrice } from '@odla/o11y-core';
|
|
|
2
2
|
export { Attrs } from '@odla/o11y-core';
|
|
3
3
|
import { Span } from '@opentelemetry/api';
|
|
4
4
|
|
|
5
|
+
/** A Cloudflare service binding to the collector Worker (structural — avoids a
|
|
6
|
+
* hard dependency on `@cloudflare/workers-types`). When present the SDK exports
|
|
7
|
+
* every signal through `binding.fetch` (private worker-to-worker) instead of
|
|
8
|
+
* the public endpoint; first-party hosting injects it as `env.ODLA_O11Y_COLLECTOR`. */
|
|
9
|
+
interface CollectorBinding {
|
|
10
|
+
fetch: typeof fetch;
|
|
11
|
+
}
|
|
5
12
|
interface ObservabilityOptions {
|
|
6
13
|
/** Service name (default: env.ODLA_O11Y_SERVICE). */
|
|
7
14
|
service?: string;
|
|
@@ -15,6 +22,9 @@ interface ObservabilityOptions {
|
|
|
15
22
|
sampleRatio?: number;
|
|
16
23
|
/** Static resource attributes merged onto every signal. */
|
|
17
24
|
attributes?: Attrs;
|
|
25
|
+
/** Service binding to the collector. Defaults to an `ODLA_O11Y_COLLECTOR`
|
|
26
|
+
* binding on env; when set, all signals export through it instead of `fetch`. */
|
|
27
|
+
fetcher?: CollectorBinding;
|
|
18
28
|
}
|
|
19
29
|
type OptsFn<E> = ObservabilityOptions | ((env: E) => ObservabilityOptions);
|
|
20
30
|
|
|
@@ -86,4 +96,4 @@ declare function recordLlmUsage(usage: LlmUsage, opts: LlmCostOpts): {
|
|
|
86
96
|
costUsd: number;
|
|
87
97
|
};
|
|
88
98
|
|
|
89
|
-
export { type ErrorReport, type LlmCostOpts, type LlmUsage, type Metrics, type ObservabilityOptions, type OptsFn, type SpanKindName, type SpanOpts, count, instrumentDurableObject, metrics, recordError, recordLlmUsage, span, withObservability };
|
|
99
|
+
export { type CollectorBinding, type ErrorReport, type LlmCostOpts, type LlmUsage, type Metrics, type ObservabilityOptions, type OptsFn, type SpanKindName, type SpanOpts, count, instrumentDurableObject, metrics, recordError, recordLlmUsage, span, withObservability };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,13 @@ import { Attrs, ModelPrice } from '@odla/o11y-core';
|
|
|
2
2
|
export { Attrs } from '@odla/o11y-core';
|
|
3
3
|
import { Span } from '@opentelemetry/api';
|
|
4
4
|
|
|
5
|
+
/** A Cloudflare service binding to the collector Worker (structural — avoids a
|
|
6
|
+
* hard dependency on `@cloudflare/workers-types`). When present the SDK exports
|
|
7
|
+
* every signal through `binding.fetch` (private worker-to-worker) instead of
|
|
8
|
+
* the public endpoint; first-party hosting injects it as `env.ODLA_O11Y_COLLECTOR`. */
|
|
9
|
+
interface CollectorBinding {
|
|
10
|
+
fetch: typeof fetch;
|
|
11
|
+
}
|
|
5
12
|
interface ObservabilityOptions {
|
|
6
13
|
/** Service name (default: env.ODLA_O11Y_SERVICE). */
|
|
7
14
|
service?: string;
|
|
@@ -15,6 +22,9 @@ interface ObservabilityOptions {
|
|
|
15
22
|
sampleRatio?: number;
|
|
16
23
|
/** Static resource attributes merged onto every signal. */
|
|
17
24
|
attributes?: Attrs;
|
|
25
|
+
/** Service binding to the collector. Defaults to an `ODLA_O11Y_COLLECTOR`
|
|
26
|
+
* binding on env; when set, all signals export through it instead of `fetch`. */
|
|
27
|
+
fetcher?: CollectorBinding;
|
|
18
28
|
}
|
|
19
29
|
type OptsFn<E> = ObservabilityOptions | ((env: E) => ObservabilityOptions);
|
|
20
30
|
|
|
@@ -86,4 +96,4 @@ declare function recordLlmUsage(usage: LlmUsage, opts: LlmCostOpts): {
|
|
|
86
96
|
costUsd: number;
|
|
87
97
|
};
|
|
88
98
|
|
|
89
|
-
export { type ErrorReport, type LlmCostOpts, type LlmUsage, type Metrics, type ObservabilityOptions, type OptsFn, type SpanKindName, type SpanOpts, count, instrumentDurableObject, metrics, recordError, recordLlmUsage, span, withObservability };
|
|
99
|
+
export { type CollectorBinding, type ErrorReport, type LlmCostOpts, type LlmUsage, type Metrics, type ObservabilityOptions, type OptsFn, type SpanKindName, type SpanOpts, count, instrumentDurableObject, metrics, recordError, recordLlmUsage, span, withObservability };
|
package/dist/index.js
CHANGED
|
@@ -57,6 +57,29 @@ function encodeLogsRequest(resource, scopeName, records) {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// ../o11y-core/src/encode-trace.ts
|
|
61
|
+
function encodeTraceRequest(resource, scopeName, spans) {
|
|
62
|
+
const otlpSpans = spans.map((s) => ({
|
|
63
|
+
traceId: s.traceId,
|
|
64
|
+
spanId: s.spanId,
|
|
65
|
+
...s.parentSpanId ? { parentSpanId: s.parentSpanId } : {},
|
|
66
|
+
name: s.name,
|
|
67
|
+
kind: s.kind,
|
|
68
|
+
startTimeUnixNano: s.startUnixNano,
|
|
69
|
+
endTimeUnixNano: s.endUnixNano,
|
|
70
|
+
attributes: attrsToKvList(s.attributes),
|
|
71
|
+
status: { code: s.statusCode }
|
|
72
|
+
}));
|
|
73
|
+
return {
|
|
74
|
+
resourceSpans: [
|
|
75
|
+
{
|
|
76
|
+
resource: { attributes: attrsToKvList(resource) },
|
|
77
|
+
scopeSpans: [{ scope: { name: scopeName }, spans: otlpSpans }]
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
60
83
|
// ../o11y-core/src/pricing.ts
|
|
61
84
|
var MODEL_PRICING = {
|
|
62
85
|
"claude-haiku-4-5": { inputPerMTok: 1, outputPerMTok: 5 },
|
|
@@ -75,19 +98,25 @@ function costUsd(model, inputTokens, outputTokens, override) {
|
|
|
75
98
|
|
|
76
99
|
// src/context.ts
|
|
77
100
|
var SCOPE = "@odla-ai/o11y";
|
|
101
|
+
function asBinding(x) {
|
|
102
|
+
return x && typeof x.fetch === "function" ? x : void 0;
|
|
103
|
+
}
|
|
78
104
|
function evalOpts(opts, env) {
|
|
79
105
|
return typeof opts === "function" ? opts(env) : opts ?? {};
|
|
80
106
|
}
|
|
81
107
|
function resolveConfig(env, opts) {
|
|
82
108
|
const o = evalOpts(opts, env);
|
|
83
109
|
const e = env;
|
|
110
|
+
const fetcher = o.fetcher ?? asBinding(env["ODLA_O11Y_COLLECTOR"]);
|
|
111
|
+
const endpoint = (o.endpoint ?? e["ODLA_O11Y_ENDPOINT"] ?? (fetcher ? "https://svc.internal" : "")).replace(/\/$/, "");
|
|
84
112
|
return {
|
|
85
113
|
service: o.service ?? e["ODLA_O11Y_SERVICE"] ?? "unknown",
|
|
86
|
-
endpoint
|
|
114
|
+
endpoint,
|
|
87
115
|
token: o.token ?? e["ODLA_O11Y_TOKEN"],
|
|
88
116
|
version: o.version ?? e["ODLA_O11Y_VERSION"] ?? "0.0.0",
|
|
89
117
|
attributes: o.attributes ?? {},
|
|
90
|
-
sampleRatio: o.sampleRatio
|
|
118
|
+
sampleRatio: o.sampleRatio,
|
|
119
|
+
fetcher
|
|
91
120
|
};
|
|
92
121
|
}
|
|
93
122
|
function nowNano() {
|
|
@@ -103,16 +132,17 @@ function runWithSink(sink, fn) {
|
|
|
103
132
|
function resourceAttrs(config) {
|
|
104
133
|
return { "service.name": config.service, "service.version": config.version, ...config.attributes };
|
|
105
134
|
}
|
|
106
|
-
async function post(url, body, token) {
|
|
135
|
+
async function post(url, body, token, fetcher) {
|
|
136
|
+
const init = {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: {
|
|
139
|
+
"content-type": "application/json",
|
|
140
|
+
...token ? { authorization: `Bearer ${token}` } : {}
|
|
141
|
+
},
|
|
142
|
+
body: JSON.stringify(body)
|
|
143
|
+
};
|
|
107
144
|
try {
|
|
108
|
-
await fetch(url,
|
|
109
|
-
method: "POST",
|
|
110
|
-
headers: {
|
|
111
|
-
"content-type": "application/json",
|
|
112
|
-
...token ? { authorization: `Bearer ${token}` } : {}
|
|
113
|
-
},
|
|
114
|
-
body: JSON.stringify(body)
|
|
115
|
-
});
|
|
145
|
+
await (fetcher ? fetcher.fetch(url, init) : fetch(url, init));
|
|
116
146
|
} catch {
|
|
117
147
|
}
|
|
118
148
|
}
|
|
@@ -129,15 +159,16 @@ function createSink(config) {
|
|
|
129
159
|
}
|
|
130
160
|
const resource = resourceAttrs(this.config);
|
|
131
161
|
const jobs = [];
|
|
162
|
+
const { token, fetcher } = this.config;
|
|
132
163
|
if (this.metrics.length) {
|
|
133
164
|
jobs.push(
|
|
134
|
-
post(`${this.config.endpoint}/v1/metrics`, encodeMetricsRequest(resource, SCOPE, this.metrics),
|
|
165
|
+
post(`${this.config.endpoint}/v1/metrics`, encodeMetricsRequest(resource, SCOPE, this.metrics), token, fetcher)
|
|
135
166
|
);
|
|
136
167
|
this.metrics = [];
|
|
137
168
|
}
|
|
138
169
|
if (this.logs.length) {
|
|
139
170
|
jobs.push(
|
|
140
|
-
post(`${this.config.endpoint}/v1/logs`, encodeLogsRequest(resource, SCOPE, this.logs),
|
|
171
|
+
post(`${this.config.endpoint}/v1/logs`, encodeLogsRequest(resource, SCOPE, this.logs), token, fetcher)
|
|
141
172
|
);
|
|
142
173
|
this.logs = [];
|
|
143
174
|
}
|
|
@@ -146,13 +177,68 @@ function createSink(config) {
|
|
|
146
177
|
};
|
|
147
178
|
}
|
|
148
179
|
|
|
180
|
+
// src/trace-export.ts
|
|
181
|
+
var SUCCESS = 0;
|
|
182
|
+
var FAILED = 1;
|
|
183
|
+
var OTLP_KIND_OFFSET = 1;
|
|
184
|
+
function hrToNano([seconds, nanos]) {
|
|
185
|
+
return (BigInt(seconds) * 1000000000n + BigInt(nanos)).toString();
|
|
186
|
+
}
|
|
187
|
+
function toSpanInput(span2) {
|
|
188
|
+
const ctx = span2.spanContext();
|
|
189
|
+
const attributes = {};
|
|
190
|
+
for (const [key, value] of Object.entries(span2.attributes)) {
|
|
191
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
192
|
+
attributes[key] = value;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
traceId: ctx.traceId,
|
|
197
|
+
spanId: ctx.spanId,
|
|
198
|
+
parentSpanId: span2.parentSpanContext?.spanId ?? "",
|
|
199
|
+
name: span2.name,
|
|
200
|
+
kind: span2.kind + OTLP_KIND_OFFSET,
|
|
201
|
+
statusCode: span2.status.code,
|
|
202
|
+
startUnixNano: hrToNano(span2.startTime),
|
|
203
|
+
endUnixNano: hrToNano(span2.endTime),
|
|
204
|
+
attributes
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
var BindingSpanExporter = class {
|
|
208
|
+
constructor(binding, url, headers, resource, scope) {
|
|
209
|
+
this.binding = binding;
|
|
210
|
+
this.url = url;
|
|
211
|
+
this.headers = headers;
|
|
212
|
+
this.resource = resource;
|
|
213
|
+
this.scope = scope;
|
|
214
|
+
}
|
|
215
|
+
binding;
|
|
216
|
+
url;
|
|
217
|
+
headers;
|
|
218
|
+
resource;
|
|
219
|
+
scope;
|
|
220
|
+
export(spans, resultCallback) {
|
|
221
|
+
const body = encodeTraceRequest(this.resource, this.scope, spans.map(toSpanInput));
|
|
222
|
+
this.binding.fetch(this.url, {
|
|
223
|
+
method: "POST",
|
|
224
|
+
headers: { "content-type": "application/json", ...this.headers },
|
|
225
|
+
body: JSON.stringify(body)
|
|
226
|
+
}).then(() => resultCallback({ code: SUCCESS })).catch((error) => resultCallback({ code: FAILED, error }));
|
|
227
|
+
}
|
|
228
|
+
async shutdown() {
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
149
232
|
// src/instrument.ts
|
|
150
233
|
function traceConfig(env, opts) {
|
|
151
234
|
const c = resolveConfig(env, opts);
|
|
152
235
|
const headers = {};
|
|
153
236
|
if (c.token) headers.authorization = `Bearer ${c.token}`;
|
|
237
|
+
const url = `${c.endpoint}/v1/traces`;
|
|
154
238
|
const config = {
|
|
155
|
-
exporter
|
|
239
|
+
// A service binding needs a custom exporter (the stock OTLP exporter posts
|
|
240
|
+
// over the global fetch); without one, fall back to the public URL exporter.
|
|
241
|
+
exporter: c.fetcher ? new BindingSpanExporter(c.fetcher, url, headers, resourceAttrs(c), SCOPE) : { url, headers },
|
|
156
242
|
service: { name: c.service, version: c.version }
|
|
157
243
|
};
|
|
158
244
|
if (c.sampleRatio != null) config.sampling = { headSampler: { ratio: c.sampleRatio } };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/instrument.ts","../src/context.ts","../../o11y-core/src/encode.ts","../../o11y-core/src/pricing.ts","../src/span.ts","../src/metrics.ts","../src/logs.ts","../src/llm.ts"],"sourcesContent":["import { instrument, instrumentDO, type ResolveConfigFn, type TraceConfig } from \"@microlabs/otel-cf-workers\";\nimport { createSink, resolveConfig, runWithSink, type OptsFn } from \"./context.js\";\n\n/** Build the otel-cf-workers trace config (exporter + service) from env/opts. */\nfunction traceConfig<E>(env: E, opts?: OptsFn<E>): TraceConfig {\n const c = resolveConfig(env, opts);\n const headers: Record<string, string> = {};\n if (c.token) headers.authorization = `Bearer ${c.token}`;\n const config: TraceConfig = {\n exporter: { url: `${c.endpoint}/v1/traces`, headers },\n service: { name: c.service, version: c.version },\n };\n if (c.sampleRatio != null) config.sampling = { headSampler: { ratio: c.sampleRatio } };\n return config;\n}\n\n/** Wrap each handler entrypoint so an ALS metrics/logs sink is live for the\n * duration of the invocation and flushed via ctx.waitUntil at the end. */\nfunction wrapHandler<E>(handler: ExportedHandler<E>, opts?: OptsFn<E>): ExportedHandler<E> {\n const wrapped: ExportedHandler<E> = { ...handler };\n\n const fetchImpl = handler.fetch;\n if (fetchImpl) {\n wrapped.fetch = (req, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await fetchImpl(req, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n const scheduledImpl = handler.scheduled;\n if (scheduledImpl) {\n wrapped.scheduled = (controller, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await scheduledImpl(controller, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n return wrapped;\n}\n\n/** Wrap a Worker handler with tracing (otel-cf-workers) + the metrics/logs sink.\n * Config is read from env vars ODLA_O11Y_{ENDPOINT,SERVICE,TOKEN,VERSION} unless\n * overridden by `opts`. */\nexport function withObservability<E = unknown>(\n handler: ExportedHandler<E>,\n opts?: OptsFn<E>,\n): ExportedHandler<E> {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env as E, opts);\n // instrument constrains E to its own Env type; we support any service env, so\n // cross the boundary with `any` and restore the caller's E on the way out.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrument(wrapHandler(handler, opts) as ExportedHandler<any>, configFn) as ExportedHandler<E>;\n}\n\n/** Wrap a Durable Object class with tracing. (Establishing the metrics/logs sink\n * inside DO methods is added in P1 when odla-db's usage counters are bridged.) */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function instrumentDurableObject<T extends abstract new (...args: any[]) => object>(\n cls: T,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n opts?: OptsFn<any>,\n): T {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env, opts);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrumentDO(cls as any, configFn) as unknown as T;\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport {\n encodeLogsRequest,\n encodeMetricsRequest,\n type Attrs,\n type LogRecord,\n type MetricPoint,\n} from \"@odla/o11y-core\";\n\n/** The instrumentation scope name stamped on every signal. */\nexport const SCOPE = \"@odla-ai/o11y\";\n\nexport interface ObservabilityOptions {\n /** Service name (default: env.ODLA_O11Y_SERVICE). */\n service?: string;\n /** Collector base URL (default: env.ODLA_O11Y_ENDPOINT). */\n endpoint?: string;\n /** Per-service bearer token (default: env.ODLA_O11Y_TOKEN). */\n token?: string;\n /** Release/version tag (default: env.ODLA_O11Y_VERSION ?? \"0.0.0\"). */\n version?: string;\n /** Head-sampling ratio 0..1 for traces (default: 1). */\n sampleRatio?: number;\n /** Static resource attributes merged onto every signal. */\n attributes?: Attrs;\n}\n\nexport type OptsFn<E> = ObservabilityOptions | ((env: E) => ObservabilityOptions);\n\nexport interface ResolvedConfig {\n service: string;\n endpoint: string;\n token?: string;\n version: string;\n attributes: Attrs;\n sampleRatio?: number;\n}\n\nfunction evalOpts<E>(opts: OptsFn<E> | undefined, env: E): ObservabilityOptions {\n return typeof opts === \"function\" ? opts(env) : (opts ?? {});\n}\n\n/** Resolve options against the service's env vars. */\nexport function resolveConfig<E>(env: E, opts?: OptsFn<E>): ResolvedConfig {\n const o = evalOpts(opts, env);\n const e = env as Record<string, string | undefined>;\n return {\n service: o.service ?? e[\"ODLA_O11Y_SERVICE\"] ?? \"unknown\",\n endpoint: (o.endpoint ?? e[\"ODLA_O11Y_ENDPOINT\"] ?? \"\").replace(/\\/$/, \"\"),\n token: o.token ?? e[\"ODLA_O11Y_TOKEN\"],\n version: o.version ?? e[\"ODLA_O11Y_VERSION\"] ?? \"0.0.0\",\n attributes: o.attributes ?? {},\n sampleRatio: o.sampleRatio,\n };\n}\n\n/** Nanoseconds since epoch as a decimal string (OTLP timestamp format). */\nexport function nowNano(): string {\n return `${Date.now()}000000`;\n}\n\nexport interface Sink {\n config: ResolvedConfig;\n metrics: MetricPoint[];\n logs: LogRecord[];\n flush(): Promise<void>;\n}\n\nconst als = new AsyncLocalStorage<Sink>();\n\nexport function currentSink(): Sink | undefined {\n return als.getStore();\n}\n\nexport function runWithSink<T>(sink: Sink, fn: () => Promise<T>): Promise<T> {\n return als.run(sink, fn);\n}\n\nfunction resourceAttrs(config: ResolvedConfig): Attrs {\n return { \"service.name\": config.service, \"service.version\": config.version, ...config.attributes };\n}\n\nasync function post(url: string, body: unknown, token?: string): Promise<void> {\n try {\n await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(token ? { authorization: `Bearer ${token}` } : {}),\n },\n body: JSON.stringify(body),\n });\n } catch {\n // Telemetry export is best-effort — never surface into the app.\n }\n}\n\n/** A per-invocation sink: accumulates metric/log records and flushes them to the\n * collector as OTLP/JSON (traces are exported separately by otel-cf-workers). */\nexport function createSink(config: ResolvedConfig): Sink {\n return {\n config,\n metrics: [],\n logs: [],\n async flush(): Promise<void> {\n if (!this.config.endpoint) {\n this.metrics = [];\n this.logs = [];\n return;\n }\n const resource = resourceAttrs(this.config);\n const jobs: Promise<void>[] = [];\n if (this.metrics.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/metrics`, encodeMetricsRequest(resource, SCOPE, this.metrics), this.config.token),\n );\n this.metrics = [];\n }\n if (this.logs.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/logs`, encodeLogsRequest(resource, SCOPE, this.logs), this.config.token),\n );\n this.logs = [];\n }\n await Promise.all(jobs);\n },\n };\n}\n","// OTLP/JSON encoders — the emit side of the wire contract (the SDK produces\n// these; the collector decodes them in later phases). Traces are produced by\n// @microlabs/otel-cf-workers; metrics and logs are hand-rolled here because that\n// library is traces-only and the Node OTel metrics SDK doesn't fit the isolate\n// model.\n\nimport type { Attrs, OtlpKeyValue, OtlpResource, OtlpScope } from \"./otlp.js\";\n\n/** Inverse of kvListToAttrs: a flat attr map → OTLP KeyValue[]. */\nexport function attrsToKvList(attrs: Attrs): OtlpKeyValue[] {\n const out: OtlpKeyValue[] = [];\n for (const [key, value] of Object.entries(attrs)) {\n if (typeof value === \"string\") out.push({ key, value: { stringValue: value } });\n else if (typeof value === \"boolean\") out.push({ key, value: { boolValue: value } });\n else if (Number.isInteger(value)) out.push({ key, value: { intValue: String(value) } });\n else out.push({ key, value: { doubleValue: value } });\n }\n return out;\n}\n\n// ---- Metrics ----\n\nexport type MetricKind = \"sum\" | \"gauge\";\n\nexport interface MetricPoint {\n name: string;\n unit?: string;\n kind: MetricKind;\n value: number;\n attrs: Attrs;\n startUnixNano: string;\n timeUnixNano: string;\n}\n\ninterface OtlpNumberDataPoint {\n attributes?: OtlpKeyValue[];\n startTimeUnixNano?: string;\n timeUnixNano: string;\n asDouble: number;\n}\n\ninterface OtlpMetric {\n name: string;\n unit?: string;\n sum?: { aggregationTemporality: number; isMonotonic: boolean; dataPoints: OtlpNumberDataPoint[] };\n gauge?: { dataPoints: OtlpNumberDataPoint[] };\n}\n\nexport interface ExportMetricsServiceRequest {\n resourceMetrics: Array<{\n resource: OtlpResource;\n scopeMetrics: Array<{ scope: OtlpScope; metrics: OtlpMetric[] }>;\n }>;\n}\n\n// OTLP AggregationTemporality: 1 = DELTA. Each ephemeral isolate reports its own\n// delta; the collector aggregates across invocations into Analytics Engine.\nconst DELTA = 1;\n\nexport function encodeMetricsRequest(\n resource: Attrs,\n scopeName: string,\n points: MetricPoint[],\n): ExportMetricsServiceRequest {\n const metrics: OtlpMetric[] = points.map((p) => {\n const dp: OtlpNumberDataPoint = {\n attributes: attrsToKvList(p.attrs),\n startTimeUnixNano: p.startUnixNano,\n timeUnixNano: p.timeUnixNano,\n asDouble: p.value,\n };\n return p.kind === \"gauge\"\n ? { name: p.name, unit: p.unit, gauge: { dataPoints: [dp] } }\n : { name: p.name, unit: p.unit, sum: { aggregationTemporality: DELTA, isMonotonic: true, dataPoints: [dp] } };\n });\n return {\n resourceMetrics: [\n { resource: { attributes: attrsToKvList(resource) }, scopeMetrics: [{ scope: { name: scopeName }, metrics }] },\n ],\n };\n}\n\n// ---- Logs (errors ride the logs signal) ----\n\nexport interface LogRecord {\n timeUnixNano: string;\n severityNumber: number; // OTel: 17 = ERROR, 9 = INFO\n severityText: string;\n body: string;\n attrs: Attrs;\n traceId?: string;\n spanId?: string;\n}\n\nexport interface ExportLogsServiceRequest {\n resourceLogs: Array<{\n resource: OtlpResource;\n scopeLogs: Array<{\n scope: OtlpScope;\n logRecords: Array<{\n timeUnixNano: string;\n observedTimeUnixNano: string;\n severityNumber: number;\n severityText: string;\n body: { stringValue: string };\n attributes: OtlpKeyValue[];\n traceId?: string;\n spanId?: string;\n }>;\n }>;\n }>;\n}\n\nexport function encodeLogsRequest(\n resource: Attrs,\n scopeName: string,\n records: LogRecord[],\n): ExportLogsServiceRequest {\n return {\n resourceLogs: [\n {\n resource: { attributes: attrsToKvList(resource) },\n scopeLogs: [\n {\n scope: { name: scopeName },\n logRecords: records.map((r) => ({\n timeUnixNano: r.timeUnixNano,\n observedTimeUnixNano: r.timeUnixNano,\n severityNumber: r.severityNumber,\n severityText: r.severityText,\n body: { stringValue: r.body },\n attributes: attrsToKvList(r.attrs),\n ...(r.traceId ? { traceId: r.traceId } : {}),\n ...(r.spanId ? { spanId: r.spanId } : {}),\n })),\n },\n ],\n },\n ],\n };\n}\n","// LLM price table + cost computation. OTel GenAI semconv has no cost attribute,\n// so we compute cost downstream from raw token counts × model price. Keep this\n// table in sync with current model pricing (verify against the claude-api\n// reference); unknown models cost 0 and should be added here.\n\nexport interface ModelPrice {\n /** USD per million input tokens. */\n inputPerMTok: number;\n /** USD per million output tokens. */\n outputPerMTok: number;\n}\n\n/** Prices in USD per million tokens (MTok). */\nexport const MODEL_PRICING: Record<string, ModelPrice> = {\n \"claude-haiku-4-5\": { inputPerMTok: 1, outputPerMTok: 5 },\n \"claude-sonnet-4-6\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-sonnet-5\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-opus-4-6\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-7\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-8\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-fable-5\": { inputPerMTok: 10, outputPerMTok: 50 },\n};\n\n/** Compute USD cost for a call. Pass `override` to price a model not in the\n * table (or to apply promotional pricing). Returns 0 for unknown models. */\nexport function costUsd(\n model: string,\n inputTokens: number,\n outputTokens: number,\n override?: ModelPrice,\n): number {\n const price = override ?? MODEL_PRICING[model];\n if (!price) return 0;\n return (inputTokens / 1_000_000) * price.inputPerMTok + (outputTokens / 1_000_000) * price.outputPerMTok;\n}\n","import { SpanKind, SpanStatusCode, trace, type Span } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { SCOPE } from \"./context.js\";\n\nexport type SpanKindName = \"internal\" | \"server\" | \"client\" | \"producer\" | \"consumer\";\n\nexport interface SpanOpts {\n attributes?: Attrs;\n kind?: SpanKindName;\n}\n\nconst KIND: Record<SpanKindName, SpanKind> = {\n internal: SpanKind.INTERNAL,\n server: SpanKind.SERVER,\n client: SpanKind.CLIENT,\n producer: SpanKind.PRODUCER,\n consumer: SpanKind.CONSUMER,\n};\n\n/** Run `fn` inside a new active span. The span is ended automatically and its\n * status set from success/throw. Works whether or not tracing is exporting —\n * falls back to a no-op span if no tracer is installed. */\nexport async function span<T>(\n name: string,\n fn: (span: Span) => Promise<T> | T,\n opts?: SpanOpts,\n): Promise<T> {\n const tracer = trace.getTracer(SCOPE);\n return tracer.startActiveSpan(\n name,\n { attributes: opts?.attributes, kind: opts?.kind ? KIND[opts.kind] : undefined },\n async (s) => {\n try {\n const result = await fn(s);\n s.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (err) {\n s.setStatus({ code: SpanStatusCode.ERROR, message: err instanceof Error ? err.message : String(err) });\n throw err;\n } finally {\n s.end();\n }\n },\n );\n}\n","import type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface Metrics {\n /** Monotonic counter (delta). Formalizes odla-db's usage counters. */\n count(name: string, by?: number, attrs?: Attrs): void;\n /** Last-value gauge. */\n gauge(name: string, value: number, attrs?: Attrs): void;\n /** A single observation (recorded as a delta sum for now; histograms later). */\n histogram(name: string, value: number, attrs?: Attrs, unit?: string): void;\n}\n\nfunction record(kind: \"sum\" | \"gauge\", name: string, value: number, attrs?: Attrs, unit?: string): void {\n const sink = currentSink();\n if (!sink) return; // outside an instrumented invocation — no-op\n const ts = nowNano();\n sink.metrics.push({ name, unit, kind, value, attrs: attrs ?? {}, startUnixNano: ts, timeUnixNano: ts });\n}\n\n/** The ambient metrics API for the current invocation. */\nexport function metrics(): Metrics {\n return {\n count: (name, by = 1, attrs) => record(\"sum\", name, by, attrs),\n gauge: (name, value, attrs) => record(\"gauge\", name, value, attrs),\n histogram: (name, value, attrs, unit) => record(\"sum\", name, value, attrs, unit),\n };\n}\n\n/** Convenience: increment a monotonic counter by `by` (default 1). */\nexport function count(name: string, by = 1, attrs?: Attrs): void {\n record(\"sum\", name, by, attrs);\n}\n","import { SpanStatusCode, trace } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface ErrorReport {\n /** Stable, low-cardinality error code (e.g. \"unique_violation\"). */\n code?: string;\n /** Low-cardinality route/step where it happened. */\n route?: string;\n /** Extra low-cardinality attributes for the index. */\n attributes?: Attrs;\n /** Rich context for triage — kept only in the R2 artifact bundle. */\n artifacts?: Record<string, unknown>;\n /** Dedup key; the collector fingerprints by (type+code+route) if absent. */\n fingerprint?: string;\n}\n\n// OTel severity number for ERROR.\nconst SEVERITY_ERROR = 17;\n\n/** Record a structured error. Returns an artifact id. The message and stack ride\n * the log body + a denied attribute so the collector persists them ONLY in the\n * R2 artifact bundle, never in metrics or the queryable index. */\nexport function recordError(err: unknown, report?: ErrorReport): string {\n const error = err instanceof Error ? err : new Error(String(err));\n const artifactId = `${Date.now().toString(16)}${crypto.randomUUID().replace(/-/g, \"\")}`;\n const span = trace.getActiveSpan();\n\n const attrs: Attrs = {\n \"error.type\": error.name,\n \"odla.artifact_id\": artifactId,\n ...(report?.code ? { \"error.code\": report.code } : {}),\n ...(report?.route ? { \"odla.route\": report.route } : {}),\n ...(report?.fingerprint ? { \"odla.fingerprint\": report.fingerprint } : {}),\n ...(report?.attributes ?? {}),\n };\n\n if (span) {\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });\n span.setAttributes(attrs);\n }\n\n const sink = currentSink();\n if (sink) {\n const ctx = span?.spanContext();\n sink.logs.push({\n timeUnixNano: nowNano(),\n severityNumber: SEVERITY_ERROR,\n severityText: \"ERROR\",\n body: error.message, // full message → R2 bundle only (collector-enforced)\n attrs: {\n ...attrs,\n // Denied keys: stripped from metrics/index, retained in the R2 bundle.\n ...(error.stack ? { \"exception.stacktrace\": error.stack } : {}),\n ...(report?.artifacts ? { \"odla.artifacts\": JSON.stringify(report.artifacts) } : {}),\n },\n ...(ctx ? { traceId: ctx.traceId, spanId: ctx.spanId } : {}),\n });\n }\n\n return artifactId;\n}\n","import { trace } from \"@opentelemetry/api\";\nimport { costUsd, type Attrs, type ModelPrice } from \"@odla/o11y-core\";\nimport { count } from \"./metrics.js\";\n\n/** The token accounting odla-kg's Provider already returns (and drops today). */\nexport interface LlmUsage {\n calls: number;\n inputTokens: number;\n outputTokens: number;\n}\n\nexport interface LlmCostOpts {\n provider: string; // \"claude\" | \"openai\" | ...\n model: string; // e.g. \"claude-sonnet-4-6\"\n operation?: string; // \"extract\" | \"search\" | ...\n attributes?: Attrs;\n /** Override the built-in price table for this call. */\n price?: ModelPrice;\n}\n\n/** Turn LLM token usage into first-class cost. Emits cost/token/call counters\n * and stamps the active span with GenAI semconv attributes. */\nexport function recordLlmUsage(usage: LlmUsage, opts: LlmCostOpts): { costUsd: number } {\n const cost = costUsd(opts.model, usage.inputTokens, usage.outputTokens, opts.price);\n const base: Attrs = {\n \"gen_ai.provider.name\": opts.provider,\n \"gen_ai.request.model\": opts.model,\n ...(opts.operation ? { \"gen_ai.operation.name\": opts.operation } : {}),\n ...(opts.attributes ?? {}),\n };\n\n count(\"odla.llm.cost.usd\", cost, base);\n count(\"odla.llm.calls\", usage.calls, base);\n count(\"odla.llm.tokens\", usage.inputTokens, { ...base, \"gen_ai.token.type\": \"input\" });\n count(\"odla.llm.tokens\", usage.outputTokens, { ...base, \"gen_ai.token.type\": \"output\" });\n\n trace.getActiveSpan()?.setAttributes({\n ...base,\n \"gen_ai.usage.input_tokens\": usage.inputTokens,\n \"gen_ai.usage.output_tokens\": usage.outputTokens,\n \"odla.llm.cost.usd\": cost,\n });\n\n return { costUsd: cost };\n}\n"],"mappings":"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","names":["metrics","SpanStatusCode","trace","span","trace","SpanStatusCode","trace","trace"]}
|
|
1
|
+
{"version":3,"sources":["../src/instrument.ts","../src/context.ts","../../o11y-core/src/encode.ts","../../o11y-core/src/encode-trace.ts","../../o11y-core/src/pricing.ts","../src/trace-export.ts","../src/span.ts","../src/metrics.ts","../src/logs.ts","../src/llm.ts"],"sourcesContent":["import { instrument, instrumentDO, type ResolveConfigFn, type TraceConfig } from \"@microlabs/otel-cf-workers\";\nimport { createSink, resolveConfig, resourceAttrs, runWithSink, SCOPE, type OptsFn } from \"./context.js\";\nimport { BindingSpanExporter } from \"./trace-export.js\";\n\n/** Build the otel-cf-workers trace config (exporter + service) from env/opts.\n * Exported for tests; not part of the package's public surface (see index.ts). */\nexport function traceConfig<E>(env: E, opts?: OptsFn<E>): TraceConfig {\n const c = resolveConfig(env, opts);\n const headers: Record<string, string> = {};\n if (c.token) headers.authorization = `Bearer ${c.token}`;\n const url = `${c.endpoint}/v1/traces`;\n const config: TraceConfig = {\n // A service binding needs a custom exporter (the stock OTLP exporter posts\n // over the global fetch); without one, fall back to the public URL exporter.\n exporter: c.fetcher\n ? new BindingSpanExporter(c.fetcher, url, headers, resourceAttrs(c), SCOPE)\n : { url, headers },\n service: { name: c.service, version: c.version },\n };\n if (c.sampleRatio != null) config.sampling = { headSampler: { ratio: c.sampleRatio } };\n return config;\n}\n\n/** Wrap each handler entrypoint so an ALS metrics/logs sink is live for the\n * duration of the invocation and flushed via ctx.waitUntil at the end.\n * Exported for tests; not part of the package's public surface (see index.ts). */\nexport function wrapHandler<E>(handler: ExportedHandler<E>, opts?: OptsFn<E>): ExportedHandler<E> {\n const wrapped: ExportedHandler<E> = { ...handler };\n\n const fetchImpl = handler.fetch;\n if (fetchImpl) {\n wrapped.fetch = (req, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await fetchImpl(req, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n const scheduledImpl = handler.scheduled;\n if (scheduledImpl) {\n wrapped.scheduled = (controller, env, ctx) => {\n const sink = createSink(resolveConfig(env, opts));\n return runWithSink(sink, async () => {\n try {\n return await scheduledImpl(controller, env, ctx);\n } finally {\n ctx.waitUntil(sink.flush());\n }\n });\n };\n }\n\n return wrapped;\n}\n\n/** Wrap a Worker handler with tracing (otel-cf-workers) + the metrics/logs sink.\n * Config is read from env vars ODLA_O11Y_{ENDPOINT,SERVICE,TOKEN,VERSION} unless\n * overridden by `opts`. */\nexport function withObservability<E = unknown>(\n handler: ExportedHandler<E>,\n opts?: OptsFn<E>,\n): ExportedHandler<E> {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env as E, opts);\n // instrument constrains E to its own Env type; we support any service env, so\n // cross the boundary with `any` and restore the caller's E on the way out.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrument(wrapHandler(handler, opts) as ExportedHandler<any>, configFn) as ExportedHandler<E>;\n}\n\n/** Wrap a Durable Object class with tracing. (Establishing the metrics/logs sink\n * inside DO methods is added in P1 when odla-db's usage counters are bridged.) */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function instrumentDurableObject<T extends abstract new (...args: any[]) => object>(\n cls: T,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n opts?: OptsFn<any>,\n): T {\n const configFn: ResolveConfigFn = (env, _trigger) => traceConfig(env, opts);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return instrumentDO(cls as any, configFn) as unknown as T;\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport {\n encodeLogsRequest,\n encodeMetricsRequest,\n type Attrs,\n type LogRecord,\n type MetricPoint,\n} from \"@odla/o11y-core\";\n\n/** The instrumentation scope name stamped on every signal. */\nexport const SCOPE = \"@odla-ai/o11y\";\n\n/** A Cloudflare service binding to the collector Worker (structural — avoids a\n * hard dependency on `@cloudflare/workers-types`). When present the SDK exports\n * every signal through `binding.fetch` (private worker-to-worker) instead of\n * the public endpoint; first-party hosting injects it as `env.ODLA_O11Y_COLLECTOR`. */\nexport interface CollectorBinding {\n fetch: typeof fetch;\n}\n\nexport interface ObservabilityOptions {\n /** Service name (default: env.ODLA_O11Y_SERVICE). */\n service?: string;\n /** Collector base URL (default: env.ODLA_O11Y_ENDPOINT). */\n endpoint?: string;\n /** Per-service bearer token (default: env.ODLA_O11Y_TOKEN). */\n token?: string;\n /** Release/version tag (default: env.ODLA_O11Y_VERSION ?? \"0.0.0\"). */\n version?: string;\n /** Head-sampling ratio 0..1 for traces (default: 1). */\n sampleRatio?: number;\n /** Static resource attributes merged onto every signal. */\n attributes?: Attrs;\n /** Service binding to the collector. Defaults to an `ODLA_O11Y_COLLECTOR`\n * binding on env; when set, all signals export through it instead of `fetch`. */\n fetcher?: CollectorBinding;\n}\n\nexport type OptsFn<E> = ObservabilityOptions | ((env: E) => ObservabilityOptions);\n\nexport interface ResolvedConfig {\n service: string;\n endpoint: string;\n token?: string;\n version: string;\n attributes: Attrs;\n sampleRatio?: number;\n fetcher?: CollectorBinding;\n}\n\n/** Narrow an arbitrary env binding to a CollectorBinding (has a `fetch` method). */\nfunction asBinding(x: unknown): CollectorBinding | undefined {\n return x && typeof (x as CollectorBinding).fetch === \"function\" ? (x as CollectorBinding) : undefined;\n}\n\nfunction evalOpts<E>(opts: OptsFn<E> | undefined, env: E): ObservabilityOptions {\n return typeof opts === \"function\" ? opts(env) : (opts ?? {});\n}\n\n/** Resolve options against the service's env vars. */\nexport function resolveConfig<E>(env: E, opts?: OptsFn<E>): ResolvedConfig {\n const o = evalOpts(opts, env);\n const e = env as Record<string, string | undefined>;\n const fetcher = o.fetcher ?? asBinding((env as Record<string, unknown>)[\"ODLA_O11Y_COLLECTOR\"]);\n // A binding ignores the URL host (it routes to the bound Worker), but the SDK\n // still needs a well-formed base to build /v1/* paths — default it internally.\n const endpoint = (o.endpoint ?? e[\"ODLA_O11Y_ENDPOINT\"] ?? (fetcher ? \"https://svc.internal\" : \"\")).replace(/\\/$/, \"\");\n return {\n service: o.service ?? e[\"ODLA_O11Y_SERVICE\"] ?? \"unknown\",\n endpoint,\n token: o.token ?? e[\"ODLA_O11Y_TOKEN\"],\n version: o.version ?? e[\"ODLA_O11Y_VERSION\"] ?? \"0.0.0\",\n attributes: o.attributes ?? {},\n sampleRatio: o.sampleRatio,\n fetcher,\n };\n}\n\n/** Nanoseconds since epoch as a decimal string (OTLP timestamp format). */\nexport function nowNano(): string {\n return `${Date.now()}000000`;\n}\n\nexport interface Sink {\n config: ResolvedConfig;\n metrics: MetricPoint[];\n logs: LogRecord[];\n flush(): Promise<void>;\n}\n\nconst als = new AsyncLocalStorage<Sink>();\n\nexport function currentSink(): Sink | undefined {\n return als.getStore();\n}\n\nexport function runWithSink<T>(sink: Sink, fn: () => Promise<T>): Promise<T> {\n return als.run(sink, fn);\n}\n\n/** Resource attributes stamped on every signal. Exported so the trace exporter\n * builds the same resource the metrics/logs sink does. */\nexport function resourceAttrs(config: ResolvedConfig): Attrs {\n return { \"service.name\": config.service, \"service.version\": config.version, ...config.attributes };\n}\n\nasync function post(url: string, body: unknown, token?: string, fetcher?: CollectorBinding): Promise<void> {\n const init = {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(token ? { authorization: `Bearer ${token}` } : {}),\n },\n body: JSON.stringify(body),\n };\n try {\n await (fetcher ? fetcher.fetch(url, init) : fetch(url, init));\n } catch {\n // Telemetry export is best-effort — never surface into the app.\n }\n}\n\n/** A per-invocation sink: accumulates metric/log records and flushes them to the\n * collector as OTLP/JSON (traces are exported separately by otel-cf-workers). */\nexport function createSink(config: ResolvedConfig): Sink {\n return {\n config,\n metrics: [],\n logs: [],\n async flush(): Promise<void> {\n if (!this.config.endpoint) {\n this.metrics = [];\n this.logs = [];\n return;\n }\n const resource = resourceAttrs(this.config);\n const jobs: Promise<void>[] = [];\n const { token, fetcher } = this.config;\n if (this.metrics.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/metrics`, encodeMetricsRequest(resource, SCOPE, this.metrics), token, fetcher),\n );\n this.metrics = [];\n }\n if (this.logs.length) {\n jobs.push(\n post(`${this.config.endpoint}/v1/logs`, encodeLogsRequest(resource, SCOPE, this.logs), token, fetcher),\n );\n this.logs = [];\n }\n await Promise.all(jobs);\n },\n };\n}\n","// OTLP/JSON encoders — the emit side of the wire contract (the SDK produces\n// these; the collector decodes them in later phases). Traces are produced by\n// @microlabs/otel-cf-workers; metrics and logs are hand-rolled here because that\n// library is traces-only and the Node OTel metrics SDK doesn't fit the isolate\n// model.\n\nimport type { Attrs, OtlpKeyValue, OtlpResource, OtlpScope } from \"./otlp.js\";\n\n/** Inverse of kvListToAttrs: a flat attr map → OTLP KeyValue[]. */\nexport function attrsToKvList(attrs: Attrs): OtlpKeyValue[] {\n const out: OtlpKeyValue[] = [];\n for (const [key, value] of Object.entries(attrs)) {\n if (typeof value === \"string\") out.push({ key, value: { stringValue: value } });\n else if (typeof value === \"boolean\") out.push({ key, value: { boolValue: value } });\n else if (Number.isInteger(value)) out.push({ key, value: { intValue: String(value) } });\n else out.push({ key, value: { doubleValue: value } });\n }\n return out;\n}\n\n// ---- Metrics ----\n\nexport type MetricKind = \"sum\" | \"gauge\";\n\nexport interface MetricPoint {\n name: string;\n unit?: string;\n kind: MetricKind;\n value: number;\n attrs: Attrs;\n startUnixNano: string;\n timeUnixNano: string;\n}\n\ninterface OtlpNumberDataPoint {\n attributes?: OtlpKeyValue[];\n startTimeUnixNano?: string;\n timeUnixNano: string;\n asDouble: number;\n}\n\ninterface OtlpMetric {\n name: string;\n unit?: string;\n sum?: { aggregationTemporality: number; isMonotonic: boolean; dataPoints: OtlpNumberDataPoint[] };\n gauge?: { dataPoints: OtlpNumberDataPoint[] };\n}\n\nexport interface ExportMetricsServiceRequest {\n resourceMetrics: Array<{\n resource: OtlpResource;\n scopeMetrics: Array<{ scope: OtlpScope; metrics: OtlpMetric[] }>;\n }>;\n}\n\n// OTLP AggregationTemporality: 1 = DELTA. Each ephemeral isolate reports its own\n// delta; the collector aggregates across invocations into Analytics Engine.\nconst DELTA = 1;\n\nexport function encodeMetricsRequest(\n resource: Attrs,\n scopeName: string,\n points: MetricPoint[],\n): ExportMetricsServiceRequest {\n const metrics: OtlpMetric[] = points.map((p) => {\n const dp: OtlpNumberDataPoint = {\n attributes: attrsToKvList(p.attrs),\n startTimeUnixNano: p.startUnixNano,\n timeUnixNano: p.timeUnixNano,\n asDouble: p.value,\n };\n return p.kind === \"gauge\"\n ? { name: p.name, unit: p.unit, gauge: { dataPoints: [dp] } }\n : { name: p.name, unit: p.unit, sum: { aggregationTemporality: DELTA, isMonotonic: true, dataPoints: [dp] } };\n });\n return {\n resourceMetrics: [\n { resource: { attributes: attrsToKvList(resource) }, scopeMetrics: [{ scope: { name: scopeName }, metrics }] },\n ],\n };\n}\n\n// ---- Logs (errors ride the logs signal) ----\n\nexport interface LogRecord {\n timeUnixNano: string;\n severityNumber: number; // OTel: 17 = ERROR, 9 = INFO\n severityText: string;\n body: string;\n attrs: Attrs;\n traceId?: string;\n spanId?: string;\n}\n\nexport interface ExportLogsServiceRequest {\n resourceLogs: Array<{\n resource: OtlpResource;\n scopeLogs: Array<{\n scope: OtlpScope;\n logRecords: Array<{\n timeUnixNano: string;\n observedTimeUnixNano: string;\n severityNumber: number;\n severityText: string;\n body: { stringValue: string };\n attributes: OtlpKeyValue[];\n traceId?: string;\n spanId?: string;\n }>;\n }>;\n }>;\n}\n\nexport function encodeLogsRequest(\n resource: Attrs,\n scopeName: string,\n records: LogRecord[],\n): ExportLogsServiceRequest {\n return {\n resourceLogs: [\n {\n resource: { attributes: attrsToKvList(resource) },\n scopeLogs: [\n {\n scope: { name: scopeName },\n logRecords: records.map((r) => ({\n timeUnixNano: r.timeUnixNano,\n observedTimeUnixNano: r.timeUnixNano,\n severityNumber: r.severityNumber,\n severityText: r.severityText,\n body: { stringValue: r.body },\n attributes: attrsToKvList(r.attrs),\n ...(r.traceId ? { traceId: r.traceId } : {}),\n ...(r.spanId ? { spanId: r.spanId } : {}),\n })),\n },\n ],\n },\n ],\n };\n}\n","// OTLP/JSON trace encoder — the emit-side inverse of `decodeTraces` (otlp.ts).\n// The default SDK path produces spans via @microlabs/otel-cf-workers, but that\n// library's exporter posts over the *global* fetch and can't be pointed at a\n// Cloudflare service binding. When a Worker exports through a binding instead\n// (first-party hosting, private ingest), the SDK hand-encodes its spans here so\n// the whole batch rides the same private transport as metrics and logs.\n\nimport { attrsToKvList } from \"./encode.js\";\nimport type { Attrs, ExportTraceServiceRequest, OtlpSpan } from \"./otlp.js\";\n\n/** The flat span shape the SDK hands the encoder. Field semantics mirror the\n * OTLP wire fields decoded in `decodeTraces`: `kind` is the OTLP span kind\n * (1 INTERNAL .. 5 CONSUMER) and `statusCode` is 0 UNSET / 1 OK / 2 ERROR. */\nexport interface SpanInput {\n traceId: string;\n spanId: string;\n parentSpanId: string;\n name: string;\n kind: number;\n statusCode: number;\n startUnixNano: string;\n endUnixNano: string;\n attributes: Attrs;\n}\n\n/** Encode a batch of spans (one service + scope) as an OTLP/JSON\n * ExportTraceServiceRequest — the exact shape `decodeTraces` consumes. */\nexport function encodeTraceRequest(\n resource: Attrs,\n scopeName: string,\n spans: SpanInput[],\n): ExportTraceServiceRequest {\n const otlpSpans: OtlpSpan[] = spans.map((s) => ({\n traceId: s.traceId,\n spanId: s.spanId,\n ...(s.parentSpanId ? { parentSpanId: s.parentSpanId } : {}),\n name: s.name,\n kind: s.kind,\n startTimeUnixNano: s.startUnixNano,\n endTimeUnixNano: s.endUnixNano,\n attributes: attrsToKvList(s.attributes),\n status: { code: s.statusCode },\n }));\n return {\n resourceSpans: [\n {\n resource: { attributes: attrsToKvList(resource) },\n scopeSpans: [{ scope: { name: scopeName }, spans: otlpSpans }],\n },\n ],\n };\n}\n","// LLM price table + cost computation. OTel GenAI semconv has no cost attribute,\n// so we compute cost downstream from raw token counts × model price. Keep this\n// table in sync with current model pricing (verify against the claude-api\n// reference); unknown models cost 0 and should be added here.\n\nexport interface ModelPrice {\n /** USD per million input tokens. */\n inputPerMTok: number;\n /** USD per million output tokens. */\n outputPerMTok: number;\n}\n\n/** Prices in USD per million tokens (MTok). */\nexport const MODEL_PRICING: Record<string, ModelPrice> = {\n \"claude-haiku-4-5\": { inputPerMTok: 1, outputPerMTok: 5 },\n \"claude-sonnet-4-6\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-sonnet-5\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-opus-4-6\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-7\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-opus-4-8\": { inputPerMTok: 5, outputPerMTok: 25 },\n \"claude-fable-5\": { inputPerMTok: 10, outputPerMTok: 50 },\n};\n\n/** Compute USD cost for a call. Pass `override` to price a model not in the\n * table (or to apply promotional pricing). Returns 0 for unknown models. */\nexport function costUsd(\n model: string,\n inputTokens: number,\n outputTokens: number,\n override?: ModelPrice,\n): number {\n const price = override ?? MODEL_PRICING[model];\n if (!price) return 0;\n return (inputTokens / 1_000_000) * price.inputPerMTok + (outputTokens / 1_000_000) * price.outputPerMTok;\n}\n","import type { ExportResult } from \"@opentelemetry/core\";\nimport type { ReadableSpan, SpanExporter } from \"@opentelemetry/sdk-trace-base\";\nimport { encodeTraceRequest, type Attrs, type SpanInput } from \"@odla/o11y-core\";\nimport type { CollectorBinding } from \"./context.js\";\n\n// ExportResultCode: 0 = SUCCESS, 1 = FAILED (@opentelemetry/core). Kept as\n// literals so we don't take a runtime dependency on the enum.\nconst SUCCESS = 0;\nconst FAILED = 1;\n\n// OTLP span kind is the OpenTelemetry SDK `SpanKind` (INTERNAL=0 .. CONSUMER=4)\n// shifted by one (OTLP INTERNAL=1 .. CONSUMER=5).\nconst OTLP_KIND_OFFSET = 1;\n\n/** OTel `HrTime` ([seconds, nanos]) → a decimal-nanosecond string (OTLP). */\nfunction hrToNano([seconds, nanos]: [number, number]): string {\n return (BigInt(seconds) * 1_000_000_000n + BigInt(nanos)).toString();\n}\n\n/** Project an OTel ReadableSpan onto the flat shape the core encoder wants,\n * dropping non-primitive attribute values (arrays/objects) the wire can't hold. */\nexport function toSpanInput(span: ReadableSpan): SpanInput {\n const ctx = span.spanContext();\n const attributes: Attrs = {};\n for (const [key, value] of Object.entries(span.attributes)) {\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n attributes[key] = value;\n }\n }\n return {\n traceId: ctx.traceId,\n spanId: ctx.spanId,\n parentSpanId: span.parentSpanContext?.spanId ?? \"\",\n name: span.name,\n kind: span.kind + OTLP_KIND_OFFSET,\n statusCode: span.status.code,\n startUnixNano: hrToNano(span.startTime),\n endUnixNano: hrToNano(span.endTime),\n attributes,\n };\n}\n\n/** A SpanExporter that ships OTLP/JSON spans through a Cloudflare service\n * binding instead of the global `fetch` the stock OTLP exporter uses — so\n * traces travel the same private worker-to-worker path as metrics and logs. */\nexport class BindingSpanExporter implements SpanExporter {\n constructor(\n private readonly binding: CollectorBinding,\n private readonly url: string,\n private readonly headers: Record<string, string>,\n private readonly resource: Attrs,\n private readonly scope: string,\n ) {}\n\n export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {\n const body = encodeTraceRequest(this.resource, this.scope, spans.map(toSpanInput));\n this.binding\n .fetch(this.url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", ...this.headers },\n body: JSON.stringify(body),\n })\n .then(() => resultCallback({ code: SUCCESS }))\n .catch((error: Error) => resultCallback({ code: FAILED, error }));\n }\n\n async shutdown(): Promise<void> {}\n}\n","import { SpanKind, SpanStatusCode, trace, type Span } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { SCOPE } from \"./context.js\";\n\nexport type SpanKindName = \"internal\" | \"server\" | \"client\" | \"producer\" | \"consumer\";\n\nexport interface SpanOpts {\n attributes?: Attrs;\n kind?: SpanKindName;\n}\n\nconst KIND: Record<SpanKindName, SpanKind> = {\n internal: SpanKind.INTERNAL,\n server: SpanKind.SERVER,\n client: SpanKind.CLIENT,\n producer: SpanKind.PRODUCER,\n consumer: SpanKind.CONSUMER,\n};\n\n/** Run `fn` inside a new active span. The span is ended automatically and its\n * status set from success/throw. Works whether or not tracing is exporting —\n * falls back to a no-op span if no tracer is installed. */\nexport async function span<T>(\n name: string,\n fn: (span: Span) => Promise<T> | T,\n opts?: SpanOpts,\n): Promise<T> {\n const tracer = trace.getTracer(SCOPE);\n return tracer.startActiveSpan(\n name,\n { attributes: opts?.attributes, kind: opts?.kind ? KIND[opts.kind] : undefined },\n async (s) => {\n try {\n const result = await fn(s);\n s.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (err) {\n s.setStatus({ code: SpanStatusCode.ERROR, message: err instanceof Error ? err.message : String(err) });\n throw err;\n } finally {\n s.end();\n }\n },\n );\n}\n","import type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface Metrics {\n /** Monotonic counter (delta). Formalizes odla-db's usage counters. */\n count(name: string, by?: number, attrs?: Attrs): void;\n /** Last-value gauge. */\n gauge(name: string, value: number, attrs?: Attrs): void;\n /** A single observation (recorded as a delta sum for now; histograms later). */\n histogram(name: string, value: number, attrs?: Attrs, unit?: string): void;\n}\n\nfunction record(kind: \"sum\" | \"gauge\", name: string, value: number, attrs?: Attrs, unit?: string): void {\n const sink = currentSink();\n if (!sink) return; // outside an instrumented invocation — no-op\n const ts = nowNano();\n sink.metrics.push({ name, unit, kind, value, attrs: attrs ?? {}, startUnixNano: ts, timeUnixNano: ts });\n}\n\n/** The ambient metrics API for the current invocation. */\nexport function metrics(): Metrics {\n return {\n count: (name, by = 1, attrs) => record(\"sum\", name, by, attrs),\n gauge: (name, value, attrs) => record(\"gauge\", name, value, attrs),\n histogram: (name, value, attrs, unit) => record(\"sum\", name, value, attrs, unit),\n };\n}\n\n/** Convenience: increment a monotonic counter by `by` (default 1). */\nexport function count(name: string, by = 1, attrs?: Attrs): void {\n record(\"sum\", name, by, attrs);\n}\n","import { SpanStatusCode, trace } from \"@opentelemetry/api\";\nimport type { Attrs } from \"@odla/o11y-core\";\nimport { currentSink, nowNano } from \"./context.js\";\n\nexport interface ErrorReport {\n /** Stable, low-cardinality error code (e.g. \"unique_violation\"). */\n code?: string;\n /** Low-cardinality route/step where it happened. */\n route?: string;\n /** Extra low-cardinality attributes for the index. */\n attributes?: Attrs;\n /** Rich context for triage — kept only in the R2 artifact bundle. */\n artifacts?: Record<string, unknown>;\n /** Dedup key; the collector fingerprints by (type+code+route) if absent. */\n fingerprint?: string;\n}\n\n// OTel severity number for ERROR.\nconst SEVERITY_ERROR = 17;\n\n/** Record a structured error. Returns an artifact id. The message and stack ride\n * the log body + a denied attribute so the collector persists them ONLY in the\n * R2 artifact bundle, never in metrics or the queryable index. */\nexport function recordError(err: unknown, report?: ErrorReport): string {\n const error = err instanceof Error ? err : new Error(String(err));\n const artifactId = `${Date.now().toString(16)}${crypto.randomUUID().replace(/-/g, \"\")}`;\n const span = trace.getActiveSpan();\n\n const attrs: Attrs = {\n \"error.type\": error.name,\n \"odla.artifact_id\": artifactId,\n ...(report?.code ? { \"error.code\": report.code } : {}),\n ...(report?.route ? { \"odla.route\": report.route } : {}),\n ...(report?.fingerprint ? { \"odla.fingerprint\": report.fingerprint } : {}),\n ...(report?.attributes ?? {}),\n };\n\n if (span) {\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });\n span.setAttributes(attrs);\n }\n\n const sink = currentSink();\n if (sink) {\n const ctx = span?.spanContext();\n sink.logs.push({\n timeUnixNano: nowNano(),\n severityNumber: SEVERITY_ERROR,\n severityText: \"ERROR\",\n body: error.message, // full message → R2 bundle only (collector-enforced)\n attrs: {\n ...attrs,\n // Denied keys: stripped from metrics/index, retained in the R2 bundle.\n ...(error.stack ? { \"exception.stacktrace\": error.stack } : {}),\n ...(report?.artifacts ? { \"odla.artifacts\": JSON.stringify(report.artifacts) } : {}),\n },\n ...(ctx ? { traceId: ctx.traceId, spanId: ctx.spanId } : {}),\n });\n }\n\n return artifactId;\n}\n","import { trace } from \"@opentelemetry/api\";\nimport { costUsd, type Attrs, type ModelPrice } from \"@odla/o11y-core\";\nimport { count } from \"./metrics.js\";\n\n/** The token accounting odla-kg's Provider already returns (and drops today). */\nexport interface LlmUsage {\n calls: number;\n inputTokens: number;\n outputTokens: number;\n}\n\nexport interface LlmCostOpts {\n provider: string; // \"claude\" | \"openai\" | ...\n model: string; // e.g. \"claude-sonnet-4-6\"\n operation?: string; // \"extract\" | \"search\" | ...\n attributes?: Attrs;\n /** Override the built-in price table for this call. */\n price?: ModelPrice;\n}\n\n/** Turn LLM token usage into first-class cost. Emits cost/token/call counters\n * and stamps the active span with GenAI semconv attributes. */\nexport function recordLlmUsage(usage: LlmUsage, opts: LlmCostOpts): { costUsd: number } {\n const cost = costUsd(opts.model, usage.inputTokens, usage.outputTokens, opts.price);\n const base: Attrs = {\n \"gen_ai.provider.name\": opts.provider,\n \"gen_ai.request.model\": opts.model,\n ...(opts.operation ? { \"gen_ai.operation.name\": opts.operation } : {}),\n ...(opts.attributes ?? {}),\n };\n\n count(\"odla.llm.cost.usd\", cost, base);\n count(\"odla.llm.calls\", usage.calls, base);\n count(\"odla.llm.tokens\", usage.inputTokens, { ...base, \"gen_ai.token.type\": \"input\" });\n count(\"odla.llm.tokens\", usage.outputTokens, { ...base, \"gen_ai.token.type\": \"output\" });\n\n trace.getActiveSpan()?.setAttributes({\n ...base,\n \"gen_ai.usage.input_tokens\": usage.inputTokens,\n \"gen_ai.usage.output_tokens\": usage.outputTokens,\n \"odla.llm.cost.usd\": cost,\n });\n\n return { costUsd: cost };\n}\n"],"mappings":"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","names":["metrics","span","SpanStatusCode","trace","span","trace","SpanStatusCode","trace","trace"]}
|
package/llms.txt
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
> Official observability client for odla Cloudflare Workers — OpenTelemetry traces, metrics, structured errors, and LLM cost, exported over OTLP to the odla-o11y collector.
|
|
4
4
|
|
|
5
|
+
> ⚠️ **Experimental — an agentic-coding experiment.** This package is built and
|
|
6
|
+
> operated by autonomous coding agents as an experiment in agentic loops. APIs
|
|
7
|
+
> change without notice and nothing here is production-hardened. **Use at your
|
|
8
|
+
> own risk.**
|
|
9
|
+
|
|
5
10
|
Official observability client for [odla](https://github.com/) Cloudflare Workers.
|
|
6
11
|
Wrap your Worker once and get OpenTelemetry **traces**, **metrics**, structured
|
|
7
12
|
**errors**, and **LLM cost** — exported over OTLP to the odla-o11y collector.
|
|
@@ -46,6 +51,16 @@ Config is read from env vars (override per call via the second `withObservabilit
|
|
|
46
51
|
|
|
47
52
|
`wrangler.jsonc` needs `"compatibility_flags": ["nodejs_compat"]` (for AsyncLocalStorage).
|
|
48
53
|
|
|
54
|
+
### Private ingest via a service binding
|
|
55
|
+
|
|
56
|
+
By default all signals export over `fetch` to `ODLA_O11Y_ENDPOINT`. If the Worker
|
|
57
|
+
is bound to the collector (a service binding named `ODLA_O11Y_COLLECTOR`, or one
|
|
58
|
+
passed as `opts.fetcher`), the SDK routes **traces, metrics, and logs** through
|
|
59
|
+
that binding instead — private worker-to-worker, no public hop, and the endpoint
|
|
60
|
+
host becomes irrelevant. This is the transport odla's first-party hosting uses;
|
|
61
|
+
the binding and its credential are injected at deploy time, so instrumented code
|
|
62
|
+
stays `export default withObservability(handler)` with nothing else to set.
|
|
63
|
+
|
|
49
64
|
Keep `ODLA_O11Y_ENDPOINT` and `ODLA_O11Y_SERVICE` set as plain vars in every
|
|
50
65
|
environment and treat only `ODLA_O11Y_TOKEN` as an optional secret: the
|
|
51
66
|
metrics/logs sink no-ops without an endpoint, but the trace exporter builds
|
|
@@ -68,13 +83,20 @@ collector rejects the export; the app itself is unaffected.
|
|
|
68
83
|
|
|
69
84
|
MIT
|
|
70
85
|
|
|
71
|
-
## API reference (generated from dist/index.d.ts, v1.0
|
|
86
|
+
## API reference (generated from dist/index.d.ts, v1.1.0)
|
|
72
87
|
|
|
73
88
|
```ts
|
|
74
89
|
import { Attrs, ModelPrice } from '@odla/o11y-core';
|
|
75
90
|
export { Attrs } from '@odla/o11y-core';
|
|
76
91
|
import { Span } from '@opentelemetry/api';
|
|
77
92
|
|
|
93
|
+
/** A Cloudflare service binding to the collector Worker (structural — avoids a
|
|
94
|
+
* hard dependency on `@cloudflare/workers-types`). When present the SDK exports
|
|
95
|
+
* every signal through `binding.fetch` (private worker-to-worker) instead of
|
|
96
|
+
* the public endpoint; first-party hosting injects it as `env.ODLA_O11Y_COLLECTOR`. */
|
|
97
|
+
interface CollectorBinding {
|
|
98
|
+
fetch: typeof fetch;
|
|
99
|
+
}
|
|
78
100
|
interface ObservabilityOptions {
|
|
79
101
|
/** Service name (default: env.ODLA_O11Y_SERVICE). */
|
|
80
102
|
service?: string;
|
|
@@ -88,6 +110,9 @@ interface ObservabilityOptions {
|
|
|
88
110
|
sampleRatio?: number;
|
|
89
111
|
/** Static resource attributes merged onto every signal. */
|
|
90
112
|
attributes?: Attrs;
|
|
113
|
+
/** Service binding to the collector. Defaults to an `ODLA_O11Y_COLLECTOR`
|
|
114
|
+
* binding on env; when set, all signals export through it instead of `fetch`. */
|
|
115
|
+
fetcher?: CollectorBinding;
|
|
91
116
|
}
|
|
92
117
|
type OptsFn<E> = ObservabilityOptions | ((env: E) => ObservabilityOptions);
|
|
93
118
|
|
|
@@ -159,5 +184,5 @@ declare function recordLlmUsage(usage: LlmUsage, opts: LlmCostOpts): {
|
|
|
159
184
|
costUsd: number;
|
|
160
185
|
};
|
|
161
186
|
|
|
162
|
-
export { type ErrorReport, type LlmCostOpts, type LlmUsage, type Metrics, type ObservabilityOptions, type OptsFn, type SpanKindName, type SpanOpts, count, instrumentDurableObject, metrics, recordError, recordLlmUsage, span, withObservability };
|
|
187
|
+
export { type CollectorBinding, type ErrorReport, type LlmCostOpts, type LlmUsage, type Metrics, type ObservabilityOptions, type OptsFn, type SpanKindName, type SpanOpts, count, instrumentDurableObject, metrics, recordError, recordLlmUsage, span, withObservability };
|
|
163
188
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@odla-ai/o11y",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Official observability client for odla Cloudflare Workers \u2014 OpenTelemetry traces, metrics, structured errors, and LLM cost, exported over OTLP to the odla-o11y collector.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "tsup",
|
|
40
40
|
"clean": "rm -rf dist",
|
|
41
|
-
"test": "vitest run
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"coverage": "vitest run --coverage",
|
|
42
43
|
"gen:llms": "node ../../scripts/gen-llms.mjs",
|
|
43
44
|
"prepublishOnly": "npm run build && npm run gen:llms"
|
|
44
45
|
},
|
|
@@ -48,6 +49,8 @@
|
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@odla/o11y-core": "*",
|
|
52
|
+
"@opentelemetry/core": "^2.9.0",
|
|
53
|
+
"@opentelemetry/sdk-trace-base": "^2.9.0",
|
|
51
54
|
"tsup": "^8.5.1",
|
|
52
55
|
"typescript": "^6.0.3"
|
|
53
56
|
}
|