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