@heystack/otel 0.9.0 → 0.9.1
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/README.md +2 -1
- package/dist/workers.js +19 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ Set the key as a secret: `wrangler secret put HEYSTACK_API_KEY`.
|
|
|
107
107
|
> ```toml
|
|
108
108
|
> compatibility_flags = ["nodejs_compat"]
|
|
109
109
|
> ```
|
|
110
|
-
> Without it the SDK falls back to a synchronous stack-based context manager — suppression still works,
|
|
110
|
+
> Without it the SDK falls back to a synchronous stack-based context manager — suppression still works, and **binding spans (D1/KV/R2/Vectorize/AI/Queue/Service) are still parented to the request's SERVER span** (they fall back to the request context explicitly, so they never orphan into their own single-span traces). What still degrades to best-effort without `nodejs_compat` is cross-`await` parenting for *outbound-fetch CLIENT spans* and *manual `withSpan` spans* — so `nodejs_compat` remains recommended for a fully-nested trace tree.
|
|
111
111
|
|
|
112
112
|
### `WorkersConfig` options
|
|
113
113
|
|
|
@@ -364,6 +364,7 @@ As belt-and-suspenders the exporter also drops any span whose HTTP target points
|
|
|
364
364
|
|
|
365
365
|
## Migration / versioning
|
|
366
366
|
|
|
367
|
+
- **`0.9.1`** — **`/workers`: binding spans no longer orphan without `nodejs_compat`.** On a workerd runtime without `nodejs_compat` (no `globalThis.AsyncLocalStorage`), the synchronous fallback context manager cannot carry the active span across an `await` — so a D1/KV/R2/Vectorize/AI/Queue/Service span created *after* an `await` in your handler was emitted as the root of its own single-span trace instead of a child of the request. `instrument()` now captures the request's SERVER context per-request and uses it as an explicit parent fallback when no span is active, so binding operations are always children of the request trace. `nodejs_compat` is still recommended for cross-`await` parenting of outbound-fetch CLIENT spans and manual `withSpan` spans. No API change; upgrade and redeploy.
|
|
367
368
|
- **`0.9.0`** — **`/workers`: automatic LLM gen_ai enrichment for outbound API calls.** Outbound `fetch` calls to known LLM providers (OpenAI, Anthropic, Cloudflare AI Gateway, Google) automatically gain `gen_ai.*` OTel semantic-convention attributes on the CLIENT span — model, token counts, finish reason, response ID — with no extra code. New optional `WorkersConfig.ai` option: `captureContent: true` also captures prompt/completion text (off by default; **strongly recommended for AI-app RCA**), with `redact` for scrubbing and `maxContentChars` for length capping. The original request/response bodies are never consumed (request read only when already a string; response via `response.clone()`). Streaming responses skip response enrichment. No breaking changes.
|
|
368
369
|
- **`0.8.0`** — **`/workers`: Workers AI, Queue producer, and Service binding instrumentation.** `instrumentBindings: true` now auto-wraps three additional binding types: `env.AI.run()` emits CLIENT spans with `gen_ai.system`, `gen_ai.request.model`, and `gen_ai.usage.input_tokens`/`output_tokens` (streaming results are never consumed); Queue `.send`/`.sendBatch` emit PRODUCER spans with `messaging.*` attributes including batch size; Service binding `.fetch` emits a CLIENT span and injects a W3C `traceparent` header into the outgoing request so calls to other Workers appear in the same distributed trace. `startSpan` factory now accepts an optional `SpanKind` for correct CLIENT/PRODUCER categorisation. No breaking changes.
|
|
369
370
|
- **`0.7.0`** — **`/workers`: remote sampling (`sampling: { remote: true }`).** New `sampling` variant that fetches the head-sampling rate from the Heystack config endpoint at runtime, so you can change it from the console without redeploying. Cold isolates keep all traffic until the first config fetch resolves (fails open). If the config endpoint is unreachable, the worker keeps everything. Same parent-respecting rule as `sampling: { rate }`. No breaking changes; existing `sampling: { rate }` configs are unchanged.
|
package/dist/workers.js
CHANGED
|
@@ -886,19 +886,35 @@ export function instrument(handler, config) {
|
|
|
886
886
|
// FR5: response header carrying THIS span's trace + span id.
|
|
887
887
|
const sc = span.spanContext();
|
|
888
888
|
const traceparent = `00-${sc.traceId}-${sc.spanId}-01`;
|
|
889
|
+
// The context with THIS request's SERVER span active. Used both to run the
|
|
890
|
+
// handler and as an explicit parent fallback for binding spans.
|
|
891
|
+
const serverCtx = trace.setSpan(startCtx, span);
|
|
889
892
|
// Instrument bindings when requested — wrap env BEFORE handing to the
|
|
890
893
|
// handler so binding calls made inside `originalFetch` (which runs inside
|
|
891
|
-
// `context.with` below)
|
|
894
|
+
// `context.with` below) parent to the root span.
|
|
892
895
|
let handlerEnv = env;
|
|
893
896
|
if (config.instrumentBindings) {
|
|
894
897
|
const binTracer = trace.getTracer("heystack");
|
|
895
898
|
handlerEnv = instrumentEnv(env, {
|
|
896
|
-
|
|
899
|
+
// Prefer the ambient active context — that gives precise nested
|
|
900
|
+
// parenting when the AlsContextManager is active (runtimes with
|
|
901
|
+
// globalThis.AsyncLocalStorage). But when no span is active — e.g. on
|
|
902
|
+
// workerd WITHOUT nodejs_compat, where SyncStackContextManager can't
|
|
903
|
+
// carry context across an `await` and its stack has already unwound —
|
|
904
|
+
// fall back to THIS request's captured SERVER context so the binding
|
|
905
|
+
// span is still a child of the request instead of an orphan root of
|
|
906
|
+
// its own trace. serverCtx is captured per-request in this closure, so
|
|
907
|
+
// concurrent requests never cross-parent.
|
|
908
|
+
startSpan: (name, attrs, kind) => {
|
|
909
|
+
const active = context.active();
|
|
910
|
+
const parentCtx = trace.getSpan(active) ? active : serverCtx;
|
|
911
|
+
return binTracer.startSpan(name, { attributes: attrs, kind }, parentCtx);
|
|
912
|
+
},
|
|
897
913
|
select: config.instrumentBindings,
|
|
898
914
|
});
|
|
899
915
|
}
|
|
900
916
|
try {
|
|
901
|
-
const response = await context.with(
|
|
917
|
+
const response = await context.with(serverCtx, () => originalFetch(req, handlerEnv, ctx));
|
|
902
918
|
const headers = new Headers(response.headers);
|
|
903
919
|
headers.set("traceparent", traceparent);
|
|
904
920
|
appendExposeHeader(headers, "traceparent");
|