autotel 7.2.0 → 7.4.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.
Files changed (46) hide show
  1. package/README.md +16 -14
  2. package/dist/auto.cjs +1 -1
  3. package/dist/auto.js +1 -1
  4. package/dist/correlation-id.cjs +1 -1
  5. package/dist/correlation-id.js +1 -1
  6. package/dist/decorators.cjs +1 -1
  7. package/dist/decorators.js +1 -1
  8. package/dist/event.cjs +1 -1
  9. package/dist/event.js +1 -1
  10. package/dist/feature-flags.cjs +204 -0
  11. package/dist/feature-flags.d.cts +175 -0
  12. package/dist/feature-flags.d.ts +175 -0
  13. package/dist/feature-flags.js +198 -0
  14. package/dist/{functional-Doi0hFm3.js → functional-C4js2vTM.js} +5 -2
  15. package/dist/{functional-C925G95W.cjs → functional-DHlD7V0x.cjs} +5 -2
  16. package/dist/functional.cjs +1 -1
  17. package/dist/functional.js +1 -1
  18. package/dist/http.cjs +1 -1
  19. package/dist/http.js +1 -1
  20. package/dist/index.cjs +2 -2
  21. package/dist/index.js +2 -2
  22. package/dist/{init-DzAdgcFM.js → init-CsKK9vZO.js} +22 -2
  23. package/dist/{init-aTZKsAwy.cjs → init-DmM7dprX.cjs} +27 -1
  24. package/dist/instrumentation.cjs +1 -1
  25. package/dist/instrumentation.js +1 -1
  26. package/dist/messaging.cjs +2 -2
  27. package/dist/messaging.js +2 -2
  28. package/dist/sampling.cjs +1 -1
  29. package/dist/sampling.js +1 -1
  30. package/dist/semantic-helpers.cjs +1 -1
  31. package/dist/semantic-helpers.js +1 -1
  32. package/dist/tail-sampling-processor.cjs +1 -1
  33. package/dist/tail-sampling-processor.js +1 -1
  34. package/dist/testing.cjs +32 -0
  35. package/dist/testing.d.cts +26 -1
  36. package/dist/testing.d.ts +26 -1
  37. package/dist/testing.js +32 -1
  38. package/dist/webhook.cjs +1 -1
  39. package/dist/webhook.js +1 -1
  40. package/dist/workflow-distributed.cjs +1 -1
  41. package/dist/workflow-distributed.js +1 -1
  42. package/dist/workflow.cjs +1 -1
  43. package/dist/workflow.js +1 -1
  44. package/dist/yaml-config.cjs +1 -1
  45. package/dist/yaml-config.js +1 -1
  46. package/package.json +6 -1
@@ -0,0 +1,198 @@
1
+ import { trace } from "@opentelemetry/api";
2
+ import { logs } from "@opentelemetry/api-logs";
3
+
4
+ //#region src/feature-flags.ts
5
+ /**
6
+ * Feature flag evaluations, in the vocabulary OpenTelemetry already defined.
7
+ *
8
+ * A flagged rollout splits your traffic into two populations that share a
9
+ * service name, a route and a version. Without the flag on the span there is no
10
+ * way to ask the only question that matters during a rollout — is the new
11
+ * branch slower, or failing more, than the old one — and the usual workaround
12
+ * is to read it in the flag vendor's own dashboard, where the latency and the
13
+ * errors are not.
14
+ *
15
+ * The specification covers this: `feature_flag.key`, `.result.value`,
16
+ * `.result.variant`, `.result.reason`, `.provider.name`, `.context.id`, plus a
17
+ * `feature_flag.evaluation` event. Emitting those means any backend can split
18
+ * any metric by variant, with no vendor in the path.
19
+ *
20
+ * ## Attributes and events both
21
+ *
22
+ * Span attributes hold one flag: a second call overwrites the first. A request
23
+ * that branched on three flags needs one event each, which is what the
24
+ * `feature_flag.evaluation` event is for. Both are recorded — attributes so a
25
+ * single-flag span is filterable without unpacking events, events so a
26
+ * many-flag span keeps them all.
27
+ *
28
+ * ## Record what you branched on
29
+ *
30
+ * Record a flag where the code *reads* it, not where it is fetched. A flag
31
+ * evaluated and ignored explains nothing; the value the request actually took a
32
+ * branch on is the one that explains its behaviour.
33
+ */
34
+ /** Canonical `feature_flag.*` attribute keys. */
35
+ const FEATURE_FLAG = {
36
+ KEY: "feature_flag.key",
37
+ RESULT_VALUE: "feature_flag.result.value",
38
+ RESULT_VARIANT: "feature_flag.result.variant",
39
+ RESULT_REASON: "feature_flag.result.reason",
40
+ PROVIDER_NAME: "feature_flag.provider.name",
41
+ CONTEXT_ID: "feature_flag.context.id",
42
+ SET_ID: "feature_flag.set.id",
43
+ VERSION: "feature_flag.version",
44
+ /**
45
+ * Why an evaluation failed. `feature_flag.evaluation.error.message` is the
46
+ * deprecated spelling of this and is deliberately not emitted — writing both
47
+ * would double the cardinality to no benefit.
48
+ */
49
+ ERROR_MESSAGE: "feature_flag.error.message"
50
+ };
51
+ /** Canonical event name for one evaluation. */
52
+ const FEATURE_FLAG_EVALUATION_EVENT = "feature_flag.evaluation";
53
+ /**
54
+ * Canonical `feature_flag.result.reason` values. The registry defines them in
55
+ * lower snake case; OpenFeature and most SDKs report them upper-cased, and
56
+ * forwarding that splits every group-by into two buckets meaning the same
57
+ * thing.
58
+ */
59
+ const FEATURE_FLAG_REASON = [
60
+ "static",
61
+ "default",
62
+ "targeting_match",
63
+ "split",
64
+ "cached",
65
+ "disabled",
66
+ "unknown",
67
+ "stale",
68
+ "error"
69
+ ];
70
+ /**
71
+ * A flag value as an attribute.
72
+ *
73
+ * Booleans and numbers are kept as they are: `feature_flag.result.value`
74
+ * permits typed values, and stringifying `true` makes a boolean flag
75
+ * uncomparable with the numeric one next to it. Only a structured value — which
76
+ * no attribute type can hold — is serialised.
77
+ */
78
+ function asAttributeValue(value) {
79
+ if (typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return value;
80
+ try {
81
+ return JSON.stringify(value) ?? String(value);
82
+ } catch {
83
+ return String(value);
84
+ }
85
+ }
86
+ /** Canonical attributes for one evaluation. Absent fields are omitted. */
87
+ function featureFlagAttributes(evaluation) {
88
+ const attributes = {
89
+ [FEATURE_FLAG.KEY]: evaluation.key,
90
+ [FEATURE_FLAG.RESULT_VALUE]: asAttributeValue(evaluation.value)
91
+ };
92
+ const optional = [
93
+ [FEATURE_FLAG.RESULT_VARIANT, evaluation.variant],
94
+ [FEATURE_FLAG.RESULT_REASON, evaluation.reason === void 0 ? void 0 : evaluation.reason.toLowerCase()],
95
+ [FEATURE_FLAG.PROVIDER_NAME, evaluation.provider],
96
+ [FEATURE_FLAG.CONTEXT_ID, evaluation.contextId],
97
+ [FEATURE_FLAG.SET_ID, evaluation.setId],
98
+ [FEATURE_FLAG.VERSION, evaluation.version],
99
+ [FEATURE_FLAG.ERROR_MESSAGE, evaluation.errorMessage]
100
+ ];
101
+ for (const [key, value] of optional) if (value !== void 0) attributes[key] = value;
102
+ return attributes;
103
+ }
104
+ /**
105
+ * Record a flag evaluation on `sink` — as attributes and as a
106
+ * `feature_flag.evaluation` event.
107
+ *
108
+ * A missing sink is a no-op: instrumentation must never be the reason a branch
109
+ * throws, and a flag read outside any span is a legitimate thing to do.
110
+ */
111
+ function recordFeatureFlag(sink, evaluation) {
112
+ if (!sink) return;
113
+ const attributes = featureFlagAttributes(evaluation);
114
+ sink.setAttributes(attributes);
115
+ sink.track?.(FEATURE_FLAG_EVALUATION_EVENT, attributes);
116
+ }
117
+ /**
118
+ * Emit the evaluation as an OpenTelemetry **log record**.
119
+ *
120
+ * This is the Logs API model the repository emits events through. The obvious
121
+ * shortcut — `Span.addEvent` on the active span — puts the evaluation somewhere
122
+ * no log or event pipeline will ever look, which is exactly the failure the
123
+ * invariant exists to prevent. `correlated-events` is not a way around it
124
+ * either: that helper prefers `addEvent` when the target has one, so routing
125
+ * through it lands in the same place with more indirection.
126
+ *
127
+ * `logs.getLogger` returns a no-op logger when no `LoggerProvider` is
128
+ * registered, so this costs nothing in an application that exports no logs.
129
+ */
130
+ function emitEvaluationRecord(attributes) {
131
+ logs.getLogger("autotel").emit({
132
+ eventName: FEATURE_FLAG_EVALUATION_EVENT,
133
+ attributes
134
+ });
135
+ }
136
+ function evaluationFrom(hookContext, value, extra) {
137
+ return {
138
+ key: hookContext.flagKey,
139
+ value,
140
+ provider: hookContext.providerMetadata?.name,
141
+ contextId: hookContext.context?.targetingKey,
142
+ setId: hookContext.clientMetadata?.name,
143
+ ...extra
144
+ };
145
+ }
146
+ /**
147
+ * An OpenFeature hook that records every evaluation under the canonical
148
+ * `feature_flag.*` convention.
149
+ *
150
+ * This is the zero-config path: OpenFeature already sits between the
151
+ * application and whichever flag vendor it uses, and it already fires on every
152
+ * evaluation — which is exactly the moment worth recording, because it is the
153
+ * moment the code branched.
154
+ *
155
+ * ```ts
156
+ * import { OpenFeature } from '@openfeature/server-sdk';
157
+ * import { autotelOpenFeatureHook } from 'autotel/feature-flags';
158
+ *
159
+ * OpenFeature.addHooks(autotelOpenFeatureHook());
160
+ * ```
161
+ *
162
+ * A failed evaluation is still recorded, with the default value the code
163
+ * actually used and `reason: 'ERROR'`. The default is what the request behaved
164
+ * as; the failure is why.
165
+ */
166
+ function autotelOpenFeatureHook(options = {}) {
167
+ const getSpan = options.getSpan ?? (() => trace.getActiveSpan());
168
+ const emitLogRecord = options.emitLogRecord ?? emitEvaluationRecord;
169
+ /**
170
+ * Attributes onto the span that branched, and the event wherever the sink
171
+ * says. A sink with no `track` of its own gets a log record — including when
172
+ * there is no span at all, because the evaluation still happened.
173
+ */
174
+ const record = (evaluation) => {
175
+ const sink = getSpan();
176
+ const attributes = featureFlagAttributes(evaluation);
177
+ sink?.setAttributes(attributes);
178
+ if (sink?.track) sink.track(FEATURE_FLAG_EVALUATION_EVENT, attributes);
179
+ else emitLogRecord(attributes);
180
+ };
181
+ return {
182
+ after(hookContext, details) {
183
+ record(evaluationFrom(hookContext, details.value, {
184
+ variant: details.variant,
185
+ reason: details.reason
186
+ }));
187
+ },
188
+ error(hookContext, error) {
189
+ record(evaluationFrom(hookContext, hookContext.defaultValue, {
190
+ reason: "error",
191
+ errorMessage: error instanceof Error ? error.message : String(error)
192
+ }));
193
+ }
194
+ };
195
+ }
196
+
197
+ //#endregion
198
+ export { FEATURE_FLAG, FEATURE_FLAG_EVALUATION_EVENT, FEATURE_FLAG_REASON, autotelOpenFeatureHook, featureFlagAttributes, recordFeatureFlag };
@@ -1,7 +1,7 @@
1
1
  import { l as asString, m as isDevelopment } from "./values-BenyvWKz.js";
2
2
  import { getForceFlushableProvider } from "./tracer-provider.js";
3
3
  import { getConfig } from "./config.js";
4
- import { C as AUTOTEL_SAMPLING_TAIL_EVALUATED, E as AlwaysSampler, F as debugCaptureRequested, G as getContextStorage, H as createTraceContext, K as hasExplicitSpanStatus, R as isForceKept, S as AUTOTEL_SAMPLING_RATE, W as getActiveContextWithBaggage, m as runInOperationContext, q as getEventQueue, r as getSdk, t as getConfig$1, w as AUTOTEL_SAMPLING_TAIL_KEEP } from "./init-DzAdgcFM.js";
4
+ import { C as AUTOTEL_SAMPLING_RATE, D as AlwaysSampler, G as getActiveContextWithBaggage, I as debugCaptureRequested, J as getEventQueue, K as getContextStorage, T as AUTOTEL_SAMPLING_TAIL_KEEP, U as createTraceContext, h as runInOperationContext, i as getSdk, n as getConfiguredSampler, q as hasExplicitSpanStatus, t as getConfig$1, w as AUTOTEL_SAMPLING_TAIL_EVALUATED, z as isForceKept } from "./init-CsKK9vZO.js";
5
5
  import { evidenceAttribute } from "./evidence.js";
6
6
  import { setSpanName } from "./trace-helpers.js";
7
7
  import { SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -343,9 +343,11 @@ function runWithTraceContextStorage(fn) {
343
343
  if (storage.getStore()) return fn();
344
344
  return storage.run({ value: context.active() }, fn);
345
345
  }
346
+ /** Used when neither the wrapper nor `init()` chose a sampler. */
347
+ const FALLBACK_SAMPLER = new AlwaysSampler();
346
348
  function wrapWithTracingSync(fnFactory, options, variableName) {
347
349
  const { tracer, meter } = getConfig();
348
- const sampler = options.sampler || new AlwaysSampler();
350
+ const resolveSampler = () => options.sampler ?? getConfiguredSampler() ?? FALLBACK_SAMPLER;
349
351
  const spanName = getSpanName(options, fnFactory, variableName);
350
352
  const callCounter = options.withMetrics ? meter.createCounter(`${spanName}.calls`, {
351
353
  description: `Call count for ${spanName}`,
@@ -361,6 +363,7 @@ function wrapWithTracingSync(fnFactory, options, variableName) {
361
363
  args,
362
364
  metadata: {}
363
365
  };
366
+ const sampler = resolveSampler();
364
367
  const shouldSample = sampler.shouldSample(samplingContext);
365
368
  const sampleRate = sampler.sampleRate?.(samplingContext);
366
369
  const needsTailSampling = sampler.needsTailSampling?.() ?? false;
@@ -2,7 +2,7 @@ const require_rolldown_runtime = require('./rolldown-runtime-C_NdSu1c.cjs');
2
2
  const require_values = require('./values-DApyq53y.cjs');
3
3
  const require_tracer_provider = require('./tracer-provider.cjs');
4
4
  const require_config = require('./config.cjs');
5
- const require_init = require('./init-aTZKsAwy.cjs');
5
+ const require_init = require('./init-DmM7dprX.cjs');
6
6
  const require_evidence = require('./evidence.cjs');
7
7
  const require_trace_helpers = require('./trace-helpers.cjs');
8
8
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -346,9 +346,11 @@ function runWithTraceContextStorage(fn) {
346
346
  if (storage.getStore()) return fn();
347
347
  return storage.run({ value: _opentelemetry_api.context.active() }, fn);
348
348
  }
349
+ /** Used when neither the wrapper nor `init()` chose a sampler. */
350
+ const FALLBACK_SAMPLER = new require_init.AlwaysSampler();
349
351
  function wrapWithTracingSync(fnFactory, options, variableName) {
350
352
  const { tracer, meter } = require_config.getConfig();
351
- const sampler = options.sampler || new require_init.AlwaysSampler();
353
+ const resolveSampler = () => options.sampler ?? require_init.getConfiguredSampler() ?? FALLBACK_SAMPLER;
352
354
  const spanName = getSpanName(options, fnFactory, variableName);
353
355
  const callCounter = options.withMetrics ? meter.createCounter(`${spanName}.calls`, {
354
356
  description: `Call count for ${spanName}`,
@@ -364,6 +366,7 @@ function wrapWithTracingSync(fnFactory, options, variableName) {
364
366
  args,
365
367
  metadata: {}
366
368
  };
369
+ const sampler = resolveSampler();
367
370
  const shouldSample = sampler.shouldSample(samplingContext);
368
371
  const sampleRate = sampler.sampleRate?.(samplingContext);
369
372
  const needsTailSampling = sampler.needsTailSampling?.() ?? false;
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_functional = require('./functional-C925G95W.cjs');
2
+ const require_functional = require('./functional-DHlD7V0x.cjs');
3
3
 
4
4
  exports.ctx = require_functional.ctx;
5
5
  exports.getActiveTraceContext = require_functional.getActiveTraceContext;
@@ -1,3 +1,3 @@
1
- import { a as trace, c as withTracing, i as span, n as getActiveTraceContext, o as withBaggage, r as instrument, s as withNewContext, t as ctx } from "./functional-Doi0hFm3.js";
1
+ import { a as trace, c as withTracing, i as span, n as getActiveTraceContext, o as withBaggage, r as instrument, s as withNewContext, t as ctx } from "./functional-C4js2vTM.js";
2
2
 
3
3
  export { ctx, getActiveTraceContext, instrument, span, trace, withBaggage, withNewContext, withTracing };
package/dist/http.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_values = require('./values-DApyq53y.cjs');
3
3
  const require_config = require('./config.cjs');
4
- const require_init = require('./init-aTZKsAwy.cjs');
4
+ const require_init = require('./init-DmM7dprX.cjs');
5
5
  let _opentelemetry_api = require("@opentelemetry/api");
6
6
 
7
7
  //#region src/http.ts
package/dist/http.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { _ as readProperty, h as isFunction, i as asNumber, l as asString } from "./values-BenyvWKz.js";
2
2
  import { getConfig } from "./config.js";
3
- import { W as getActiveContextWithBaggage } from "./init-DzAdgcFM.js";
3
+ import { G as getActiveContextWithBaggage } from "./init-CsKK9vZO.js";
4
4
  import { SpanStatusCode, context, propagation } from "@opentelemetry/api";
5
5
 
6
6
  //#region src/http.ts
package/dist/index.cjs CHANGED
@@ -3,13 +3,13 @@ const require_rolldown_runtime = require('./rolldown-runtime-C_NdSu1c.cjs');
3
3
  const require_values = require('./values-DApyq53y.cjs');
4
4
  const require_tracer_provider = require('./tracer-provider.cjs');
5
5
  const require_structured_error = require('./structured-error--_wXpd_C.cjs');
6
- const require_init = require('./init-aTZKsAwy.cjs');
6
+ const require_init = require('./init-DmM7dprX.cjs');
7
7
  const require_filtering_span_processor = require('./filtering-span-processor.cjs');
8
8
  const require_span_name_normalizer = require('./span-name-normalizer.cjs');
9
9
  const require_attribute_redacting_processor = require('./attribute-redacting-processor.cjs');
10
10
  const require_canonical_log_line_processor = require('./canonical-log-line-processor-DolzdawH.cjs');
11
11
  const require_metric = require('./metric.cjs');
12
- const require_functional = require('./functional-C925G95W.cjs');
12
+ const require_functional = require('./functional-DHlD7V0x.cjs');
13
13
  const require_trace_helpers = require('./trace-helpers.cjs');
14
14
  const require_stable_hash = require('./stable-hash-C0pOegH_.cjs');
15
15
  const require_correlated_events = require('./correlated-events-CcuknWnU.cjs');
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { g as nonEmptyString, i as asNumber, l as asString, r as asFunction, s as asRecord, x as toError } from "./values-BenyvWKz.js";
2
2
  import { getAutotelTracer, getAutotelTracerProvider, setAutotelTracerProvider } from "./tracer-provider.js";
3
3
  import { a as flattenToAttributes, i as structuredErrorToJSON, n as getStructuredErrorAttributes, o as toAttributeValue, r as recordStructuredError, t as createStructuredError } from "./structured-error-YIuka-FE.js";
4
- import { $ as getOrCreateCorrelationId, B as resolveSamplingPreset, C as AUTOTEL_SAMPLING_TAIL_EVALUATED, E as AlwaysSampler, H as createTraceContext, I as extractLinksFromBatch, J as track, M as RandomSampler, N as UserIdSampler, P as createLinkFromHeaders, T as AdaptiveSampler, U as defineBaggageSchema, V as samplingPresets, X as generateCorrelationId, Y as CORRELATION_ID_BAGGAGE_KEY, Z as getCorrelationId, a as isInitialized, c as flush, d as getEvents, et as runWithCorrelationId, f as resetEvents, i as init, j as NeverSampler, l as shutdown, m as runInOperationContext, nt as setCorrelationIdInBaggage, o as isLoggerLocked, p as getOperationContext, q as getEventQueue, s as lockLogger, tt as setCorrelationId, u as Event, v as createStringRedactor, w as AUTOTEL_SAMPLING_TAIL_KEEP, x as AUTOTEL_DEBUG_BAGGAGE_KEY, y as BaggageSpanProcessor, z as markForceKept } from "./init-DzAdgcFM.js";
4
+ import { B as markForceKept, D as AlwaysSampler, E as AdaptiveSampler, F as createLinkFromHeaders, H as samplingPresets, J as getEventQueue, L as extractLinksFromBatch, M as NeverSampler, N as RandomSampler, P as UserIdSampler, Q as getCorrelationId, S as AUTOTEL_DEBUG_BAGGAGE_KEY, T as AUTOTEL_SAMPLING_TAIL_KEEP, U as createTraceContext, V as resolveSamplingPreset, W as defineBaggageSchema, X as CORRELATION_ID_BAGGAGE_KEY, Y as track, Z as generateCorrelationId, a as init, b as BaggageSpanProcessor, c as lockLogger, d as Event, et as getOrCreateCorrelationId, f as getEvents, h as runInOperationContext, l as flush, m as getOperationContext, nt as setCorrelationId, o as isInitialized, p as resetEvents, rt as setCorrelationIdInBaggage, s as isLoggerLocked, tt as runWithCorrelationId, u as shutdown, w as AUTOTEL_SAMPLING_TAIL_EVALUATED, y as createStringRedactor } from "./init-CsKK9vZO.js";
5
5
  import { FilteringSpanProcessor } from "./filtering-span-processor.js";
6
6
  import { NORMALIZER_PATTERNS, NORMALIZER_PRESETS, SpanNameNormalizingProcessor } from "./span-name-normalizer.js";
7
7
  import { AttributeRedactingProcessor, REDACTOR_PATTERNS, REDACTOR_PRESETS, builtinPatterns, createAttributeRedactor, createRedactedSpan, normalizeAttributeRedactorConfig } from "./attribute-redacting-processor.js";
8
8
  import { n as formatDuration } from "./canonical-log-line-processor-hFUmS__K.js";
9
9
  import { Metric, getMetrics, resetMetrics } from "./metric.js";
10
- import { a as trace$2, c as withTracing, i as span, n as getActiveTraceContext, o as withBaggage, r as instrument, s as withNewContext, t as ctx } from "./functional-Doi0hFm3.js";
10
+ import { a as trace$2, c as withTracing, i as span, n as getActiveTraceContext, o as withBaggage, r as instrument, s as withNewContext, t as ctx } from "./functional-C4js2vTM.js";
11
11
  import { createDeterministicTraceId, enrichWithTraceContext, finalizeSpan, flattenMetadata, getActiveContext, getActiveSpan, getTraceContext, getTracer, isTracing, resolveTraceUrl, runWithSpan } from "./trace-helpers.js";
12
12
  import { t as hashJson } from "./stable-hash-BkoAYVks.js";
13
13
  import { t as emitCorrelatedEvent } from "./correlated-events-DwMMbItg.js";
@@ -782,6 +782,7 @@ function validateEvent(eventName, attributes, config) {
782
782
  * Simple, no instantiation needed, auto-attaches trace context
783
783
  */
784
784
  let eventsQueue = null;
785
+ let warnedNoSubscribers = false;
785
786
  /**
786
787
  * Build autotel event context for trace correlation
787
788
  *
@@ -838,7 +839,13 @@ function getOrCreateQueue() {
838
839
  }
839
840
  if (!eventsQueue) {
840
841
  const config = getConfig();
841
- if (!config?.subscribers || config.subscribers.length === 0) return null;
842
+ if (!config?.subscribers || config.subscribers.length === 0) {
843
+ if (!warnedNoSubscribers) {
844
+ warnedNoSubscribers = true;
845
+ getLogger().warn({}, "[autotel] track() dropped an event: init() configured no subscribers. Events (including gen_ai.evaluation.result and audit events) reach a backend through init({ subscribers: [...] }).");
846
+ }
847
+ return null;
848
+ }
842
849
  eventsQueue = new EventQueue(config.subscribers);
843
850
  }
844
851
  return eventsQueue;
@@ -910,6 +917,7 @@ function getEventQueue() {
910
917
  */
911
918
  function resetEventQueue() {
912
919
  eventsQueue = null;
920
+ warnedNoSubscribers = false;
913
921
  }
914
922
 
915
923
  //#endregion
@@ -4212,6 +4220,18 @@ function warnIfNotInitialized(context) {
4212
4220
  }
4213
4221
  }
4214
4222
  /**
4223
+ * The sampler `init()` was configured with, or undefined when it was not.
4224
+ *
4225
+ * Separate from {@link getDefaultSampler} on purpose: the tracing wrapper needs
4226
+ * to know whether a sampler was *chosen*. Falling back to the production preset
4227
+ * there would start dropping spans for every app that never asked to sample.
4228
+ *
4229
+ * @internal
4230
+ */
4231
+ function getConfiguredSampler() {
4232
+ return config?.sampler;
4233
+ }
4234
+ /**
4215
4235
  * @internal Close embedded devtools if running.
4216
4236
  */
4217
4237
  async function _closeEmbeddedDevtools() {
@@ -4228,4 +4248,4 @@ function getSdk() {
4228
4248
  }
4229
4249
 
4230
4250
  //#endregion
4231
- export { getOrCreateCorrelationId as $, KeyTargetRateSampler as A, resolveSamplingPreset as B, AUTOTEL_SAMPLING_TAIL_EVALUATED as C, CompositeSampler as D, AlwaysSampler as E, debugCaptureRequested as F, getContextStorage as G, createTraceContext as H, extractLinksFromBatch as I, track as J, hasExplicitSpanStatus as K, hashUnitInterval as L, RandomSampler as M, UserIdSampler as N, DeterministicSampler as O, createLinkFromHeaders as P, getCorrelationStorage as Q, isForceKept as R, AUTOTEL_SAMPLING_RATE as S, AdaptiveSampler as T, defineBaggageSchema as U, samplingPresets as V, getActiveContextWithBaggage as W, generateCorrelationId as X, CORRELATION_ID_BAGGAGE_KEY as Y, getCorrelationId as Z, loadYamlConfigFromFile as _, isInitialized as a, TailSamplingSpanProcessor as b, flush as c, getEvents as d, runWithCorrelationId as et, resetEvents as f, loadYamlConfig as g, hasYamlConfig as h, init as i, NeverSampler as j, FeatureFlagSampler as k, shutdown as l, runInOperationContext as m, getLogger as n, setCorrelationIdInBaggage as nt, isLoggerLocked as o, getOperationContext as p, getEventQueue as q, getSdk as r, lockLogger as s, getConfig as t, setCorrelationId as tt, Event as u, createStringRedactor as v, AUTOTEL_SAMPLING_TAIL_KEEP as w, AUTOTEL_DEBUG_BAGGAGE_KEY as x, BaggageSpanProcessor as y, markForceKept as z };
4251
+ export { getCorrelationStorage as $, FeatureFlagSampler as A, markForceKept as B, AUTOTEL_SAMPLING_RATE as C, AlwaysSampler as D, AdaptiveSampler as E, createLinkFromHeaders as F, getActiveContextWithBaggage as G, samplingPresets as H, debugCaptureRequested as I, getEventQueue as J, getContextStorage as K, extractLinksFromBatch as L, NeverSampler as M, RandomSampler as N, CompositeSampler as O, UserIdSampler as P, getCorrelationId as Q, hashUnitInterval as R, AUTOTEL_DEBUG_BAGGAGE_KEY as S, AUTOTEL_SAMPLING_TAIL_KEEP as T, createTraceContext as U, resolveSamplingPreset as V, defineBaggageSchema as W, CORRELATION_ID_BAGGAGE_KEY as X, track as Y, generateCorrelationId as Z, loadYamlConfig as _, init as a, BaggageSpanProcessor as b, lockLogger as c, Event as d, getOrCreateCorrelationId as et, getEvents as f, hasYamlConfig as g, runInOperationContext as h, getSdk as i, KeyTargetRateSampler as j, DeterministicSampler as k, flush as l, getOperationContext as m, getConfiguredSampler as n, setCorrelationId as nt, isInitialized as o, resetEvents as p, hasExplicitSpanStatus as q, getLogger as r, setCorrelationIdInBaggage as rt, isLoggerLocked as s, getConfig as t, runWithCorrelationId as tt, shutdown as u, loadYamlConfigFromFile as v, AUTOTEL_SAMPLING_TAIL_EVALUATED as w, TailSamplingSpanProcessor as x, createStringRedactor as y, isForceKept as z };
@@ -786,6 +786,7 @@ function validateEvent(eventName, attributes, config) {
786
786
  * Simple, no instantiation needed, auto-attaches trace context
787
787
  */
788
788
  let eventsQueue = null;
789
+ let warnedNoSubscribers = false;
789
790
  /**
790
791
  * Build autotel event context for trace correlation
791
792
  *
@@ -842,7 +843,13 @@ function getOrCreateQueue() {
842
843
  }
843
844
  if (!eventsQueue) {
844
845
  const config = getConfig();
845
- if (!config?.subscribers || config.subscribers.length === 0) return null;
846
+ if (!config?.subscribers || config.subscribers.length === 0) {
847
+ if (!warnedNoSubscribers) {
848
+ warnedNoSubscribers = true;
849
+ getLogger().warn({}, "[autotel] track() dropped an event: init() configured no subscribers. Events (including gen_ai.evaluation.result and audit events) reach a backend through init({ subscribers: [...] }).");
850
+ }
851
+ return null;
852
+ }
846
853
  eventsQueue = new EventQueue(config.subscribers);
847
854
  }
848
855
  return eventsQueue;
@@ -914,6 +921,7 @@ function getEventQueue() {
914
921
  */
915
922
  function resetEventQueue() {
916
923
  eventsQueue = null;
924
+ warnedNoSubscribers = false;
917
925
  }
918
926
 
919
927
  //#endregion
@@ -4216,6 +4224,18 @@ function warnIfNotInitialized(context) {
4216
4224
  }
4217
4225
  }
4218
4226
  /**
4227
+ * The sampler `init()` was configured with, or undefined when it was not.
4228
+ *
4229
+ * Separate from {@link getDefaultSampler} on purpose: the tracing wrapper needs
4230
+ * to know whether a sampler was *chosen*. Falling back to the production preset
4231
+ * there would start dropping spans for every app that never asked to sample.
4232
+ *
4233
+ * @internal
4234
+ */
4235
+ function getConfiguredSampler() {
4236
+ return config?.sampler;
4237
+ }
4238
+ /**
4219
4239
  * @internal Close embedded devtools if running.
4220
4240
  */
4221
4241
  async function _closeEmbeddedDevtools() {
@@ -4394,6 +4414,12 @@ Object.defineProperty(exports, 'getConfig', {
4394
4414
  return getConfig;
4395
4415
  }
4396
4416
  });
4417
+ Object.defineProperty(exports, 'getConfiguredSampler', {
4418
+ enumerable: true,
4419
+ get: function () {
4420
+ return getConfiguredSampler;
4421
+ }
4422
+ });
4397
4423
  Object.defineProperty(exports, 'getContextStorage', {
4398
4424
  enumerable: true,
4399
4425
  get: function () {
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_init = require('./init-aTZKsAwy.cjs');
2
+ const require_init = require('./init-DmM7dprX.cjs');
3
3
  const require_node_require = require('./node-require-EifOMikz.cjs');
4
4
  let _opentelemetry_sdk_node = require("@opentelemetry/sdk-node");
5
5
  let _opentelemetry_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
@@ -1,4 +1,4 @@
1
- import { b as TailSamplingSpanProcessor, n as getLogger } from "./init-DzAdgcFM.js";
1
+ import { r as getLogger, x as TailSamplingSpanProcessor } from "./init-CsKK9vZO.js";
2
2
  import { n as safeRequire, t as requireModule } from "./node-require-DSPjaw_l.js";
3
3
  import { NodeSDK } from "@opentelemetry/sdk-node";
4
4
  import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_values = require('./values-DApyq53y.cjs');
3
- const require_init = require('./init-aTZKsAwy.cjs');
4
- const require_functional = require('./functional-C925G95W.cjs');
3
+ const require_init = require('./init-DmM7dprX.cjs');
4
+ const require_functional = require('./functional-DHlD7V0x.cjs');
5
5
  const require_correlated_events = require('./correlated-events-CcuknWnU.cjs');
6
6
  const require_trace_factory_validation = require('./trace-factory-validation-Dc8epn87.cjs');
7
7
  let _opentelemetry_api = require("@opentelemetry/api");
package/dist/messaging.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { _ as readProperty, b as toAttributeValue, c as asScalar, g as nonEmptyString, h as isFunction, s as asRecord, v as resolveValue, y as splitNameOrOptions } from "./values-BenyvWKz.js";
2
- import { I as extractLinksFromBatch, P as createLinkFromHeaders } from "./init-DzAdgcFM.js";
3
- import { c as withTracing } from "./functional-Doi0hFm3.js";
2
+ import { F as createLinkFromHeaders, L as extractLinksFromBatch } from "./init-CsKK9vZO.js";
3
+ import { c as withTracing } from "./functional-C4js2vTM.js";
4
4
  import { t as emitCorrelatedEvent } from "./correlated-events-DwMMbItg.js";
5
5
  import { t as assertTraceFactory } from "./trace-factory-validation-BjVAinz6.js";
6
6
  import { SpanKind, context, propagation } from "@opentelemetry/api";
package/dist/sampling.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_init = require('./init-aTZKsAwy.cjs');
2
+ const require_init = require('./init-DmM7dprX.cjs');
3
3
 
4
4
  exports.AUTOTEL_DEBUG_BAGGAGE_KEY = require_init.AUTOTEL_DEBUG_BAGGAGE_KEY;
5
5
  exports.AUTOTEL_SAMPLING_RATE = require_init.AUTOTEL_SAMPLING_RATE;
package/dist/sampling.js CHANGED
@@ -1,3 +1,3 @@
1
- import { A as KeyTargetRateSampler, B as resolveSamplingPreset, C as AUTOTEL_SAMPLING_TAIL_EVALUATED, D as CompositeSampler, E as AlwaysSampler, F as debugCaptureRequested, I as extractLinksFromBatch, L as hashUnitInterval, M as RandomSampler, N as UserIdSampler, O as DeterministicSampler, P as createLinkFromHeaders, R as isForceKept, S as AUTOTEL_SAMPLING_RATE, T as AdaptiveSampler, V as samplingPresets, j as NeverSampler, k as FeatureFlagSampler, w as AUTOTEL_SAMPLING_TAIL_KEEP, x as AUTOTEL_DEBUG_BAGGAGE_KEY, z as markForceKept } from "./init-DzAdgcFM.js";
1
+ import { A as FeatureFlagSampler, B as markForceKept, C as AUTOTEL_SAMPLING_RATE, D as AlwaysSampler, E as AdaptiveSampler, F as createLinkFromHeaders, H as samplingPresets, I as debugCaptureRequested, L as extractLinksFromBatch, M as NeverSampler, N as RandomSampler, O as CompositeSampler, P as UserIdSampler, R as hashUnitInterval, S as AUTOTEL_DEBUG_BAGGAGE_KEY, T as AUTOTEL_SAMPLING_TAIL_KEEP, V as resolveSamplingPreset, j as KeyTargetRateSampler, k as DeterministicSampler, w as AUTOTEL_SAMPLING_TAIL_EVALUATED, z as isForceKept } from "./init-CsKK9vZO.js";
2
2
 
3
3
  export { AUTOTEL_DEBUG_BAGGAGE_KEY, AUTOTEL_SAMPLING_RATE, AUTOTEL_SAMPLING_TAIL_EVALUATED, AUTOTEL_SAMPLING_TAIL_KEEP, AdaptiveSampler, AlwaysSampler, CompositeSampler, DeterministicSampler, FeatureFlagSampler, KeyTargetRateSampler, NeverSampler, RandomSampler, UserIdSampler, createLinkFromHeaders, debugCaptureRequested, extractLinksFromBatch, hashUnitInterval, isForceKept, markForceKept, resolveSamplingPreset, samplingPresets };
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_values = require('./values-DApyq53y.cjs');
3
- const require_functional = require('./functional-C925G95W.cjs');
3
+ const require_functional = require('./functional-DHlD7V0x.cjs');
4
4
  const require_trace_factory_validation = require('./trace-factory-validation-Dc8epn87.cjs');
5
5
  let _opentelemetry_api = require("@opentelemetry/api");
6
6
 
@@ -1,5 +1,5 @@
1
1
  import { b as toAttributeValue, g as nonEmptyString, s as asRecord } from "./values-BenyvWKz.js";
2
- import { c as withTracing } from "./functional-Doi0hFm3.js";
2
+ import { c as withTracing } from "./functional-C4js2vTM.js";
3
3
  import { t as assertTraceFactory } from "./trace-factory-validation-BjVAinz6.js";
4
4
  import { SpanKind } from "@opentelemetry/api";
5
5
 
@@ -1,4 +1,4 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_init = require('./init-aTZKsAwy.cjs');
2
+ const require_init = require('./init-DmM7dprX.cjs');
3
3
 
4
4
  exports.TailSamplingSpanProcessor = require_init.TailSamplingSpanProcessor;
@@ -1,3 +1,3 @@
1
- import { b as TailSamplingSpanProcessor } from "./init-DzAdgcFM.js";
1
+ import { x as TailSamplingSpanProcessor } from "./init-CsKK9vZO.js";
2
2
 
3
3
  export { TailSamplingSpanProcessor };
package/dist/testing.cjs CHANGED
@@ -8,6 +8,37 @@ let _opentelemetry_api = require("@opentelemetry/api");
8
8
  let node_async_hooks = require("node:async_hooks");
9
9
  node_async_hooks = require_rolldown_runtime.__toESM(node_async_hooks, 1);
10
10
 
11
+ //#region src/memory-exporter.ts
12
+ function toRecordedSpan(span) {
13
+ return {
14
+ name: span.name,
15
+ traceId: span.spanContext().traceId,
16
+ spanId: span.spanContext().spanId,
17
+ parentSpanId: span.parentSpanContext?.spanId,
18
+ durationMs: span.duration[0] * 1e3 + span.duration[1] / 1e6,
19
+ attributes: { ...span.attributes },
20
+ status: span.status
21
+ };
22
+ }
23
+ function createMemoryExporter() {
24
+ const collected = [];
25
+ return {
26
+ export(spans, resultCallback) {
27
+ for (const span of spans) collected.push(toRecordedSpan(span));
28
+ resultCallback({ code: 0 });
29
+ },
30
+ async shutdown() {},
31
+ async forceFlush() {},
32
+ spans: () => [...collected],
33
+ findSpan: (name) => collected.find((span) => span.name === name),
34
+ findSpans: (name) => collected.filter((span) => span.name === name),
35
+ reset: () => {
36
+ collected.length = 0;
37
+ }
38
+ };
39
+ }
40
+
41
+ //#endregion
11
42
  //#region src/testing.ts
12
43
  /**
13
44
  * Testing Utilities
@@ -479,6 +510,7 @@ exports.assertTraceDuration = assertTraceDuration;
479
510
  exports.assertTraceFailed = assertTraceFailed;
480
511
  exports.assertTraceSucceeded = assertTraceSucceeded;
481
512
  exports.createEventCollector = require_event_testing.createEventCollector;
513
+ exports.createMemoryExporter = createMemoryExporter;
482
514
  exports.createMockLogger = createMockLogger;
483
515
  exports.createTraceCollector = createTraceCollector;
484
516
  exports.getTraceDuration = getTraceDuration;
@@ -2,7 +2,32 @@ import { t as UnknownRecord } from "./values-CeMH-mYG.cjs";
2
2
  import { s as Logger } from "./logger-S1Fgl0g3.cjs";
3
3
  import { a as EventsValue, c as createEventCollector, i as EventsOutcome, n as EventData, o as assertEventTracked, r as EventsFunnelStep, s as assertOutcomeTracked, t as EventCollector } from "./event-testing-CBtnJXE5.cjs";
4
4
  import { SerializedSpan, TestSpanCollector, serializeSpan } from "./test-span-collector.cjs";
5
+ import { SpanExporter } from "@opentelemetry/sdk-trace-base";
5
6
  import { Attributes, Link, SpanKind, SpanStatus, SpanStatusCode } from "@opentelemetry/api";
7
+ //#region src/memory-exporter.d.ts
8
+ /** One finished span, flattened to the fields an assertion usually wants. */
9
+ interface RecordedSpan {
10
+ name: string;
11
+ traceId: string;
12
+ spanId: string;
13
+ parentSpanId: string | undefined;
14
+ /** Wall-clock duration of the span, in milliseconds. */
15
+ durationMs: number;
16
+ attributes: Attributes;
17
+ status: SpanStatus;
18
+ }
19
+ interface MemoryExporter extends SpanExporter {
20
+ /** Every span exported so far, in the order it finished. */
21
+ spans(): RecordedSpan[];
22
+ /** The first span with this name, or undefined. */
23
+ findSpan(name: string): RecordedSpan | undefined;
24
+ /** Every span with this name. */
25
+ findSpans(name: string): RecordedSpan[];
26
+ /** Discard what has been collected. */
27
+ reset(): void;
28
+ }
29
+ declare function createMemoryExporter(): MemoryExporter;
30
+ //#endregion
6
31
  //#region src/testing.d.ts
7
32
  /**
8
33
  * Note: OpenTelemetry exporters and processors have moved to dedicated modules
@@ -290,4 +315,4 @@ declare function getTraceDuration(collector: TraceCollector, operationName: stri
290
315
  */
291
316
  declare function assertTraceDuration(collector: TraceCollector, operationName: string, maxDurationMs: number): void;
292
317
  //#endregion
293
- export { type EventCollector, type EventData, type EventsFunnelStep, type EventsOutcome, type EventsValue, LogCollector, LogEntry, type SerializedSpan, SpanMatch, TestSpan, TestSpanCollector, TraceCollector, assertEventTracked, assertNoErrors, assertNoErrorsLogged, assertOutcomeTracked, assertTraceCreated, assertTraceDuration, assertTraceFailed, assertTraceSucceeded, createEventCollector, createMockLogger, createTraceCollector, getTraceDuration, serializeSpan, waitForTrace };
318
+ export { type EventCollector, type EventData, type EventsFunnelStep, type EventsOutcome, type EventsValue, LogCollector, LogEntry, type MemoryExporter, type RecordedSpan, type SerializedSpan, SpanMatch, TestSpan, TestSpanCollector, TraceCollector, assertEventTracked, assertNoErrors, assertNoErrorsLogged, assertOutcomeTracked, assertTraceCreated, assertTraceDuration, assertTraceFailed, assertTraceSucceeded, createEventCollector, createMemoryExporter, createMockLogger, createTraceCollector, getTraceDuration, serializeSpan, waitForTrace };