autotel 6.2.1 → 6.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.
- package/README.md +50 -1
- package/dist/auto.cjs +1 -1
- package/dist/auto.js +1 -1
- package/dist/correlation-id.cjs +1 -1
- package/dist/correlation-id.js +1 -1
- package/dist/decorators.cjs +1 -1
- package/dist/decorators.d.cts +1 -1
- package/dist/decorators.d.ts +1 -1
- package/dist/decorators.js +1 -1
- package/dist/event.cjs +1 -1
- package/dist/event.js +1 -1
- package/dist/functional-AT22vCw6.d.ts +389 -0
- package/dist/{functional-CogDARqC.cjs → functional-BEEhlVKD.cjs} +221 -283
- package/dist/{functional-2AZsH_gQ.js → functional-DbCVvGfT.js} +221 -283
- package/dist/functional-e6jw5zQj.d.cts +389 -0
- package/dist/functional.cjs +1 -1
- package/dist/functional.d.cts +2 -432
- package/dist/functional.d.ts +2 -432
- package/dist/functional.js +1 -1
- package/dist/http.cjs +1 -1
- package/dist/http.js +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/{init-Bu6wR-k_.js → init-C2fjPVkW.js} +458 -358
- package/dist/{init-BzVuFs5a.cjs → init-C5V4z7S5.cjs} +458 -358
- package/dist/{init-BoIYyB6Q.d.cts → init-Cc7-vvzj.d.cts} +42 -2
- package/dist/{init-BHp-kqLJ.d.ts → init-CsODvlau.d.ts} +42 -2
- package/dist/instrumentation.cjs +2 -2
- package/dist/instrumentation.js +2 -2
- package/dist/messaging.cjs +1 -1
- package/dist/messaging.js +1 -1
- package/dist/policy-CbpP4tFH.d.cts +147 -0
- package/dist/policy-CbpP4tFH.d.ts +147 -0
- package/dist/policy.cjs +0 -0
- package/dist/policy.d.cts +2 -0
- package/dist/policy.d.ts +2 -0
- package/dist/policy.js +0 -0
- package/dist/semantic-helpers.cjs +1 -1
- package/dist/semantic-helpers.js +1 -1
- package/dist/webhook.cjs +1 -1
- package/dist/webhook.js +1 -1
- package/dist/workflow-distributed.cjs +1 -1
- package/dist/workflow-distributed.js +1 -1
- package/dist/workflow.cjs +1 -1
- package/dist/workflow.js +1 -1
- package/dist/yaml-config.cjs +1 -0
- package/dist/yaml-config.d.cts +3 -1
- package/dist/yaml-config.d.ts +3 -1
- package/dist/yaml-config.js +1 -0
- package/package.json +6 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getForceFlushableProvider } from "./tracer-provider.js";
|
|
2
2
|
import { getConfig } from "./config.js";
|
|
3
3
|
import { AUTOTEL_SAMPLING_RATE, AUTOTEL_SAMPLING_TAIL_EVALUATED, AUTOTEL_SAMPLING_TAIL_KEEP, AlwaysSampler } from "./sampling.js";
|
|
4
|
-
import { D as getActiveContextWithBaggage, O as getContextStorage, T as createTraceContext, h as getEventQueue, m as runInOperationContext, r as getSdk, t as getConfig$1 } from "./init-
|
|
4
|
+
import { D as getActiveContextWithBaggage, O as getContextStorage, T as createTraceContext, h as getEventQueue, m as runInOperationContext, r as getSdk, t as getConfig$1 } from "./init-C2fjPVkW.js";
|
|
5
5
|
import { setSpanName } from "./trace-helpers.js";
|
|
6
6
|
import { SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
|
|
7
7
|
import * as nodeFs from "node:fs";
|
|
@@ -34,6 +34,11 @@ function captureStackTrace() {
|
|
|
34
34
|
Error.stackTraceLimit = originalStackTraceLimit;
|
|
35
35
|
return stack;
|
|
36
36
|
}
|
|
37
|
+
const INTERNAL_FRAME = new RegExp(String.raw`(?:${[
|
|
38
|
+
"variable-name-inference",
|
|
39
|
+
"functional",
|
|
40
|
+
"functional-wrapper"
|
|
41
|
+
].join("|")})\.(?:ts|js)`);
|
|
37
42
|
/**
|
|
38
43
|
* Parses the stack trace to find where trace() was called
|
|
39
44
|
*
|
|
@@ -41,7 +46,7 @@ function captureStackTrace() {
|
|
|
41
46
|
* at functionName (file:line:column)
|
|
42
47
|
* at file:line:column
|
|
43
48
|
*
|
|
44
|
-
* We skip frames until we find one that's
|
|
49
|
+
* We skip frames until we find one that's not in the functional API internals.
|
|
45
50
|
* We also need to skip one additional frame (the trace/span/instrument function itself)
|
|
46
51
|
* to get to the actual user code.
|
|
47
52
|
*/
|
|
@@ -49,7 +54,7 @@ function parseCallLocation(stack) {
|
|
|
49
54
|
const lines = stack.split("\n");
|
|
50
55
|
let skippedExternalFrame = false;
|
|
51
56
|
for (const line of lines) {
|
|
52
|
-
if (
|
|
57
|
+
if (INTERNAL_FRAME.test(line)) continue;
|
|
53
58
|
const match = line.match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/) || line.match(/^.*?([^:]+):(\d+):(\d+)/);
|
|
54
59
|
if (match) {
|
|
55
60
|
let filePath = match[1].trim();
|
|
@@ -147,68 +152,35 @@ function inferVariableNameFromCallStack() {
|
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
//#endregion
|
|
150
|
-
//#region src/functional.ts
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
* 1. trace() - Zero-ceremony HOF for single functions
|
|
156
|
-
* 2. withTracing() - Middleware-style composable wrapper
|
|
157
|
-
* 3. instrument() - Batch auto-instrumentation for modules
|
|
158
|
-
*
|
|
159
|
-
* @example trace() - Single function
|
|
160
|
-
* ```typescript
|
|
161
|
-
* export const createUser = trace(async (data) => {
|
|
162
|
-
* getActiveTraceContext()?.setAttribute('user.id', data.id)
|
|
163
|
-
* return await db.users.create(data)
|
|
164
|
-
* })
|
|
165
|
-
* ```
|
|
166
|
-
*
|
|
167
|
-
* @example withTracing() - Composable middleware
|
|
168
|
-
* ```typescript
|
|
169
|
-
* export const createUser = withTracing({
|
|
170
|
-
* name: 'user.create'
|
|
171
|
-
* })(ctx => async (data) => {
|
|
172
|
-
* ctx.setAttribute('user.id', data.id)
|
|
173
|
-
* return await db.users.create(data)
|
|
174
|
-
* })
|
|
175
|
-
* ```
|
|
176
|
-
*
|
|
177
|
-
* @example instrument() - Batch instrumentation
|
|
178
|
-
* ```typescript
|
|
179
|
-
* export default instrument({
|
|
180
|
-
* createUser: async (data) => { },
|
|
181
|
-
* updateUser: async (id, data) => { }
|
|
182
|
-
* }, { serviceName: 'user' })
|
|
183
|
-
* ```
|
|
184
|
-
*/
|
|
155
|
+
//#region src/functional-wrapper.ts
|
|
156
|
+
const FUNCTIONAL_ERROR_MESSAGE_LIMIT = 500;
|
|
157
|
+
const AUTOTEL_INPUT_ATTR = "autotel.input";
|
|
158
|
+
const AUTOTEL_OUTPUT_ATTR = "autotel.output";
|
|
159
|
+
const CAPTURE_MAX_CHARS = 4096;
|
|
185
160
|
let unknownSpanNameWarningEmitted = false;
|
|
161
|
+
function inferFunctionName(fn) {
|
|
162
|
+
if (fn.displayName) return fn.displayName;
|
|
163
|
+
if (fn.name && fn.name !== "anonymous") return fn.name;
|
|
164
|
+
const match = Function.prototype.toString.call(fn).match(/function\s+([^(\s]+)/);
|
|
165
|
+
return match?.[1] && match[1] !== "anonymous" ? match[1] : void 0;
|
|
166
|
+
}
|
|
186
167
|
function resolveVariableName(named, variableName) {
|
|
187
168
|
const suppliedFunctionName = inferFunctionName(named);
|
|
188
169
|
const callStackVariableName = suppliedFunctionName ? void 0 : inferVariableNameFromCallStack();
|
|
189
170
|
return variableName || suppliedFunctionName || callStackVariableName;
|
|
190
171
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
* {@link instrument}: the function receives its real arguments and no context
|
|
203
|
-
* is injected. Reach the active span via {@link getActiveTraceContext} inside
|
|
204
|
-
* the body (or any helper it calls).
|
|
205
|
-
*/
|
|
206
|
-
function wrapPlainWithTracing(fn, options, variableName) {
|
|
207
|
-
const effectiveVariableName = resolveVariableName(fn, variableName);
|
|
208
|
-
const factory = (_ctx) => fn;
|
|
209
|
-
return wrapWithTracingSync(factory, options, effectiveVariableName);
|
|
172
|
+
function getSpanName(options, fn, variableName) {
|
|
173
|
+
if (options.name) return options.name;
|
|
174
|
+
const fnName = variableName || inferFunctionName(fn) || "anonymous";
|
|
175
|
+
if (options.serviceName) return `${options.serviceName}.${fnName}`;
|
|
176
|
+
if (fnName !== "anonymous") return fnName;
|
|
177
|
+
const initConfig = getConfig$1();
|
|
178
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV !== "production" && !unknownSpanNameWarningEmitted && initConfig?.logger?.warn) {
|
|
179
|
+
unknownSpanNameWarningEmitted = true;
|
|
180
|
+
initConfig.logger.warn({}, "[autotel] Span name resolved to \"unknown\". Pass an explicit name, for example trace(\"operation.name\", fn).");
|
|
181
|
+
}
|
|
182
|
+
return "unknown";
|
|
210
183
|
}
|
|
211
|
-
const MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
212
184
|
function createDummyCtx() {
|
|
213
185
|
return {
|
|
214
186
|
traceId: "",
|
|
@@ -229,11 +201,6 @@ function createDummyCtx() {
|
|
|
229
201
|
getAllBaggage: () => /* @__PURE__ */ new Map()
|
|
230
202
|
};
|
|
231
203
|
}
|
|
232
|
-
/** Attribute keys for opt-in function I/O capture (see TracingOptions). */
|
|
233
|
-
const AUTOTEL_INPUT_ATTR = "autotel.input";
|
|
234
|
-
const AUTOTEL_OUTPUT_ATTR = "autotel.output";
|
|
235
|
-
const CAPTURE_MAX_CHARS = 4096;
|
|
236
|
-
/** JSON-serialize a captured value, defensively (truncate, swallow cycles). */
|
|
237
204
|
function serializeCapture(value) {
|
|
238
205
|
if (value === void 0) return void 0;
|
|
239
206
|
try {
|
|
@@ -244,41 +211,21 @@ function serializeCapture(value) {
|
|
|
244
211
|
return;
|
|
245
212
|
}
|
|
246
213
|
}
|
|
247
|
-
/** `autotel.input` from args (single arg captured directly, else the array). */
|
|
248
214
|
function captureInputAttrs(args, enabled) {
|
|
249
215
|
if (!enabled) return {};
|
|
250
|
-
const
|
|
251
|
-
return
|
|
216
|
+
const serialized = serializeCapture(args.length === 1 ? args[0] : args);
|
|
217
|
+
return serialized === void 0 ? {} : { [AUTOTEL_INPUT_ATTR]: serialized };
|
|
252
218
|
}
|
|
253
|
-
/** `autotel.output` from the return value. */
|
|
254
219
|
function captureOutputAttrs(result, enabled) {
|
|
255
220
|
if (!enabled) return {};
|
|
256
|
-
const
|
|
257
|
-
return
|
|
258
|
-
}
|
|
259
|
-
const INSTRUMENTED_SYMBOL = Symbol.for("autotel.functional.instrumented");
|
|
260
|
-
function hasInstrumentationFlag(value) {
|
|
261
|
-
return (typeof value === "function" || typeof value === "object") && value !== null && Boolean(value[INSTRUMENTED_SYMBOL]);
|
|
221
|
+
const serialized = serializeCapture(result);
|
|
222
|
+
return serialized === void 0 ? {} : { [AUTOTEL_OUTPUT_ATTR]: serialized };
|
|
262
223
|
}
|
|
263
|
-
/**
|
|
264
|
-
* Truncate error message to prevent span bloat
|
|
265
|
-
*/
|
|
266
224
|
function truncateErrorMessage(message) {
|
|
267
|
-
|
|
268
|
-
return `${message.slice(0, MAX_ERROR_MESSAGE_LENGTH)}... (truncated)`;
|
|
225
|
+
return message.length <= 500 ? message : `${message.slice(0, 500)}... (truncated)`;
|
|
269
226
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
* `trace()` execution paths (immediate + wrapper, sync + async) so the outcome
|
|
273
|
-
* rules live in exactly one place. Ends the span; the caller flushes (await/void)
|
|
274
|
-
* and rethrows.
|
|
275
|
-
*
|
|
276
|
-
* If `isError` classifies the throw as expected control flow — e.g. a framework
|
|
277
|
-
* `redirect()` / `notFound()` signal — it is recorded as a success outcome with
|
|
278
|
-
* an OK status and no exception. Otherwise it is recorded as an error.
|
|
279
|
-
*/
|
|
280
|
-
function finalizeThrownSpan(error, isError, ctx) {
|
|
281
|
-
const { span, spanName, duration, callCounter, durationHistogram, handleTailSampling, extraAttributes } = ctx;
|
|
227
|
+
function finalizeThrownSpan(error, isError, finalizeContext) {
|
|
228
|
+
const { span, spanName, duration, callCounter, durationHistogram, handleTailSampling, extraAttributes } = finalizeContext;
|
|
282
229
|
const baseAttributes = {
|
|
283
230
|
...extraAttributes,
|
|
284
231
|
"operation.name": spanName,
|
|
@@ -323,45 +270,197 @@ function finalizeThrownSpan(error, isError, ctx) {
|
|
|
323
270
|
"exception.type": error instanceof Error ? error.constructor.name : "Error",
|
|
324
271
|
"exception.message": truncatedMessage
|
|
325
272
|
});
|
|
326
|
-
if (error instanceof Error && error.stack) span.setAttribute("exception.stack", error.stack.slice(0,
|
|
273
|
+
if (error instanceof Error && error.stack) span.setAttribute("exception.stack", error.stack.slice(0, 500));
|
|
327
274
|
const thrown = error instanceof Error ? error : new Error(String(error));
|
|
328
275
|
span.recordException(thrown);
|
|
329
276
|
handleTailSampling(false, duration, thrown);
|
|
330
277
|
span.end();
|
|
331
278
|
}
|
|
332
|
-
|
|
333
|
-
* Try to infer function name from function properties
|
|
334
|
-
* Checks for displayName, name, or other metadata that might be set
|
|
335
|
-
*/
|
|
336
|
-
function inferFunctionName(fn) {
|
|
337
|
-
const displayName = fn.displayName;
|
|
338
|
-
if (displayName) return displayName;
|
|
339
|
-
if (fn.name && fn.name !== "anonymous" && fn.name !== "") return fn.name;
|
|
340
|
-
const match = Function.prototype.toString.call(fn).match(/function\s+([^(\s]+)/);
|
|
341
|
-
if (match && match[1] && match[1] !== "anonymous") return match[1];
|
|
342
|
-
}
|
|
343
|
-
/**
|
|
344
|
-
* Determine span name using priority:
|
|
345
|
-
* 1. Explicit name option
|
|
346
|
-
* 2. serviceName + functionName
|
|
347
|
-
* 3. Inferred from function/variable name (including stack trace fallback)
|
|
348
|
-
* 4. Fallback to 'unknown'
|
|
349
|
-
*/
|
|
350
|
-
function getSpanName(options, fn, variableName) {
|
|
351
|
-
if (options.name) return options.name;
|
|
352
|
-
let fnName = variableName || inferFunctionName(fn);
|
|
353
|
-
fnName = fnName || "anonymous";
|
|
354
|
-
if (options.serviceName) return `${options.serviceName}.${fnName}`;
|
|
355
|
-
if (fnName && fnName !== "anonymous") return fnName;
|
|
279
|
+
function createRootTelemetryFlusher(options, isRootSpan) {
|
|
356
280
|
const initConfig = getConfig$1();
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
281
|
+
const shouldFlush = options.flushOnRootSpanEnd ?? initConfig?.flushOnRootSpanEnd ?? true;
|
|
282
|
+
const shouldFlushSpans = initConfig?.forceFlushOnShutdown ?? false;
|
|
283
|
+
return async () => {
|
|
284
|
+
if (!shouldFlush || !isRootSpan) return;
|
|
285
|
+
try {
|
|
286
|
+
const queue = getEventQueue();
|
|
287
|
+
if (queue && queue.size() > 0) await queue.flush();
|
|
288
|
+
if (shouldFlushSpans) await getForceFlushableProvider(getSdk())?.forceFlush();
|
|
289
|
+
} catch (error) {
|
|
290
|
+
getConfig$1()?.logger?.error?.({ err: error instanceof Error ? error : void 0 }, `[autotel] Auto-flush failed${error instanceof Error ? "" : `: ${String(error)}`}`);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function runWithTraceContextStorage(fn) {
|
|
295
|
+
const storage = getContextStorage();
|
|
296
|
+
if (storage.getStore()) return fn();
|
|
297
|
+
return storage.run({ value: context.active() }, fn);
|
|
298
|
+
}
|
|
299
|
+
function wrapWithTracingSync(fnFactory, options, variableName) {
|
|
300
|
+
const { tracer, meter } = getConfig();
|
|
301
|
+
const sampler = options.sampler || new AlwaysSampler();
|
|
302
|
+
const spanName = getSpanName(options, fnFactory, variableName);
|
|
303
|
+
const callCounter = options.withMetrics ? meter.createCounter(`${spanName}.calls`, {
|
|
304
|
+
description: `Call count for ${spanName}`,
|
|
305
|
+
unit: "1"
|
|
306
|
+
}) : void 0;
|
|
307
|
+
const durationHistogram = options.withMetrics ? meter.createHistogram(`${spanName}.duration`, {
|
|
308
|
+
description: `Duration for ${spanName}`,
|
|
309
|
+
unit: "ms"
|
|
310
|
+
}) : void 0;
|
|
311
|
+
function wrappedFunction(...args) {
|
|
312
|
+
const samplingContext = {
|
|
313
|
+
operationName: spanName,
|
|
314
|
+
args,
|
|
315
|
+
metadata: {}
|
|
316
|
+
};
|
|
317
|
+
const shouldSample = sampler.shouldSample(samplingContext);
|
|
318
|
+
const sampleRate = sampler.sampleRate?.(samplingContext);
|
|
319
|
+
const needsTailSampling = sampler.needsTailSampling?.() ?? false;
|
|
320
|
+
if (!shouldSample && !needsTailSampling) return fnFactory(createDummyCtx()).call(this, ...args);
|
|
321
|
+
const startTime = performance.now();
|
|
322
|
+
const flushRootTelemetry = createRootTelemetryFlusher(options, options.startNewRoot || trace.getActiveSpan() === void 0);
|
|
323
|
+
const spanOptions = {};
|
|
324
|
+
if (options.startNewRoot) spanOptions.root = true;
|
|
325
|
+
if (options.spanKind !== void 0) spanOptions.kind = options.spanKind;
|
|
326
|
+
return tracer.startActiveSpan(spanName, spanOptions, getActiveContextWithBaggage(), (span) => runInOperationContext(spanName, () => runWithTraceContextStorage(() => {
|
|
327
|
+
let shouldKeepSpan = true;
|
|
328
|
+
setSpanName(span, spanName);
|
|
329
|
+
if (sampleRate !== void 0 && sampleRate > 1) span.setAttribute(AUTOTEL_SAMPLING_RATE, sampleRate);
|
|
330
|
+
const fn = fnFactory(createTraceContext(span));
|
|
331
|
+
const argsAttributes = {
|
|
332
|
+
...captureInputAttrs(args, options.captureInput),
|
|
333
|
+
...options.attributesFromArgs ? options.attributesFromArgs(args) : {}
|
|
334
|
+
};
|
|
335
|
+
const handleTailSampling = (success, duration, error) => {
|
|
336
|
+
if (needsTailSampling && sampler.shouldKeepTrace) {
|
|
337
|
+
shouldKeepSpan = sampler.shouldKeepTrace(samplingContext, {
|
|
338
|
+
success,
|
|
339
|
+
duration,
|
|
340
|
+
error
|
|
341
|
+
});
|
|
342
|
+
span.setAttribute(AUTOTEL_SAMPLING_TAIL_KEEP, shouldKeepSpan);
|
|
343
|
+
span.setAttribute(AUTOTEL_SAMPLING_TAIL_EVALUATED, true);
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
const onSuccess = (result) => {
|
|
347
|
+
const duration = performance.now() - startTime;
|
|
348
|
+
callCounter?.add(1, {
|
|
349
|
+
operation: spanName,
|
|
350
|
+
status: "success"
|
|
351
|
+
});
|
|
352
|
+
durationHistogram?.record(duration, {
|
|
353
|
+
operation: spanName,
|
|
354
|
+
status: "success"
|
|
355
|
+
});
|
|
356
|
+
const resultAttributes = {
|
|
357
|
+
...captureOutputAttrs(result, options.captureOutput),
|
|
358
|
+
...options.attributesFromResult ? options.attributesFromResult(result) : {}
|
|
359
|
+
};
|
|
360
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
361
|
+
span.setAttributes({
|
|
362
|
+
...argsAttributes,
|
|
363
|
+
...resultAttributes,
|
|
364
|
+
"operation.name": spanName,
|
|
365
|
+
"code.function": spanName,
|
|
366
|
+
"operation.duration": duration,
|
|
367
|
+
"operation.success": true
|
|
368
|
+
});
|
|
369
|
+
handleTailSampling(true, duration);
|
|
370
|
+
span.end();
|
|
371
|
+
return result;
|
|
372
|
+
};
|
|
373
|
+
const onError = (error) => {
|
|
374
|
+
finalizeThrownSpan(error, options.isError, {
|
|
375
|
+
span,
|
|
376
|
+
spanName,
|
|
377
|
+
duration: performance.now() - startTime,
|
|
378
|
+
callCounter,
|
|
379
|
+
durationHistogram,
|
|
380
|
+
handleTailSampling,
|
|
381
|
+
extraAttributes: argsAttributes
|
|
382
|
+
});
|
|
383
|
+
throw error;
|
|
384
|
+
};
|
|
385
|
+
try {
|
|
386
|
+
callCounter?.add(1, {
|
|
387
|
+
operation: spanName,
|
|
388
|
+
status: "started"
|
|
389
|
+
});
|
|
390
|
+
const result = context.with(trace.setSpan(getActiveContextWithBaggage(), span), () => fn.call(this, ...args));
|
|
391
|
+
if (result instanceof Promise) return result.then(async (value) => {
|
|
392
|
+
const completed = onSuccess(value);
|
|
393
|
+
await flushRootTelemetry();
|
|
394
|
+
return completed;
|
|
395
|
+
}, async (error) => {
|
|
396
|
+
try {
|
|
397
|
+
return onError(error);
|
|
398
|
+
} finally {
|
|
399
|
+
await flushRootTelemetry();
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
const completed = onSuccess(result);
|
|
403
|
+
flushRootTelemetry();
|
|
404
|
+
return completed;
|
|
405
|
+
} catch (error) {
|
|
406
|
+
try {
|
|
407
|
+
return onError(error);
|
|
408
|
+
} finally {
|
|
409
|
+
flushRootTelemetry();
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
})));
|
|
360
413
|
}
|
|
361
|
-
|
|
414
|
+
Object.defineProperty(wrappedFunction, "name", {
|
|
415
|
+
value: variableName || "trace",
|
|
416
|
+
configurable: true
|
|
417
|
+
});
|
|
418
|
+
return wrappedFunction;
|
|
362
419
|
}
|
|
420
|
+
/** Wrap an explicit `(ctx) => (...args) => result` context factory. */
|
|
421
|
+
function wrapFactoryWithTracing(factory, options, variableName) {
|
|
422
|
+
return wrapWithTracingSync(factory, options, resolveVariableName(factory, variableName));
|
|
423
|
+
}
|
|
424
|
+
/** Wrap a plain function without injecting a context argument. */
|
|
425
|
+
function wrapPlainWithTracing(fn, options, variableName) {
|
|
426
|
+
return wrapWithTracingSync((_ctx) => fn, options, resolveVariableName(fn, variableName));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
//#endregion
|
|
430
|
+
//#region src/functional.ts
|
|
363
431
|
/**
|
|
364
|
-
*
|
|
432
|
+
* Functional API for non-class code
|
|
433
|
+
*
|
|
434
|
+
* Three approaches for different use cases:
|
|
435
|
+
* 1. trace() - Zero-ceremony HOF for single functions
|
|
436
|
+
* 2. withTracing() - Middleware-style composable wrapper
|
|
437
|
+
* 3. instrument() - Batch auto-instrumentation for modules
|
|
438
|
+
*
|
|
439
|
+
* @example trace() - Single function
|
|
440
|
+
* ```typescript
|
|
441
|
+
* export const createUser = trace(async (data) => {
|
|
442
|
+
* getActiveTraceContext()?.setAttribute('user.id', data.id)
|
|
443
|
+
* return await db.users.create(data)
|
|
444
|
+
* })
|
|
445
|
+
* ```
|
|
446
|
+
*
|
|
447
|
+
* @example withTracing() - Composable middleware
|
|
448
|
+
* ```typescript
|
|
449
|
+
* export const createUser = withTracing({
|
|
450
|
+
* name: 'user.create'
|
|
451
|
+
* })(ctx => async (data) => {
|
|
452
|
+
* ctx.setAttribute('user.id', data.id)
|
|
453
|
+
* return await db.users.create(data)
|
|
454
|
+
* })
|
|
455
|
+
* ```
|
|
456
|
+
*
|
|
457
|
+
* @example instrument() - Batch instrumentation
|
|
458
|
+
* ```typescript
|
|
459
|
+
* export default instrument({
|
|
460
|
+
* createUser: async (data) => { },
|
|
461
|
+
* updateUser: async (id, data) => { }
|
|
462
|
+
* }, { serviceName: 'user' })
|
|
463
|
+
* ```
|
|
365
464
|
*/
|
|
366
465
|
function shouldSkip(key, fn, skip) {
|
|
367
466
|
if (key.startsWith("_")) return true;
|
|
@@ -443,167 +542,6 @@ const ctx = new Proxy({}, {
|
|
|
443
542
|
return Object.getOwnPropertyDescriptor(ctxValue, prop);
|
|
444
543
|
}
|
|
445
544
|
});
|
|
446
|
-
/**
|
|
447
|
-
* Build the root-operation flush policy once and reuse it for wrapped and
|
|
448
|
-
* immediate execution. Async operations await this function; sync operations
|
|
449
|
-
* trigger it without blocking.
|
|
450
|
-
*/
|
|
451
|
-
function createRootTelemetryFlusher(options, isRootSpan) {
|
|
452
|
-
const initConfig = getConfig$1();
|
|
453
|
-
const shouldFlush = options.flushOnRootSpanEnd ?? initConfig?.flushOnRootSpanEnd ?? true;
|
|
454
|
-
const shouldFlushSpans = initConfig?.forceFlushOnShutdown ?? false;
|
|
455
|
-
return async () => {
|
|
456
|
-
if (!shouldFlush || !isRootSpan) return;
|
|
457
|
-
try {
|
|
458
|
-
const queue = getEventQueue();
|
|
459
|
-
if (queue && queue.size() > 0) await queue.flush();
|
|
460
|
-
if (shouldFlushSpans) await getForceFlushableProvider(getSdk())?.forceFlush();
|
|
461
|
-
} catch (error) {
|
|
462
|
-
(getConfig$1()?.logger)?.error?.({ err: error instanceof Error ? error : void 0 }, `[autotel] Auto-flush failed${error instanceof Error ? "" : `: ${String(error)}`}`);
|
|
463
|
-
}
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Give every root operation its own baggage context without replacing a
|
|
468
|
-
* caller's existing scope. AsyncLocalStorage keeps the scope alive until an
|
|
469
|
-
* async result settles and restores the previous store afterwards.
|
|
470
|
-
*/
|
|
471
|
-
function runWithTraceContextStorage(fn) {
|
|
472
|
-
const storage = getContextStorage();
|
|
473
|
-
if (storage.getStore()) return fn();
|
|
474
|
-
return storage.run({ value: context.active() }, fn);
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Core tracing wrapper for sync functions (internal implementation)
|
|
478
|
-
*/
|
|
479
|
-
function wrapWithTracingSync(fnFactory, options, variableName) {
|
|
480
|
-
if (hasInstrumentationFlag(fnFactory)) {}
|
|
481
|
-
const config = getConfig();
|
|
482
|
-
const tracer = config.tracer;
|
|
483
|
-
const meter = config.meter;
|
|
484
|
-
const sampler = options.sampler || new AlwaysSampler();
|
|
485
|
-
const spanName = getSpanName(options, fnFactory, variableName);
|
|
486
|
-
const callCounter = options.withMetrics ? meter.createCounter(`${spanName}.calls`, {
|
|
487
|
-
description: `Call count for ${spanName}`,
|
|
488
|
-
unit: "1"
|
|
489
|
-
}) : void 0;
|
|
490
|
-
const durationHistogram = options.withMetrics ? meter.createHistogram(`${spanName}.duration`, {
|
|
491
|
-
description: `Duration for ${spanName}`,
|
|
492
|
-
unit: "ms"
|
|
493
|
-
}) : void 0;
|
|
494
|
-
function wrappedFunction(...args) {
|
|
495
|
-
const samplingContext = {
|
|
496
|
-
operationName: spanName,
|
|
497
|
-
args,
|
|
498
|
-
metadata: {}
|
|
499
|
-
};
|
|
500
|
-
const shouldSample = sampler.shouldSample(samplingContext);
|
|
501
|
-
const sampleRate = sampler.sampleRate?.(samplingContext);
|
|
502
|
-
const needsTailSampling = sampler.needsTailSampling?.() ?? false;
|
|
503
|
-
if (!shouldSample && !needsTailSampling) return fnFactory(createDummyCtx()).call(this, ...args);
|
|
504
|
-
const startTime = performance.now();
|
|
505
|
-
const flushRootTelemetry = createRootTelemetryFlusher(options, options.startNewRoot || trace.getActiveSpan() === void 0);
|
|
506
|
-
const spanOptions = {};
|
|
507
|
-
if (options.startNewRoot) spanOptions.root = true;
|
|
508
|
-
if (options.spanKind !== void 0) spanOptions.kind = options.spanKind;
|
|
509
|
-
const parentContext = getActiveContextWithBaggage();
|
|
510
|
-
return tracer.startActiveSpan(spanName, spanOptions, parentContext, (span) => {
|
|
511
|
-
return runInOperationContext(spanName, () => runWithTraceContextStorage(() => {
|
|
512
|
-
let shouldKeepSpan = true;
|
|
513
|
-
setSpanName(span, spanName);
|
|
514
|
-
if (sampleRate !== void 0 && sampleRate > 1) span.setAttribute(AUTOTEL_SAMPLING_RATE, sampleRate);
|
|
515
|
-
const fn = fnFactory(createTraceContext(span));
|
|
516
|
-
const argsAttributes = {
|
|
517
|
-
...captureInputAttrs(args, options.captureInput),
|
|
518
|
-
...options.attributesFromArgs ? options.attributesFromArgs(args) : {}
|
|
519
|
-
};
|
|
520
|
-
const handleTailSampling = (success, duration, error) => {
|
|
521
|
-
if (needsTailSampling && sampler.shouldKeepTrace) {
|
|
522
|
-
shouldKeepSpan = sampler.shouldKeepTrace(samplingContext, {
|
|
523
|
-
success,
|
|
524
|
-
duration,
|
|
525
|
-
error
|
|
526
|
-
});
|
|
527
|
-
span.setAttribute(AUTOTEL_SAMPLING_TAIL_KEEP, shouldKeepSpan);
|
|
528
|
-
span.setAttribute(AUTOTEL_SAMPLING_TAIL_EVALUATED, true);
|
|
529
|
-
}
|
|
530
|
-
};
|
|
531
|
-
const onSuccess = (result) => {
|
|
532
|
-
const duration = performance.now() - startTime;
|
|
533
|
-
callCounter?.add(1, {
|
|
534
|
-
operation: spanName,
|
|
535
|
-
status: "success"
|
|
536
|
-
});
|
|
537
|
-
durationHistogram?.record(duration, {
|
|
538
|
-
operation: spanName,
|
|
539
|
-
status: "success"
|
|
540
|
-
});
|
|
541
|
-
const resultAttributes = {
|
|
542
|
-
...captureOutputAttrs(result, options.captureOutput),
|
|
543
|
-
...options.attributesFromResult ? options.attributesFromResult(result) : {}
|
|
544
|
-
};
|
|
545
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
546
|
-
span.setAttributes({
|
|
547
|
-
...argsAttributes,
|
|
548
|
-
...resultAttributes,
|
|
549
|
-
"operation.name": spanName,
|
|
550
|
-
"code.function": spanName,
|
|
551
|
-
"operation.duration": duration,
|
|
552
|
-
"operation.success": true
|
|
553
|
-
});
|
|
554
|
-
handleTailSampling(true, duration);
|
|
555
|
-
span.end();
|
|
556
|
-
return result;
|
|
557
|
-
};
|
|
558
|
-
const onError = (error) => {
|
|
559
|
-
finalizeThrownSpan(error, options.isError, {
|
|
560
|
-
span,
|
|
561
|
-
spanName,
|
|
562
|
-
duration: performance.now() - startTime,
|
|
563
|
-
callCounter,
|
|
564
|
-
durationHistogram,
|
|
565
|
-
handleTailSampling,
|
|
566
|
-
extraAttributes: argsAttributes
|
|
567
|
-
});
|
|
568
|
-
throw error;
|
|
569
|
-
};
|
|
570
|
-
try {
|
|
571
|
-
callCounter?.add(1, {
|
|
572
|
-
operation: spanName,
|
|
573
|
-
status: "started"
|
|
574
|
-
});
|
|
575
|
-
const result = context.with(getActiveContextWithBaggage(), () => fn.call(this, ...args));
|
|
576
|
-
if (result instanceof Promise) return result.then(async (value) => {
|
|
577
|
-
const completed = onSuccess(value);
|
|
578
|
-
await flushRootTelemetry();
|
|
579
|
-
return completed;
|
|
580
|
-
}, async (error) => {
|
|
581
|
-
try {
|
|
582
|
-
return onError(error);
|
|
583
|
-
} finally {
|
|
584
|
-
await flushRootTelemetry();
|
|
585
|
-
}
|
|
586
|
-
});
|
|
587
|
-
const completed = onSuccess(result);
|
|
588
|
-
flushRootTelemetry();
|
|
589
|
-
return completed;
|
|
590
|
-
} catch (error) {
|
|
591
|
-
try {
|
|
592
|
-
return onError(error);
|
|
593
|
-
} finally {
|
|
594
|
-
flushRootTelemetry();
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
}));
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
wrappedFunction[INSTRUMENTED_SYMBOL] = true;
|
|
601
|
-
Object.defineProperty(wrappedFunction, "name", {
|
|
602
|
-
value: variableName || "trace",
|
|
603
|
-
configurable: true
|
|
604
|
-
});
|
|
605
|
-
return wrappedFunction;
|
|
606
|
-
}
|
|
607
545
|
function trace$1(fnOrNameOrOptions, maybeFn) {
|
|
608
546
|
if (typeof fnOrNameOrOptions === "function") return wrapPlainWithTracing(fnOrNameOrOptions, {});
|
|
609
547
|
if (!maybeFn) throw new Error("trace(name|options, fn): fn is required");
|
|
@@ -694,7 +632,7 @@ function span(nameOrOptions, fn) {
|
|
|
694
632
|
span.end();
|
|
695
633
|
return resolved;
|
|
696
634
|
}).catch((error) => {
|
|
697
|
-
const errorMessage = error instanceof Error ? error.message.slice(0,
|
|
635
|
+
const errorMessage = error instanceof Error ? error.message.slice(0, 500) : String(error).slice(0, 500);
|
|
698
636
|
span.setAttribute("error.message", errorMessage);
|
|
699
637
|
span.setStatus({
|
|
700
638
|
code: SpanStatusCode.ERROR,
|
|
@@ -710,7 +648,7 @@ function span(nameOrOptions, fn) {
|
|
|
710
648
|
return result;
|
|
711
649
|
}
|
|
712
650
|
} catch (error) {
|
|
713
|
-
const errorMessage = error instanceof Error ? error.message.slice(0,
|
|
651
|
+
const errorMessage = error instanceof Error ? error.message.slice(0, 500) : String(error).slice(0, 500);
|
|
714
652
|
span.setAttribute("error.message", errorMessage);
|
|
715
653
|
span.setStatus({
|
|
716
654
|
code: SpanStatusCode.ERROR,
|