@heystack/otel 0.18.0 → 0.19.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/dist/workers.d.ts CHANGED
@@ -3,6 +3,10 @@ import { type Context, type ContextManager } from "@opentelemetry/api";
3
3
  import { BasicTracerProvider, type ReadableSpan, type SpanExporter } from "@opentelemetry/sdk-trace-base";
4
4
  import { type HeystackOptions } from "./core.js";
5
5
  import { type LLMGatewayConfig } from "./llm-enrich.js";
6
+ import { wrapSqlClient } from "./workers-bindings.js";
7
+ import { NATIVELY_COVERED, NATIVE_PROPAGATES_EXTERNAL, shouldEnrichNatively, type BindingKind } from "./workers-coverage.js";
8
+ import { type CloudflareSpan, type CloudflareTracing } from "./workers-native.js";
9
+ import { type SpanPrivacyOptions } from "./redact.js";
6
10
  declare const ExportResultCode: {
7
11
  readonly SUCCESS: 0;
8
12
  readonly FAILED: 1;
@@ -54,13 +58,15 @@ interface OtlpTracesPayload {
54
58
  }[];
55
59
  }[];
56
60
  }
61
+ /** Per-span attribute transform applied at serialization (privacy pass). */
62
+ type AttributeTransform = (attrs: Record<string, unknown>, span: ReadableSpan) => Record<string, unknown>;
57
63
  /**
58
64
  * Serialize a batch of ended spans into a single OTLP/JSON traces payload.
59
65
  *
60
66
  * All spans in a SimpleSpanProcessor batch share the same per-request provider,
61
67
  * hence the same resource, so we emit a single resourceSpans entry.
62
68
  */
63
- export declare function serializeSpans(spans: ReadableSpan[]): OtlpTracesPayload;
69
+ export declare function serializeSpans(spans: ReadableSpan[], transform?: AttributeTransform): OtlpTracesPayload;
64
70
  /** Parse a W3C `traceparent`. Returns null for malformed or all-zero ids. */
65
71
  export declare function parseTraceparent(header: string | null): {
66
72
  traceId: string;
@@ -112,7 +118,13 @@ export declare class HeystackSpanExporter implements SpanExporter {
112
118
  * the `pending` set + manual `flushHeystack()` when absent/unavailable.
113
119
  */
114
120
  waitUntil?: (p: Promise<unknown>) => void;
115
- constructor(options: HeystackOptions);
121
+ /**
122
+ * Privacy pass on every exported span — redacts URL attributes (`url.full`,
123
+ * `http.url`, `http.target`, `url.query`, …) so spans from host frameworks
124
+ * (Next/OpenNext, manual spans) are covered too, not just Heystack's own.
125
+ */
126
+ private readonly privacy;
127
+ constructor(options: HeystackOptions & SpanPrivacyOptions);
116
128
  export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void;
117
129
  /**
118
130
  * POST the batch with ONE bounded automatic retry for network errors / 429 /
@@ -143,7 +155,7 @@ export declare class HeystackSpanExporter implements SpanExporter {
143
155
  */
144
156
  forceFlush(): Promise<void>;
145
157
  }
146
- export interface WorkersConfig {
158
+ export interface WorkersConfig extends SpanPrivacyOptions {
147
159
  service: string;
148
160
  /** Defaults to env.HEYSTACK_API_KEY at request time if omitted. */
149
161
  apiKey?: string;
@@ -185,17 +197,33 @@ export interface WorkersConfig {
185
197
  * Defaults to `false` (no binding tracing). Consumed by a later task.
186
198
  */
187
199
  instrumentBindings?: boolean | string[];
200
+ /**
201
+ * Native mode only: per-kind override of which bindings the SDK enriches on
202
+ * top of Cloudflare's own spans. Default: only kinds Cloudflare does not trace
203
+ * (`ai`, `vectorize`, `workflow`, `hyperdrive`, `analytics_engine`). Set
204
+ * `{ ai: false }` to opt out or `{ d1: true }` to force-enrich a covered kind.
205
+ * Ignored when `instrumentBindings` is set explicitly.
206
+ */
207
+ nativeEnrichment?: Partial<Record<BindingKind, boolean>>;
188
208
  /**
189
209
  * Select the Workers tracing plane. `auto` (default) uses Cloudflare's native
190
210
  * tracer when `ctx.tracing` is available and otherwise falls back to direct
191
211
  * OTLP export. `native` prefers the platform tracer but still fails open when
192
212
  * unavailable. `direct` always uses Heystack's fetch exporter.
193
213
  *
194
- * Native mode deliberately disables Heystack root/fetch/binding
195
- * instrumentation: Cloudflare already emits those spans, and running both
196
- * creates duplicate, disconnected trace trees.
214
+ * Native mode creates no OTel provider and does not re-instrument what
215
+ * Cloudflare already spans (fetch, D1, KV, R2, DO, Queues, rate limit,
216
+ * service bindings). It enriches only the uncovered binding kinds (Workers AI,
217
+ * Vectorize, Workflows, Analytics Engine; Hyperdrive via `wrapSqlClient`) as
218
+ * native child spans, gated by `nativeEnrichment` / an explicit
219
+ * `instrumentBindings`.
197
220
  */
198
221
  nativeTracing?: "auto" | "native" | "direct";
222
+ /**
223
+ * Explicit Cloudflare tracer (`ctx.tracing`) for contexts that have none of
224
+ * their own, e.g. Durable Objects. Preferred over `ctx.tracing` when set.
225
+ */
226
+ tracing?: CloudflareTracing;
199
227
  /**
200
228
  * Head-sampling configuration. When omitted (or `rate` >= 1), all traces are
201
229
  * kept. When `rate` < 1, a deterministic FNV-1a hash of the trace ID decides
@@ -204,7 +232,9 @@ export interface WorkersConfig {
204
232
  *
205
233
  * Note: head sampling is parent-respecting, so an incoming request carrying a
206
234
  * sampled `traceparent` is still recorded even at `rate: 0` (it is not an
207
- * absolute kill-switch; it governs only fresh/root traces).
235
+ * absolute kill-switch; it governs only fresh/root traces) — unless
236
+ * `trustInboundSampling: false`, which makes the rate decide for inbound
237
+ * traces too.
208
238
  *
209
239
  * When `remote: true`, the sampling rate is fetched once per isolate from
210
240
  * `{endpoint}/v1/sampling/config` on the first request. Cold-isolate spans
@@ -215,6 +245,25 @@ export interface WorkersConfig {
215
245
  rate?: number;
216
246
  remote?: boolean;
217
247
  };
248
+ /**
249
+ * Whether an inbound W3C `traceparent` sampled flag (`-01` / `-00`) decides
250
+ * keep/drop for the request's trace. Default `true` (parent-based sampling,
251
+ * so a browser→Worker trace samples consistently). Set `false` on a Worker
252
+ * that receives traffic from callers you don't control: the trace id is
253
+ * still continued (correlation is kept) but the keep/drop decision is made
254
+ * by THIS Worker's `sampling` rate over the trace id, so a client can't force
255
+ * recording with `-01` (or hide its requests with `-00`). Only meaningful when
256
+ * `sampling` is set (with no rate every trace is kept anyway).
257
+ */
258
+ trustInboundSampling?: boolean;
259
+ /**
260
+ * Cloudflare geo attributes recorded on the SERVER span from `req.cf`.
261
+ * `"country"` (default) records only `geo.country`; `"full"` also records
262
+ * `geo.region`, `geo.city` and `geo.asn`; `false` records none. The end-user
263
+ * IP (`client.address`) is separate and off by default — see
264
+ * {@link SpanPrivacyOptions.captureClientIp}.
265
+ */
266
+ captureGeo?: "country" | "full" | false;
218
267
  /**
219
268
  * LLM/gen_ai capture for outbound calls to known providers (OpenAI, Anthropic,
220
269
  * Mistral, Google, CF AI Gateway). Metadata (model, tokens, latency,
@@ -243,17 +292,36 @@ export interface WorkersConfig {
243
292
  redact?: (text: string) => string;
244
293
  maxContentChars?: number;
245
294
  gateways?: LLMGatewayConfig[];
295
+ /**
296
+ * The Cloudflare AI Gateway exports its own OTel spans for these calls.
297
+ * When true the SDK's LLM span is marked `heystack.ai.delegated` so the
298
+ * call is counted once (by the gateway). Opt-in: without it the SDK span is
299
+ * the counted one, so an AI call isn't lost if the gateway's own export
300
+ * isn't configured. The `cf-aig-otel-*` headers are sent either way.
301
+ */
302
+ gatewayExport?: boolean;
246
303
  };
304
+ /**
305
+ * Path templates for `http.route`, e.g. `["/users/:id", "/files/*"]` (first
306
+ * match wins; a trailing `*` matches the rest). If neither this nor
307
+ * `routeTemplate` is set, `http.route` is omitted and Heystack templates the
308
+ * path server-side.
309
+ */
310
+ routes?: string[];
311
+ /** Compute the `http.route` template per request; wins over `routes`. */
312
+ routeTemplate?: (req: Request) => string | undefined;
313
+ /**
314
+ * Hostnames (exact string or RegExp) that receive `traceparent` (and AI
315
+ * Gateway headers) on native-mode outbound fetches. Default: every host except
316
+ * the Heystack ingest endpoint. `[]` disables propagation.
317
+ */
318
+ propagateTargets?: Array<string | RegExp>;
247
319
  }
248
- /** Minimal public surface exposed by Cloudflare's Workers custom-spans API. */
249
- export interface CloudflareNativeSpan {
250
- readonly isTraced: boolean;
251
- setAttribute(key: string, value?: string | number | boolean): void;
252
- }
253
- /** `ExecutionContext.tracing` without importing the `cloudflare:workers` module. */
254
- export interface CloudflareNativeTracing {
255
- enterSpan<T, A extends unknown[]>(name: string, callback: (span: CloudflareNativeSpan, ...args: A) => T, ...args: A): T;
256
- }
320
+ export type { CloudflareSpan, CloudflareTracing };
321
+ /** @deprecated Use {@link CloudflareSpan}. */
322
+ export type CloudflareNativeSpan = CloudflareSpan;
323
+ /** @deprecated Use {@link CloudflareTracing}. */
324
+ export type CloudflareNativeTracing = CloudflareTracing;
257
325
  /**
258
326
  * A `BasicTracerProvider` with the underlying `HeystackSpanExporter` attached so
259
327
  * flush paths can drain its in-flight fetches directly (the OTel processor's
@@ -272,10 +340,12 @@ export type HeystackTracerProvider = BasicTracerProvider & {
272
340
  * must drain the exporter — `await provider.heystackExporter.forceFlush()` (or
273
341
  * `flushHeystack()`), not just `provider.forceFlush()`.
274
342
  */
275
- export declare function createTracerProvider(config: HeystackOptions & {
343
+ export declare function createTracerProvider(config: HeystackOptions & SpanPrivacyOptions & {
276
344
  sampling?: {
277
345
  rate?: number;
346
+ remote?: boolean;
278
347
  };
348
+ trustInboundSampling?: boolean;
279
349
  }): HeystackTracerProvider;
280
350
  /**
281
351
  * An AsyncLocalStorage-backed ContextManager for the /workers entry. When the
@@ -483,6 +553,22 @@ export declare function wrapDurableObjectClass<C extends {
483
553
  export declare function wrapWorkflowEntrypointClass<C extends {
484
554
  readonly prototype: object;
485
555
  }>(cls: C, opts?: InstrumentClassesOpts): C;
556
+ /**
557
+ * Decorate a `WorkerEntrypoint` class (the target of a Service binding):
558
+ * - `fetch()` is traced exactly like an `instrument()`ed handler (SERVER span,
559
+ * inbound `traceparent` continued, export drained via `ctx.waitUntil`).
560
+ * - every public RPC method gets a SERVER span `rpc <method>` with
561
+ * `jsrpc.method` / `rpc.method`. Return values (including stubs, RpcTargets
562
+ * and custom thenables), `this` and errors pass through untouched.
563
+ *
564
+ * RPC spans exist in DIRECT mode only. When Cloudflare native tracing is active
565
+ * it already emits `jsrpc` spans, so RPC calls pass straight through and `fetch`
566
+ * takes `instrument()`'s native path. Constructor, `#private`, `_underscore` and
567
+ * Symbol-keyed members are never wrapped.
568
+ *
569
+ * export default wrapWorkerEntrypointClass(class Api extends WorkerEntrypoint { ... }, { service: "api" });
570
+ */
571
+ export declare function wrapWorkerEntrypointClass<T extends new (...a: any[]) => object>(cls: T, cfg: WorkersConfig): T;
486
572
  /**
487
573
  * Run `fn` inside a new child span named `name`. The span is automatically
488
574
  * parented to the currently-active span (via `context.active()`), started
@@ -502,8 +588,8 @@ export declare function withSpan<T>(name: string, attrs: Record<string, string |
502
588
  * without `nodejs_compat`: the explicit context cannot be lost across async
503
589
  * boundaries. It falls back to the regular OTel tracer outside Cloudflare.
504
590
  */
505
- export declare function withCloudflareSpan<T>(ctx: ExecutionContext | CloudflareNativeTracing, name: string, fn: (span: Span) => T | Promise<T>): Promise<T>;
506
- export declare function withCloudflareSpan<T>(ctx: ExecutionContext | CloudflareNativeTracing, name: string, attrs: Record<string, string | number | boolean>, fn: (span: Span) => T | Promise<T>): Promise<T>;
591
+ export declare function withCloudflareSpan<T>(ctx: ExecutionContext | CloudflareTracing, name: string, fn: (span: Span) => T | Promise<T>): Promise<T>;
592
+ export declare function withCloudflareSpan<T>(ctx: ExecutionContext | CloudflareTracing, name: string, attrs: Record<string, string | number | boolean>, fn: (span: Span) => T | Promise<T>): Promise<T>;
507
593
  /**
508
594
  * Add a named event (with optional attributes) to the currently-active span.
509
595
  * No-op when no span is active.
@@ -523,3 +609,6 @@ export { llmRequestAttrs as genAiRequestAttrs } from "./llm-enrich.js";
523
609
  */
524
610
  export { llmResponseAttrs as genAiResponseAttrs } from "./llm-enrich.js";
525
611
  export type { LLMGatewayConfig, LLMProvider } from "./llm-enrich.js";
612
+ export { NATIVELY_COVERED, NATIVE_PROPAGATES_EXTERNAL, shouldEnrichNatively, wrapSqlClient };
613
+ export { matchRoute } from "./workers-routes.js";
614
+ export type { BindingKind };