autotel 6.2.0 → 6.3.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/config.cjs +8 -1
- package/dist/config.js +8 -1
- package/dist/correlation-id.cjs +1 -1
- package/dist/correlation-id.js +1 -1
- package/dist/decorators.cjs +1 -1
- package/dist/decorators.js +1 -1
- package/dist/event.cjs +1 -1
- package/dist/event.js +1 -1
- package/dist/{functional-DYAZiYxr.js → functional-DCBhFool.js} +1 -1
- package/dist/{functional-D545J0Ba.cjs → functional-TbnG2lu7.cjs} +1 -1
- package/dist/functional.cjs +1 -1
- 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 +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/{init-CzNFWUd8.cjs → init-BneZ7WJ8.cjs} +459 -351
- package/dist/{init-BHp-kqLJ.d.ts → init-C1i2_2zl.d.ts} +24 -2
- package/dist/{init-BoIYyB6Q.d.cts → init-CfVWN4ND.d.cts} +24 -2
- package/dist/{init-DDkh2brw.js → init-Qn4gIW1i.js} +459 -351
- 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/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.d.cts +1 -1
- package/dist/yaml-config.d.ts +1 -1
- package/package.json +1 -1
|
@@ -17,12 +17,12 @@ import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
|
17
17
|
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
|
18
18
|
import { context, propagation, trace } from "@opentelemetry/api";
|
|
19
19
|
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
20
|
-
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
|
21
|
-
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
22
|
-
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
|
23
20
|
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
|
24
21
|
import { constants } from "node:os";
|
|
25
22
|
import * as nodeAsyncHooks from "node:async_hooks";
|
|
23
|
+
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
|
24
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
25
|
+
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
|
26
26
|
|
|
27
27
|
//#region src/posthog-logs.ts
|
|
28
28
|
var RedactingLogRecordProcessor = class {
|
|
@@ -325,6 +325,39 @@ function resolveDevtoolsConfig(config) {
|
|
|
325
325
|
//#endregion
|
|
326
326
|
//#region src/process-handlers.ts
|
|
327
327
|
let removeOwnedHandlers = [];
|
|
328
|
+
/**
|
|
329
|
+
* Tracked separately from `removeOwnedHandlers` because the exit flush outlives
|
|
330
|
+
* a call to `installProcessHandlers`, which clears that list before installing
|
|
331
|
+
* its own signal listeners.
|
|
332
|
+
*/
|
|
333
|
+
let removeExitFlush;
|
|
334
|
+
/**
|
|
335
|
+
* Shared across every path that can end the process: signals, fatal errors and
|
|
336
|
+
* a clean exit. They can overlap — a container stopping a job that has just
|
|
337
|
+
* finished its work sends SIGTERM while the exit flush is still draining — and
|
|
338
|
+
* a second shutdown would tear down queues the first is still using.
|
|
339
|
+
*/
|
|
340
|
+
let shutdownInFlight;
|
|
341
|
+
/**
|
|
342
|
+
* The clean-exit flush, while it runs. Doubles as the latch that keeps a
|
|
343
|
+
* re-emitted `beforeExit` from flushing twice, and as the thing a shutdown
|
|
344
|
+
* waits on rather than tearing down queues mid-drain.
|
|
345
|
+
*/
|
|
346
|
+
let exitFlushInFlight;
|
|
347
|
+
const DEFAULT_SHUTDOWN_TIMEOUT_MS = 2e3;
|
|
348
|
+
function runShutdownOnce(shutdown, timeoutMs) {
|
|
349
|
+
if (shutdownInFlight) return shutdownInFlight;
|
|
350
|
+
let timeoutHandle;
|
|
351
|
+
const shutdownAttempt = Promise.resolve(exitFlushInFlight).then(shutdown).catch(() => void 0);
|
|
352
|
+
const timeout = new Promise((resolve) => {
|
|
353
|
+
timeoutHandle = setTimeout(resolve, timeoutMs);
|
|
354
|
+
timeoutHandle.unref();
|
|
355
|
+
});
|
|
356
|
+
shutdownInFlight = Promise.race([shutdownAttempt, timeout]).then(() => {
|
|
357
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
358
|
+
});
|
|
359
|
+
return shutdownInFlight;
|
|
360
|
+
}
|
|
328
361
|
const DEFAULT_SIGNALS = ["SIGTERM", "SIGINT"];
|
|
329
362
|
function signalExitCode(signal) {
|
|
330
363
|
return 128 + constants.signals[signal];
|
|
@@ -343,21 +376,7 @@ function reportFatalError(error, event) {
|
|
|
343
376
|
}
|
|
344
377
|
function installProcessHandlers(config, shutdown) {
|
|
345
378
|
uninstallProcessHandlers();
|
|
346
|
-
|
|
347
|
-
const runShutdownOnce = () => {
|
|
348
|
-
if (shutdownInFlight) return shutdownInFlight;
|
|
349
|
-
const timeoutMs = config.shutdownTimeoutMs ?? 2e3;
|
|
350
|
-
let timeoutHandle;
|
|
351
|
-
const shutdownAttempt = Promise.resolve().then(shutdown).catch(() => void 0);
|
|
352
|
-
const timeout = new Promise((resolve) => {
|
|
353
|
-
timeoutHandle = setTimeout(resolve, timeoutMs);
|
|
354
|
-
timeoutHandle.unref();
|
|
355
|
-
});
|
|
356
|
-
shutdownInFlight = Promise.race([shutdownAttempt, timeout]).then(() => {
|
|
357
|
-
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
358
|
-
});
|
|
359
|
-
return shutdownInFlight;
|
|
360
|
-
};
|
|
379
|
+
const timeoutMs = config.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
|
361
380
|
let exitScheduled = false;
|
|
362
381
|
let resolvedExitCode = 0;
|
|
363
382
|
let fatalLatched = false;
|
|
@@ -370,7 +389,7 @@ function installProcessHandlers(config, shutdown) {
|
|
|
370
389
|
} else if (!exitScheduled && !fatalLatched) resolvedExitCode = exitCode;
|
|
371
390
|
if (exitScheduled) return;
|
|
372
391
|
exitScheduled = true;
|
|
373
|
-
runShutdownOnce().then(() => {
|
|
392
|
+
runShutdownOnce(shutdown, timeoutMs).then(() => {
|
|
374
393
|
process.exit(resolvedExitCode);
|
|
375
394
|
});
|
|
376
395
|
};
|
|
@@ -401,9 +420,43 @@ function installProcessHandlers(config, shutdown) {
|
|
|
401
420
|
});
|
|
402
421
|
}
|
|
403
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* Flush telemetry when the process runs to completion.
|
|
425
|
+
*
|
|
426
|
+
* The signal and fatal-error handlers above cover a process that is stopped or
|
|
427
|
+
* that crashes. Neither fires when a script simply finishes: the event loop
|
|
428
|
+
* drains and Node exits, taking whatever the batch span processor was still
|
|
429
|
+
* holding with it. `beforeExit` is the only hook for that case, and it is the
|
|
430
|
+
* one that matters for CLIs, cron jobs, CI steps and serverless handlers.
|
|
431
|
+
*
|
|
432
|
+
* A flush, never a shutdown: `beforeExit` fires on *any* event-loop drain, not
|
|
433
|
+
* only the final one, so tearing the SDK down here would silently kill
|
|
434
|
+
* telemetry in a process that goes on to do more work.
|
|
435
|
+
*/
|
|
436
|
+
function installExitFlush(flushTelemetry, timeoutMs = DEFAULT_SHUTDOWN_TIMEOUT_MS) {
|
|
437
|
+
removeExitFlush?.();
|
|
438
|
+
exitFlushInFlight = void 0;
|
|
439
|
+
const listener = (code) => {
|
|
440
|
+
if (exitFlushInFlight || shutdownInFlight) return;
|
|
441
|
+
const deadline = setTimeout(() => {
|
|
442
|
+
process.exit(process.exitCode ?? code);
|
|
443
|
+
}, timeoutMs);
|
|
444
|
+
exitFlushInFlight = flushTelemetry().catch(() => void 0).finally(() => {
|
|
445
|
+
clearTimeout(deadline);
|
|
446
|
+
});
|
|
447
|
+
};
|
|
448
|
+
process.on("beforeExit", listener);
|
|
449
|
+
removeExitFlush = () => {
|
|
450
|
+
process.removeListener("beforeExit", listener);
|
|
451
|
+
removeExitFlush = void 0;
|
|
452
|
+
};
|
|
453
|
+
}
|
|
404
454
|
function uninstallProcessHandlers() {
|
|
405
455
|
for (const removeHandler of removeOwnedHandlers) removeHandler();
|
|
406
456
|
removeOwnedHandlers = [];
|
|
457
|
+
removeExitFlush?.();
|
|
458
|
+
shutdownInFlight = void 0;
|
|
459
|
+
exitFlushInFlight = void 0;
|
|
407
460
|
}
|
|
408
461
|
|
|
409
462
|
//#endregion
|
|
@@ -911,6 +964,11 @@ function getSubscriberName(subscriber) {
|
|
|
911
964
|
return (subscriber.constructor?.name || "unknown").replace(/Subscriber$/i, "").toLowerCase();
|
|
912
965
|
}
|
|
913
966
|
/**
|
|
967
|
+
* Subscribers whose `shutdown()` has run. Terminal for the client they wrap, so
|
|
968
|
+
* a queue rebuilt from the same config must not reuse them.
|
|
969
|
+
*/
|
|
970
|
+
const shutDownSubscribers = /* @__PURE__ */ new WeakSet();
|
|
971
|
+
/**
|
|
914
972
|
* Events queue with batching and backpressure
|
|
915
973
|
*
|
|
916
974
|
* Features:
|
|
@@ -932,13 +990,15 @@ var EventQueue = class {
|
|
|
932
990
|
observableCleanups = [];
|
|
933
991
|
subscriberHealthy = /* @__PURE__ */ new Map();
|
|
934
992
|
constructor(subscribers, config) {
|
|
935
|
-
|
|
993
|
+
const live = subscribers.filter((s) => !shutDownSubscribers.has(s));
|
|
994
|
+
if (live.length < subscribers.length) getLogger().warn({ subscribers: subscribers.filter((s) => shutDownSubscribers.has(s)).map((s) => getSubscriberName(s)) }, "[autotel] Subscriber already shut down, events will not be delivered; pass new subscriber instances to init()");
|
|
995
|
+
this.subscribers = live;
|
|
936
996
|
this.config = {
|
|
937
997
|
...DEFAULT_CONFIG$2,
|
|
938
998
|
...config
|
|
939
999
|
};
|
|
940
1000
|
this.rateLimiter = this.config.rateLimit ? new TokenBucketRateLimiter(this.config.rateLimit) : null;
|
|
941
|
-
for (const subscriber of subscribers) {
|
|
1001
|
+
for (const subscriber of this.subscribers) {
|
|
942
1002
|
const name = getSubscriberName(subscriber);
|
|
943
1003
|
this.subscriberHealthy.set(name, true);
|
|
944
1004
|
}
|
|
@@ -1232,6 +1292,20 @@ var EventQueue = class {
|
|
|
1232
1292
|
async shutdown() {
|
|
1233
1293
|
this.isShuttingDown = true;
|
|
1234
1294
|
await this.flush();
|
|
1295
|
+
const drains = await Promise.allSettled(this.subscribers.map(async (subscriber) => {
|
|
1296
|
+
if (!subscriber.shutdown) return;
|
|
1297
|
+
shutDownSubscribers.add(subscriber);
|
|
1298
|
+
return subscriber.shutdown();
|
|
1299
|
+
}));
|
|
1300
|
+
for (const [index, drain] of drains.entries()) {
|
|
1301
|
+
if (drain.status !== "rejected") continue;
|
|
1302
|
+
const subscriberName = getSubscriberName(this.subscribers[index]);
|
|
1303
|
+
this.subscriberHealthy.set(subscriberName, false);
|
|
1304
|
+
getLogger().error({
|
|
1305
|
+
subscriber: subscriberName,
|
|
1306
|
+
err: drain.reason
|
|
1307
|
+
}, "[autotel] Subscriber shutdown failed, buffered events may be lost");
|
|
1308
|
+
}
|
|
1235
1309
|
}
|
|
1236
1310
|
/**
|
|
1237
1311
|
* Cleanup observable metric callbacks to prevent memory leaks
|
|
@@ -2461,47 +2535,7 @@ async function shutdown() {
|
|
|
2461
2535
|
}
|
|
2462
2536
|
|
|
2463
2537
|
//#endregion
|
|
2464
|
-
//#region src/
|
|
2465
|
-
/**
|
|
2466
|
-
* Simplified initialization for autotel
|
|
2467
|
-
*
|
|
2468
|
-
* Single init() function with sensible defaults.
|
|
2469
|
-
* Replaces initInstrumentation() and separate events config.
|
|
2470
|
-
*/
|
|
2471
|
-
/**
|
|
2472
|
-
* Silent logger (no-op) - used as default when user doesn't provide one.
|
|
2473
|
-
* Internal autotel logs are silent by default to avoid spam.
|
|
2474
|
-
* Users can import { autotelLogger } from 'autotel/logger' to create their own.
|
|
2475
|
-
*/
|
|
2476
|
-
const silentLogger = {
|
|
2477
|
-
info: () => {},
|
|
2478
|
-
warn: () => {},
|
|
2479
|
-
error: () => {},
|
|
2480
|
-
debug: () => {}
|
|
2481
|
-
};
|
|
2482
|
-
/**
|
|
2483
|
-
* Adapts an Autotel Sampler to the OTel SDK Sampler interface.
|
|
2484
|
-
*/
|
|
2485
|
-
function toOtelSampler(sampler) {
|
|
2486
|
-
return {
|
|
2487
|
-
shouldSample(_context, _traceId, spanName, _spanKind, _attributes, links) {
|
|
2488
|
-
const samplingContext = {
|
|
2489
|
-
operationName: spanName,
|
|
2490
|
-
args: [],
|
|
2491
|
-
links
|
|
2492
|
-
};
|
|
2493
|
-
const shouldTrace = sampler.shouldSample(samplingContext);
|
|
2494
|
-
const rate = sampler.sampleRate?.(samplingContext);
|
|
2495
|
-
return {
|
|
2496
|
-
decision: shouldTrace ? SamplingDecision.RECORD_AND_SAMPLED : SamplingDecision.NOT_RECORD,
|
|
2497
|
-
...shouldTrace && rate !== void 0 && rate > 1 ? { attributes: { [AUTOTEL_SAMPLING_RATE]: rate } } : {}
|
|
2498
|
-
};
|
|
2499
|
-
},
|
|
2500
|
-
toString() {
|
|
2501
|
-
return `AutotelSamplerAdapter`;
|
|
2502
|
-
}
|
|
2503
|
-
};
|
|
2504
|
-
}
|
|
2538
|
+
//#region src/otlp-exporters.ts
|
|
2505
2539
|
let OTLPTraceExporterPROTO;
|
|
2506
2540
|
let OTLPMetricExporterPROTO;
|
|
2507
2541
|
let OTLPLogExporterPROTO;
|
|
@@ -2615,62 +2649,9 @@ function formatEndpointUrl(endpoint, signal, protocol) {
|
|
|
2615
2649
|
if (!endpoint.endsWith(`/v1/${signal}`)) return `${endpoint}/v1/${signal}`;
|
|
2616
2650
|
return endpoint;
|
|
2617
2651
|
}
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
let sdk = null;
|
|
2622
|
-
let warnedOnce = false;
|
|
2623
|
-
let logger = silentLogger;
|
|
2624
|
-
let validationConfig = null;
|
|
2625
|
-
let eventsConfig = null;
|
|
2626
|
-
let _stringRedactor = null;
|
|
2627
|
-
function acceptsStringRedactor(subscriber) {
|
|
2628
|
-
return typeof subscriber === "object" && subscriber !== null && "setStringRedactor" in subscriber && typeof subscriber.setStringRedactor === "function";
|
|
2629
|
-
}
|
|
2630
|
-
let _optionalRequire = safeRequire;
|
|
2631
|
-
let _devtoolsClose = null;
|
|
2632
|
-
const LOG_LEVELS = {
|
|
2633
|
-
debug: 0,
|
|
2634
|
-
info: 1,
|
|
2635
|
-
warn: 2,
|
|
2636
|
-
error: 3
|
|
2637
|
-
};
|
|
2638
|
-
/**
|
|
2639
|
-
* Lock the logger to prevent further `init()` calls.
|
|
2640
|
-
* Use this when framework plugins set up instrumentation and you want
|
|
2641
|
-
* to prevent accidental re-initialization from user code.
|
|
2642
|
-
*/
|
|
2643
|
-
function lockLogger() {
|
|
2644
|
-
locked = true;
|
|
2645
|
-
}
|
|
2646
|
-
/**
|
|
2647
|
-
* Check if the logger has been locked.
|
|
2648
|
-
*/
|
|
2649
|
-
function isLoggerLocked() {
|
|
2650
|
-
return locked;
|
|
2651
|
-
}
|
|
2652
|
-
function createSilentLogger() {
|
|
2653
|
-
return {
|
|
2654
|
-
info: () => {},
|
|
2655
|
-
warn: () => {},
|
|
2656
|
-
error: () => {},
|
|
2657
|
-
debug: () => {}
|
|
2658
|
-
};
|
|
2659
|
-
}
|
|
2660
|
-
function wrapLogger(base, silent, minLevel) {
|
|
2661
|
-
if (silent) return createSilentLogger();
|
|
2662
|
-
const threshold = LOG_LEVELS[minLevel];
|
|
2663
|
-
const wrap = (fn, level) => {
|
|
2664
|
-
if (LOG_LEVELS[level] < threshold) return (() => {});
|
|
2665
|
-
return ((...args) => fn(...args));
|
|
2666
|
-
};
|
|
2667
|
-
return {
|
|
2668
|
-
debug: wrap(base.debug, "debug"),
|
|
2669
|
-
info: wrap(base.info, "info"),
|
|
2670
|
-
warn: wrap(base.warn, "warn"),
|
|
2671
|
-
error: wrap(base.error, "error")
|
|
2672
|
-
};
|
|
2673
|
-
}
|
|
2652
|
+
|
|
2653
|
+
//#endregion
|
|
2654
|
+
//#region src/config-resolution.ts
|
|
2674
2655
|
/**
|
|
2675
2656
|
* Resolve the effective attribute redactor. Explicit config wins (`false`
|
|
2676
2657
|
* disables). Otherwise the `AUTOTEL_REDACT_PII` env var controls it, and as a
|
|
@@ -2698,6 +2679,17 @@ function resolveAttributeRedactor(explicit, environment) {
|
|
|
2698
2679
|
}
|
|
2699
2680
|
return environment === "production" ? "default" : void 0;
|
|
2700
2681
|
}
|
|
2682
|
+
/**
|
|
2683
|
+
* Read a duration-in-milliseconds environment variable, ignoring anything that
|
|
2684
|
+
* is not a positive number so a typo falls back to the SDK default rather than
|
|
2685
|
+
* throwing at startup.
|
|
2686
|
+
*/
|
|
2687
|
+
function readMillisEnv(name) {
|
|
2688
|
+
const raw = process.env[name]?.trim();
|
|
2689
|
+
if (!raw) return void 0;
|
|
2690
|
+
const value = Number(raw);
|
|
2691
|
+
return Number.isFinite(value) && value > 0 ? value : void 0;
|
|
2692
|
+
}
|
|
2701
2693
|
function detectEnvironmentAttributes() {
|
|
2702
2694
|
const attrs = {};
|
|
2703
2695
|
const commitSha = process.env.COMMIT_SHA || process.env.GITHUB_SHA || process.env.VERCEL_GIT_COMMIT_SHA || process.env.CF_PAGES_COMMIT_SHA || process.env.AWS_CODEPIPELINE_EXECUTION_ID;
|
|
@@ -2774,103 +2766,344 @@ function destinationSupportsSignal(destination, signal) {
|
|
|
2774
2766
|
return destination.signals ? destination.signals.has(signal) : true;
|
|
2775
2767
|
}
|
|
2776
2768
|
/**
|
|
2777
|
-
*
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
*
|
|
2789
|
-
*
|
|
2790
|
-
*
|
|
2791
|
-
* init({
|
|
2792
|
-
* service: 'my-app',
|
|
2793
|
-
* subscribers: [new PostHogSubscriber({ apiKey: '...' })]
|
|
2794
|
-
* })
|
|
2795
|
-
* ```
|
|
2796
|
-
*
|
|
2797
|
-
* @example Observe in Jaeger
|
|
2798
|
-
* ```typescript
|
|
2799
|
-
* import { JaegerExporter } from '@opentelemetry/exporter-jaeger'
|
|
2800
|
-
*
|
|
2801
|
-
* init({
|
|
2802
|
-
* service: 'my-app',
|
|
2803
|
-
* spanExporter: new JaegerExporter({ endpoint: 'http://localhost:14268/api/traces' })
|
|
2804
|
-
* })
|
|
2805
|
-
* ```
|
|
2806
|
-
*
|
|
2807
|
-
* @example Observe in Zipkin
|
|
2808
|
-
* ```typescript
|
|
2809
|
-
* import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'
|
|
2810
|
-
*
|
|
2811
|
-
* init({
|
|
2812
|
-
* service: 'my-app',
|
|
2813
|
-
* spanExporter: new ZipkinExporter({ url: 'http://localhost:9411/api/v2/spans' })
|
|
2814
|
-
* })
|
|
2815
|
-
* ```
|
|
2816
|
-
*
|
|
2817
|
-
* @example Observe in Datadog
|
|
2818
|
-
* ```typescript
|
|
2819
|
-
* import { DatadogSpanProcessor } from '@opentelemetry/exporter-datadog'
|
|
2820
|
-
*
|
|
2821
|
-
* init({
|
|
2822
|
-
* service: 'my-app',
|
|
2823
|
-
* spanProcessor: new DatadogSpanProcessor({ ... })
|
|
2824
|
-
* })
|
|
2825
|
-
* ```
|
|
2769
|
+
* Auto-detect version from package.json
|
|
2770
|
+
*/
|
|
2771
|
+
function detectVersion() {
|
|
2772
|
+
try {
|
|
2773
|
+
const fs = requireModule("node:fs");
|
|
2774
|
+
return JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`, "utf8")).version || "1.0.0";
|
|
2775
|
+
} catch {
|
|
2776
|
+
return "1.0.0";
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
/**
|
|
2780
|
+
* Detect hostname for resource attributes.
|
|
2781
|
+
* Supports Datadog conventions (DD_HOSTNAME) and falls back to system hostname.
|
|
2826
2782
|
*
|
|
2827
|
-
*
|
|
2828
|
-
*
|
|
2829
|
-
*
|
|
2783
|
+
* Priority order:
|
|
2784
|
+
* 1. DD_HOSTNAME environment variable (Datadog convention)
|
|
2785
|
+
* 2. HOSTNAME environment variable (common Unix convention)
|
|
2786
|
+
* 3. os.hostname() (system hostname)
|
|
2830
2787
|
*
|
|
2831
|
-
*
|
|
2832
|
-
* service: 'my-app',
|
|
2833
|
-
* spanProcessor: new SimpleSpanProcessor(new ConsoleSpanExporter())
|
|
2834
|
-
* })
|
|
2835
|
-
* ```
|
|
2788
|
+
* @returns hostname string or undefined if detection fails
|
|
2836
2789
|
*/
|
|
2837
|
-
function
|
|
2838
|
-
if (
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
...cfg,
|
|
2845
|
-
resourceAttributes: {
|
|
2846
|
-
...envConfig.resourceAttributes,
|
|
2847
|
-
...yamlConfig.resourceAttributes,
|
|
2848
|
-
...detectEnvironmentAttributes(),
|
|
2849
|
-
...cfg.resourceAttributes
|
|
2850
|
-
},
|
|
2851
|
-
headers: cfg.headers ?? yamlConfig.headers ?? envConfig.headers
|
|
2852
|
-
};
|
|
2853
|
-
const resolvedRedactor = resolveAttributeRedactor(mergedConfig.attributeRedactor, mergedConfig.environment || process.env.NODE_ENV || "development");
|
|
2854
|
-
if (resolvedRedactor === void 0) mergedConfig.attributeRedactor = void 0;
|
|
2855
|
-
else {
|
|
2856
|
-
const normalizedRedactor = normalizeAttributeRedactorConfig(resolvedRedactor);
|
|
2857
|
-
if (!normalizedRedactor) throw new Error("Invalid attributeRedactor config");
|
|
2858
|
-
mergedConfig.attributeRedactor = normalizedRedactor;
|
|
2790
|
+
function detectHostname() {
|
|
2791
|
+
if (process.env.DD_HOSTNAME) return process.env.DD_HOSTNAME;
|
|
2792
|
+
if (process.env.HOSTNAME) return process.env.HOSTNAME;
|
|
2793
|
+
try {
|
|
2794
|
+
return requireModule("node:os").hostname();
|
|
2795
|
+
} catch {
|
|
2796
|
+
return;
|
|
2859
2797
|
}
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
const
|
|
2872
|
-
|
|
2873
|
-
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
//#endregion
|
|
2801
|
+
//#region src/auto-instrumentations.ts
|
|
2802
|
+
/**
|
|
2803
|
+
* Extract instrumentation class names from instrumentation instances
|
|
2804
|
+
* Used to detect duplicates between manual and auto instrumentations
|
|
2805
|
+
*/
|
|
2806
|
+
function getInstrumentationNames(instrumentations) {
|
|
2807
|
+
const names = /* @__PURE__ */ new Set();
|
|
2808
|
+
if (!instrumentations) return names;
|
|
2809
|
+
for (const instrumentation of instrumentations) if (instrumentation && typeof instrumentation === "object") names.add(instrumentation.constructor.name);
|
|
2810
|
+
return names;
|
|
2811
|
+
}
|
|
2812
|
+
/**
|
|
2813
|
+
* Map common instrumentation class names to their package names
|
|
2814
|
+
* Used to disable auto-instrumentations when user provides manual configs
|
|
2815
|
+
*/
|
|
2816
|
+
const INSTRUMENTATION_CLASS_TO_PACKAGE = {
|
|
2817
|
+
HttpInstrumentation: "@opentelemetry/instrumentation-http",
|
|
2818
|
+
HttpsInstrumentation: "@opentelemetry/instrumentation-http",
|
|
2819
|
+
ExpressInstrumentation: "@opentelemetry/instrumentation-express",
|
|
2820
|
+
FastifyInstrumentation: "@opentelemetry/instrumentation-fastify",
|
|
2821
|
+
MongoDBInstrumentation: "@opentelemetry/instrumentation-mongodb",
|
|
2822
|
+
MongooseInstrumentation: "@opentelemetry/instrumentation-mongoose",
|
|
2823
|
+
PrismaInstrumentation: "@opentelemetry/instrumentation-prisma",
|
|
2824
|
+
PinoInstrumentation: "@opentelemetry/instrumentation-pino",
|
|
2825
|
+
WinstonInstrumentation: "@opentelemetry/instrumentation-winston",
|
|
2826
|
+
RedisInstrumentation: "@opentelemetry/instrumentation-redis",
|
|
2827
|
+
GraphQLInstrumentation: "@opentelemetry/instrumentation-graphql",
|
|
2828
|
+
GrpcInstrumentation: "@opentelemetry/instrumentation-grpc",
|
|
2829
|
+
IORedisInstrumentation: "@opentelemetry/instrumentation-ioredis",
|
|
2830
|
+
KnexInstrumentation: "@opentelemetry/instrumentation-knex",
|
|
2831
|
+
NestJsInstrumentation: "@opentelemetry/instrumentation-nestjs-core",
|
|
2832
|
+
PgInstrumentation: "@opentelemetry/instrumentation-pg",
|
|
2833
|
+
MySQLInstrumentation: "@opentelemetry/instrumentation-mysql",
|
|
2834
|
+
MySQL2Instrumentation: "@opentelemetry/instrumentation-mysql2"
|
|
2835
|
+
};
|
|
2836
|
+
/**
|
|
2837
|
+
* Detect if we're running in ESM mode
|
|
2838
|
+
*/
|
|
2839
|
+
function isESMMode() {
|
|
2840
|
+
try {
|
|
2841
|
+
const fs = requireModule("node:fs");
|
|
2842
|
+
try {
|
|
2843
|
+
return JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`, "utf8")).type === "module";
|
|
2844
|
+
} catch {
|
|
2845
|
+
return false;
|
|
2846
|
+
}
|
|
2847
|
+
} catch {
|
|
2848
|
+
return false;
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
/**
|
|
2852
|
+
* Lazy-load auto-instrumentations (optional peer dependency)
|
|
2853
|
+
* Only loads when integrations config is truthy, avoiding ~40+ package imports at startup.
|
|
2854
|
+
*/
|
|
2855
|
+
function loadNodeAutoInstrumentations() {
|
|
2856
|
+
try {
|
|
2857
|
+
return requireModule("@opentelemetry/auto-instrumentations-node").getNodeAutoInstrumentations;
|
|
2858
|
+
} catch {
|
|
2859
|
+
const isESM = isESMMode();
|
|
2860
|
+
const baseMessage = "@opentelemetry/auto-instrumentations-node not found.";
|
|
2861
|
+
if (isESM) throw new Error(`${baseMessage}\n\nESM Setup Required:
|
|
2862
|
+
1. Install as a direct dependency: pnpm add @opentelemetry/auto-instrumentations-node
|
|
2863
|
+
2. Create instrumentation.mjs with:
|
|
2864
|
+
import 'autotel/register'; // MUST be first!
|
|
2865
|
+
import { init } from 'autotel';
|
|
2866
|
+
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
|
2867
|
+
init({ service: "my-app", instrumentations: getNodeAutoInstrumentations() });
|
|
2868
|
+
3. Run with: tsx --import ./instrumentation.mjs src/index.ts
|
|
2869
|
+
|
|
2870
|
+
See: https://github.com/jagreehal/autotel#esm-setup`);
|
|
2871
|
+
throw new Error(`${baseMessage} Install it: pnpm add @opentelemetry/auto-instrumentations-node`);
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
/**
|
|
2875
|
+
* Injectable loader for testing. Set to override the default loader.
|
|
2876
|
+
* @internal
|
|
2877
|
+
*/
|
|
2878
|
+
let _autoInstrumentationsLoader = null;
|
|
2879
|
+
/**
|
|
2880
|
+
* Get auto-instrumentations based on simple integration names
|
|
2881
|
+
* Excludes instrumentations that are manually provided to avoid conflicts
|
|
2882
|
+
*/
|
|
2883
|
+
function getAutoInstrumentations(integrations, manualInstrumentationNames = /* @__PURE__ */ new Set()) {
|
|
2884
|
+
if (integrations === false) return [];
|
|
2885
|
+
const getNodeAutoInstrumentations = _autoInstrumentationsLoader ? _autoInstrumentationsLoader() : loadNodeAutoInstrumentations();
|
|
2886
|
+
const exclusionConfig = {};
|
|
2887
|
+
for (const className of manualInstrumentationNames) {
|
|
2888
|
+
const packageName = INSTRUMENTATION_CLASS_TO_PACKAGE[className];
|
|
2889
|
+
if (packageName) exclusionConfig[packageName] = { enabled: false };
|
|
2890
|
+
}
|
|
2891
|
+
if (integrations === true) {
|
|
2892
|
+
if (Object.keys(exclusionConfig).length > 0) return getNodeAutoInstrumentations(exclusionConfig);
|
|
2893
|
+
return getNodeAutoInstrumentations();
|
|
2894
|
+
}
|
|
2895
|
+
if (Array.isArray(integrations)) {
|
|
2896
|
+
const config = { ...exclusionConfig };
|
|
2897
|
+
for (const name of integrations) {
|
|
2898
|
+
const packageName = `@opentelemetry/instrumentation-${name}`;
|
|
2899
|
+
if (!exclusionConfig[packageName]) config[packageName] = { enabled: true };
|
|
2900
|
+
}
|
|
2901
|
+
return getNodeAutoInstrumentations(config);
|
|
2902
|
+
}
|
|
2903
|
+
const config = {
|
|
2904
|
+
...exclusionConfig,
|
|
2905
|
+
...integrations
|
|
2906
|
+
};
|
|
2907
|
+
for (const packageName of Object.keys(exclusionConfig)) if (Object.keys(integrations).find((key) => packageName.includes(key))) config[packageName] = { enabled: false };
|
|
2908
|
+
return getNodeAutoInstrumentations(config);
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
//#endregion
|
|
2912
|
+
//#region src/init.ts
|
|
2913
|
+
/**
|
|
2914
|
+
* Simplified initialization for autotel
|
|
2915
|
+
*
|
|
2916
|
+
* Single init() function with sensible defaults.
|
|
2917
|
+
* Replaces initInstrumentation() and separate events config.
|
|
2918
|
+
*/
|
|
2919
|
+
/**
|
|
2920
|
+
* Silent logger (no-op) - used as default when user doesn't provide one.
|
|
2921
|
+
* Internal autotel logs are silent by default to avoid spam.
|
|
2922
|
+
* Users can import { autotelLogger } from 'autotel/logger' to create their own.
|
|
2923
|
+
*/
|
|
2924
|
+
const silentLogger = {
|
|
2925
|
+
info: () => {},
|
|
2926
|
+
warn: () => {},
|
|
2927
|
+
error: () => {},
|
|
2928
|
+
debug: () => {}
|
|
2929
|
+
};
|
|
2930
|
+
/**
|
|
2931
|
+
* Adapts an Autotel Sampler to the OTel SDK Sampler interface.
|
|
2932
|
+
*/
|
|
2933
|
+
function toOtelSampler(sampler) {
|
|
2934
|
+
return {
|
|
2935
|
+
shouldSample(_context, _traceId, spanName, _spanKind, _attributes, links) {
|
|
2936
|
+
const samplingContext = {
|
|
2937
|
+
operationName: spanName,
|
|
2938
|
+
args: [],
|
|
2939
|
+
links
|
|
2940
|
+
};
|
|
2941
|
+
const shouldTrace = sampler.shouldSample(samplingContext);
|
|
2942
|
+
const rate = sampler.sampleRate?.(samplingContext);
|
|
2943
|
+
return {
|
|
2944
|
+
decision: shouldTrace ? SamplingDecision.RECORD_AND_SAMPLED : SamplingDecision.NOT_RECORD,
|
|
2945
|
+
...shouldTrace && rate !== void 0 && rate > 1 ? { attributes: { [AUTOTEL_SAMPLING_RATE]: rate } } : {}
|
|
2946
|
+
};
|
|
2947
|
+
},
|
|
2948
|
+
toString() {
|
|
2949
|
+
return `AutotelSamplerAdapter`;
|
|
2950
|
+
}
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
let initialized = false;
|
|
2954
|
+
let locked = false;
|
|
2955
|
+
let config = null;
|
|
2956
|
+
let sdk = null;
|
|
2957
|
+
let warnedOnce = false;
|
|
2958
|
+
let logger = silentLogger;
|
|
2959
|
+
let validationConfig = null;
|
|
2960
|
+
let eventsConfig = null;
|
|
2961
|
+
let _stringRedactor = null;
|
|
2962
|
+
function acceptsStringRedactor(subscriber) {
|
|
2963
|
+
return typeof subscriber === "object" && subscriber !== null && "setStringRedactor" in subscriber && typeof subscriber.setStringRedactor === "function";
|
|
2964
|
+
}
|
|
2965
|
+
let _optionalRequire = safeRequire;
|
|
2966
|
+
let _devtoolsClose = null;
|
|
2967
|
+
const LOG_LEVELS = {
|
|
2968
|
+
debug: 0,
|
|
2969
|
+
info: 1,
|
|
2970
|
+
warn: 2,
|
|
2971
|
+
error: 3
|
|
2972
|
+
};
|
|
2973
|
+
/**
|
|
2974
|
+
* Lock the logger to prevent further `init()` calls.
|
|
2975
|
+
* Use this when framework plugins set up instrumentation and you want
|
|
2976
|
+
* to prevent accidental re-initialization from user code.
|
|
2977
|
+
*/
|
|
2978
|
+
function lockLogger() {
|
|
2979
|
+
locked = true;
|
|
2980
|
+
}
|
|
2981
|
+
/**
|
|
2982
|
+
* Check if the logger has been locked.
|
|
2983
|
+
*/
|
|
2984
|
+
function isLoggerLocked() {
|
|
2985
|
+
return locked;
|
|
2986
|
+
}
|
|
2987
|
+
function createSilentLogger() {
|
|
2988
|
+
return {
|
|
2989
|
+
info: () => {},
|
|
2990
|
+
warn: () => {},
|
|
2991
|
+
error: () => {},
|
|
2992
|
+
debug: () => {}
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
function wrapLogger(base, silent, minLevel) {
|
|
2996
|
+
if (silent) return createSilentLogger();
|
|
2997
|
+
const threshold = LOG_LEVELS[minLevel];
|
|
2998
|
+
const wrap = (fn, level) => {
|
|
2999
|
+
if (LOG_LEVELS[level] < threshold) return (() => {});
|
|
3000
|
+
return ((...args) => fn(...args));
|
|
3001
|
+
};
|
|
3002
|
+
return {
|
|
3003
|
+
debug: wrap(base.debug, "debug"),
|
|
3004
|
+
info: wrap(base.info, "info"),
|
|
3005
|
+
warn: wrap(base.warn, "warn"),
|
|
3006
|
+
error: wrap(base.error, "error")
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
/**
|
|
3010
|
+
* Initialize autotel - Write Once, Observe Everywhere
|
|
3011
|
+
*
|
|
3012
|
+
* Follows OpenTelemetry standards: opinionated defaults with full flexibility
|
|
3013
|
+
* Idempotent: multiple calls are safe, last one wins
|
|
3014
|
+
*
|
|
3015
|
+
* @example Minimal setup (OTLP default)
|
|
3016
|
+
* ```typescript
|
|
3017
|
+
* init({ service: 'my-app' })
|
|
3018
|
+
* ```
|
|
3019
|
+
*
|
|
3020
|
+
* @example With events (observe in PostHog, Mixpanel, etc.)
|
|
3021
|
+
* ```typescript
|
|
3022
|
+
* import { PostHogSubscriber } from 'autotel-subscribers/posthog';
|
|
3023
|
+
*
|
|
3024
|
+
* init({
|
|
3025
|
+
* service: 'my-app',
|
|
3026
|
+
* subscribers: [new PostHogSubscriber({ apiKey: '...' })]
|
|
3027
|
+
* })
|
|
3028
|
+
* ```
|
|
3029
|
+
*
|
|
3030
|
+
* @example Observe in Jaeger
|
|
3031
|
+
* ```typescript
|
|
3032
|
+
* import { JaegerExporter } from '@opentelemetry/exporter-jaeger'
|
|
3033
|
+
*
|
|
3034
|
+
* init({
|
|
3035
|
+
* service: 'my-app',
|
|
3036
|
+
* spanExporter: new JaegerExporter({ endpoint: 'http://localhost:14268/api/traces' })
|
|
3037
|
+
* })
|
|
3038
|
+
* ```
|
|
3039
|
+
*
|
|
3040
|
+
* @example Observe in Zipkin
|
|
3041
|
+
* ```typescript
|
|
3042
|
+
* import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'
|
|
3043
|
+
*
|
|
3044
|
+
* init({
|
|
3045
|
+
* service: 'my-app',
|
|
3046
|
+
* spanExporter: new ZipkinExporter({ url: 'http://localhost:9411/api/v2/spans' })
|
|
3047
|
+
* })
|
|
3048
|
+
* ```
|
|
3049
|
+
*
|
|
3050
|
+
* @example Observe in Datadog
|
|
3051
|
+
* ```typescript
|
|
3052
|
+
* import { DatadogSpanProcessor } from '@opentelemetry/exporter-datadog'
|
|
3053
|
+
*
|
|
3054
|
+
* init({
|
|
3055
|
+
* service: 'my-app',
|
|
3056
|
+
* spanProcessor: new DatadogSpanProcessor({ ... })
|
|
3057
|
+
* })
|
|
3058
|
+
* ```
|
|
3059
|
+
*
|
|
3060
|
+
* @example Console output (dev)
|
|
3061
|
+
* ```typescript
|
|
3062
|
+
* import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
3063
|
+
*
|
|
3064
|
+
* init({
|
|
3065
|
+
* service: 'my-app',
|
|
3066
|
+
* spanProcessor: new SimpleSpanProcessor(new ConsoleSpanExporter())
|
|
3067
|
+
* })
|
|
3068
|
+
* ```
|
|
3069
|
+
*/
|
|
3070
|
+
function init(cfg) {
|
|
3071
|
+
if (locked) return;
|
|
3072
|
+
const envConfig = resolveConfigFromEnv();
|
|
3073
|
+
const yamlConfig = loadYamlConfig() ?? {};
|
|
3074
|
+
const mergedConfig = {
|
|
3075
|
+
...envConfig,
|
|
3076
|
+
...yamlConfig,
|
|
3077
|
+
...cfg,
|
|
3078
|
+
resourceAttributes: {
|
|
3079
|
+
...envConfig.resourceAttributes,
|
|
3080
|
+
...yamlConfig.resourceAttributes,
|
|
3081
|
+
...detectEnvironmentAttributes(),
|
|
3082
|
+
...cfg.resourceAttributes
|
|
3083
|
+
},
|
|
3084
|
+
headers: cfg.headers ?? yamlConfig.headers ?? envConfig.headers
|
|
3085
|
+
};
|
|
3086
|
+
const resolvedRedactor = resolveAttributeRedactor(mergedConfig.attributeRedactor, mergedConfig.environment || process.env.NODE_ENV || "development");
|
|
3087
|
+
if (resolvedRedactor === void 0) mergedConfig.attributeRedactor = void 0;
|
|
3088
|
+
else {
|
|
3089
|
+
const normalizedRedactor = normalizeAttributeRedactorConfig(resolvedRedactor);
|
|
3090
|
+
if (!normalizedRedactor) throw new Error("Invalid attributeRedactor config");
|
|
3091
|
+
mergedConfig.attributeRedactor = normalizedRedactor;
|
|
3092
|
+
}
|
|
3093
|
+
const devtoolsConfig = resolveDevtoolsConfig(mergedConfig.devtools);
|
|
3094
|
+
if (devtoolsConfig.enabled && mergedConfig.logs === void 0) mergedConfig.logs = true;
|
|
3095
|
+
const silent = mergedConfig.silent ?? false;
|
|
3096
|
+
const minLevel = mergedConfig.minLevel ?? "info";
|
|
3097
|
+
logger = wrapLogger(mergedConfig.logger || silentLogger, silent, minLevel);
|
|
3098
|
+
if (initialized) logger.warn({}, "[autotel] init() called again - last config wins. This may cause unexpected behavior.");
|
|
3099
|
+
config = mergedConfig;
|
|
3100
|
+
validationConfig = mergedConfig.validation || null;
|
|
3101
|
+
eventsConfig = mergedConfig.events || null;
|
|
3102
|
+
let endpoint = mergedConfig.endpoint ?? devtoolsConfig.endpoint;
|
|
3103
|
+
const version = mergedConfig.version || detectVersion();
|
|
3104
|
+
const environment = mergedConfig.environment || process.env.NODE_ENV || "development";
|
|
3105
|
+
const metricsEnabled = resolveMetricsFlag(mergedConfig.metrics);
|
|
3106
|
+
const logsEnabled = resolveLogsFlag(mergedConfig.logs);
|
|
2874
3107
|
if (devtoolsConfig.enabled && devtoolsConfig.embedded) {
|
|
2875
3108
|
const devtoolsModule = _optionalRequire("autotel-devtools");
|
|
2876
3109
|
if (devtoolsModule?.createDevtools) {
|
|
@@ -2953,7 +3186,13 @@ function init(cfg) {
|
|
|
2953
3186
|
url: formatEndpointUrl(destination.endpoint, "metrics", destination.protocol),
|
|
2954
3187
|
headers: destination.headers
|
|
2955
3188
|
});
|
|
2956
|
-
|
|
3189
|
+
const exportIntervalMillis = readMillisEnv("OTEL_METRIC_EXPORT_INTERVAL");
|
|
3190
|
+
const exportTimeoutMillis = readMillisEnv("OTEL_METRIC_EXPORT_TIMEOUT");
|
|
3191
|
+
metricReaders.push(new PeriodicExportingMetricReader({
|
|
3192
|
+
exporter: metricExporter,
|
|
3193
|
+
...exportIntervalMillis === void 0 ? {} : { exportIntervalMillis },
|
|
3194
|
+
...exportTimeoutMillis === void 0 ? {} : { exportTimeoutMillis }
|
|
3195
|
+
}));
|
|
2957
3196
|
}
|
|
2958
3197
|
let logRecordProcessors;
|
|
2959
3198
|
if (configuredLogRecordProcessors && configuredLogRecordProcessors.length > 0) logRecordProcessors = [...configuredLogRecordProcessors];
|
|
@@ -3022,118 +3261,18 @@ function init(cfg) {
|
|
|
3022
3261
|
}
|
|
3023
3262
|
initialized = true;
|
|
3024
3263
|
const processHandlers = mergedConfig.processHandlers;
|
|
3025
|
-
|
|
3264
|
+
const handlersConfig = processHandlers && processHandlers !== true ? processHandlers : {};
|
|
3265
|
+
if (processHandlers) installProcessHandlers(handlersConfig, shutdown);
|
|
3026
3266
|
else uninstallProcessHandlers();
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
const names = /* @__PURE__ */ new Set();
|
|
3034
|
-
if (!instrumentations) return names;
|
|
3035
|
-
for (const instrumentation of instrumentations) if (instrumentation && typeof instrumentation === "object") names.add(instrumentation.constructor.name);
|
|
3036
|
-
return names;
|
|
3037
|
-
}
|
|
3038
|
-
/**
|
|
3039
|
-
* Map common instrumentation class names to their package names
|
|
3040
|
-
* Used to disable auto-instrumentations when user provides manual configs
|
|
3041
|
-
*/
|
|
3042
|
-
const INSTRUMENTATION_CLASS_TO_PACKAGE = {
|
|
3043
|
-
HttpInstrumentation: "@opentelemetry/instrumentation-http",
|
|
3044
|
-
HttpsInstrumentation: "@opentelemetry/instrumentation-http",
|
|
3045
|
-
ExpressInstrumentation: "@opentelemetry/instrumentation-express",
|
|
3046
|
-
FastifyInstrumentation: "@opentelemetry/instrumentation-fastify",
|
|
3047
|
-
MongoDBInstrumentation: "@opentelemetry/instrumentation-mongodb",
|
|
3048
|
-
MongooseInstrumentation: "@opentelemetry/instrumentation-mongoose",
|
|
3049
|
-
PrismaInstrumentation: "@opentelemetry/instrumentation-prisma",
|
|
3050
|
-
PinoInstrumentation: "@opentelemetry/instrumentation-pino",
|
|
3051
|
-
WinstonInstrumentation: "@opentelemetry/instrumentation-winston",
|
|
3052
|
-
RedisInstrumentation: "@opentelemetry/instrumentation-redis",
|
|
3053
|
-
GraphQLInstrumentation: "@opentelemetry/instrumentation-graphql",
|
|
3054
|
-
GrpcInstrumentation: "@opentelemetry/instrumentation-grpc",
|
|
3055
|
-
IORedisInstrumentation: "@opentelemetry/instrumentation-ioredis",
|
|
3056
|
-
KnexInstrumentation: "@opentelemetry/instrumentation-knex",
|
|
3057
|
-
NestJsInstrumentation: "@opentelemetry/instrumentation-nestjs-core",
|
|
3058
|
-
PgInstrumentation: "@opentelemetry/instrumentation-pg",
|
|
3059
|
-
MySQLInstrumentation: "@opentelemetry/instrumentation-mysql",
|
|
3060
|
-
MySQL2Instrumentation: "@opentelemetry/instrumentation-mysql2"
|
|
3061
|
-
};
|
|
3062
|
-
/**
|
|
3063
|
-
* Detect if we're running in ESM mode
|
|
3064
|
-
*/
|
|
3065
|
-
function isESMMode() {
|
|
3066
|
-
try {
|
|
3067
|
-
const fs = requireModule("node:fs");
|
|
3068
|
-
try {
|
|
3069
|
-
return JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`, "utf8")).type === "module";
|
|
3070
|
-
} catch {
|
|
3071
|
-
return false;
|
|
3072
|
-
}
|
|
3073
|
-
} catch {
|
|
3074
|
-
return false;
|
|
3075
|
-
}
|
|
3076
|
-
}
|
|
3077
|
-
/**
|
|
3078
|
-
* Lazy-load auto-instrumentations (optional peer dependency)
|
|
3079
|
-
* Only loads when integrations config is truthy, avoiding ~40+ package imports at startup.
|
|
3080
|
-
*/
|
|
3081
|
-
function loadNodeAutoInstrumentations() {
|
|
3082
|
-
try {
|
|
3083
|
-
return requireModule("@opentelemetry/auto-instrumentations-node").getNodeAutoInstrumentations;
|
|
3084
|
-
} catch {
|
|
3085
|
-
const isESM = isESMMode();
|
|
3086
|
-
const baseMessage = "@opentelemetry/auto-instrumentations-node not found.";
|
|
3087
|
-
if (isESM) throw new Error(`${baseMessage}\n\nESM Setup Required:
|
|
3088
|
-
1. Install as a direct dependency: pnpm add @opentelemetry/auto-instrumentations-node
|
|
3089
|
-
2. Create instrumentation.mjs with:
|
|
3090
|
-
import 'autotel/register'; // MUST be first!
|
|
3091
|
-
import { init } from 'autotel';
|
|
3092
|
-
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
|
3093
|
-
init({ service: "my-app", instrumentations: getNodeAutoInstrumentations() });
|
|
3094
|
-
3. Run with: tsx --import ./instrumentation.mjs src/index.ts
|
|
3095
|
-
|
|
3096
|
-
See: https://github.com/jagreehal/autotel#esm-setup`);
|
|
3097
|
-
throw new Error(`${baseMessage} Install it: pnpm add @opentelemetry/auto-instrumentations-node`);
|
|
3267
|
+
if (mergedConfig.flushOnExit !== false) {
|
|
3268
|
+
const timeoutMs = handlersConfig.shutdownTimeoutMs;
|
|
3269
|
+
installExitFlush(() => flush({
|
|
3270
|
+
forShutdown: true,
|
|
3271
|
+
timeout: timeoutMs
|
|
3272
|
+
}), timeoutMs);
|
|
3098
3273
|
}
|
|
3099
3274
|
}
|
|
3100
3275
|
/**
|
|
3101
|
-
* Injectable loader for testing. Set to override the default loader.
|
|
3102
|
-
* @internal
|
|
3103
|
-
*/
|
|
3104
|
-
let _autoInstrumentationsLoader = null;
|
|
3105
|
-
/**
|
|
3106
|
-
* Get auto-instrumentations based on simple integration names
|
|
3107
|
-
* Excludes instrumentations that are manually provided to avoid conflicts
|
|
3108
|
-
*/
|
|
3109
|
-
function getAutoInstrumentations(integrations, manualInstrumentationNames = /* @__PURE__ */ new Set()) {
|
|
3110
|
-
if (integrations === false) return [];
|
|
3111
|
-
const getNodeAutoInstrumentations = _autoInstrumentationsLoader ? _autoInstrumentationsLoader() : loadNodeAutoInstrumentations();
|
|
3112
|
-
const exclusionConfig = {};
|
|
3113
|
-
for (const className of manualInstrumentationNames) {
|
|
3114
|
-
const packageName = INSTRUMENTATION_CLASS_TO_PACKAGE[className];
|
|
3115
|
-
if (packageName) exclusionConfig[packageName] = { enabled: false };
|
|
3116
|
-
}
|
|
3117
|
-
if (integrations === true) {
|
|
3118
|
-
if (Object.keys(exclusionConfig).length > 0) return getNodeAutoInstrumentations(exclusionConfig);
|
|
3119
|
-
return getNodeAutoInstrumentations();
|
|
3120
|
-
}
|
|
3121
|
-
if (Array.isArray(integrations)) {
|
|
3122
|
-
const config = { ...exclusionConfig };
|
|
3123
|
-
for (const name of integrations) {
|
|
3124
|
-
const packageName = `@opentelemetry/instrumentation-${name}`;
|
|
3125
|
-
if (!exclusionConfig[packageName]) config[packageName] = { enabled: true };
|
|
3126
|
-
}
|
|
3127
|
-
return getNodeAutoInstrumentations(config);
|
|
3128
|
-
}
|
|
3129
|
-
const config = {
|
|
3130
|
-
...exclusionConfig,
|
|
3131
|
-
...integrations
|
|
3132
|
-
};
|
|
3133
|
-
for (const packageName of Object.keys(exclusionConfig)) if (Object.keys(integrations).find((key) => packageName.includes(key))) config[packageName] = { enabled: false };
|
|
3134
|
-
return getNodeAutoInstrumentations(config);
|
|
3135
|
-
}
|
|
3136
|
-
/**
|
|
3137
3276
|
* Check if autotel has been initialized
|
|
3138
3277
|
*/
|
|
3139
3278
|
function isInitialized() {
|
|
@@ -3173,37 +3312,6 @@ function warnIfNotInitialized(context) {
|
|
|
3173
3312
|
}
|
|
3174
3313
|
}
|
|
3175
3314
|
/**
|
|
3176
|
-
* Auto-detect version from package.json
|
|
3177
|
-
*/
|
|
3178
|
-
function detectVersion() {
|
|
3179
|
-
try {
|
|
3180
|
-
const fs = requireModule("node:fs");
|
|
3181
|
-
return JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`, "utf8")).version || "1.0.0";
|
|
3182
|
-
} catch {
|
|
3183
|
-
return "1.0.0";
|
|
3184
|
-
}
|
|
3185
|
-
}
|
|
3186
|
-
/**
|
|
3187
|
-
* Detect hostname for resource attributes.
|
|
3188
|
-
* Supports Datadog conventions (DD_HOSTNAME) and falls back to system hostname.
|
|
3189
|
-
*
|
|
3190
|
-
* Priority order:
|
|
3191
|
-
* 1. DD_HOSTNAME environment variable (Datadog convention)
|
|
3192
|
-
* 2. HOSTNAME environment variable (common Unix convention)
|
|
3193
|
-
* 3. os.hostname() (system hostname)
|
|
3194
|
-
*
|
|
3195
|
-
* @returns hostname string or undefined if detection fails
|
|
3196
|
-
*/
|
|
3197
|
-
function detectHostname() {
|
|
3198
|
-
if (process.env.DD_HOSTNAME) return process.env.DD_HOSTNAME;
|
|
3199
|
-
if (process.env.HOSTNAME) return process.env.HOSTNAME;
|
|
3200
|
-
try {
|
|
3201
|
-
return requireModule("node:os").hostname();
|
|
3202
|
-
} catch {
|
|
3203
|
-
return;
|
|
3204
|
-
}
|
|
3205
|
-
}
|
|
3206
|
-
/**
|
|
3207
3315
|
* @internal Close embedded devtools if running.
|
|
3208
3316
|
*/
|
|
3209
3317
|
async function _closeEmbeddedDevtools() {
|