apitally 1.0.0-beta.4 → 1.0.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +128 -101
  2. package/dist/_virtual/_rolldown/runtime.cjs +23 -0
  3. package/dist/activation.cjs +31 -1
  4. package/dist/activation.cjs.map +1 -1
  5. package/dist/activation.d.cts +1 -0
  6. package/dist/activation.d.ts +1 -0
  7. package/dist/activation.js +29 -2
  8. package/dist/activation.js.map +1 -1
  9. package/dist/adonisjs/middleware.cjs +1 -1
  10. package/dist/adonisjs/middleware.cjs.map +1 -1
  11. package/dist/adonisjs/middleware.js +1 -1
  12. package/dist/adonisjs/middleware.js.map +1 -1
  13. package/dist/config.cjs +6 -3
  14. package/dist/config.cjs.map +1 -1
  15. package/dist/config.js +6 -3
  16. package/dist/config.js.map +1 -1
  17. package/dist/elysia/index.cjs.map +1 -1
  18. package/dist/elysia/index.js.map +1 -1
  19. package/dist/elysia/middleware.cjs +8 -10
  20. package/dist/elysia/middleware.cjs.map +1 -1
  21. package/dist/elysia/middleware.js +8 -10
  22. package/dist/elysia/middleware.js.map +1 -1
  23. package/dist/elysia/routes.cjs +2 -1
  24. package/dist/elysia/routes.cjs.map +1 -1
  25. package/dist/elysia/routes.js +2 -1
  26. package/dist/elysia/routes.js.map +1 -1
  27. package/dist/express/index.cjs.map +1 -1
  28. package/dist/express/index.js.map +1 -1
  29. package/dist/express/install.cjs +1 -0
  30. package/dist/express/install.cjs.map +1 -1
  31. package/dist/express/install.js +2 -1
  32. package/dist/express/install.js.map +1 -1
  33. package/dist/express/middleware.cjs +1 -1
  34. package/dist/express/middleware.cjs.map +1 -1
  35. package/dist/express/middleware.js +1 -1
  36. package/dist/express/middleware.js.map +1 -1
  37. package/dist/fastify/middleware.cjs +1 -1
  38. package/dist/fastify/middleware.cjs.map +1 -1
  39. package/dist/fastify/middleware.js +1 -1
  40. package/dist/fastify/middleware.js.map +1 -1
  41. package/dist/h3/index.cjs +1 -0
  42. package/dist/h3/index.cjs.map +1 -1
  43. package/dist/h3/index.js +2 -1
  44. package/dist/h3/index.js.map +1 -1
  45. package/dist/h3/middleware.cjs +1 -1
  46. package/dist/h3/middleware.cjs.map +1 -1
  47. package/dist/h3/middleware.js +1 -1
  48. package/dist/h3/middleware.js.map +1 -1
  49. package/dist/hapi/middleware.cjs +1 -1
  50. package/dist/hapi/middleware.cjs.map +1 -1
  51. package/dist/hapi/middleware.js +1 -1
  52. package/dist/hapi/middleware.js.map +1 -1
  53. package/dist/hono/index.cjs.map +1 -1
  54. package/dist/hono/index.js.map +1 -1
  55. package/dist/hono/middleware.cjs +2 -1
  56. package/dist/hono/middleware.cjs.map +1 -1
  57. package/dist/hono/middleware.js +3 -2
  58. package/dist/hono/middleware.js.map +1 -1
  59. package/dist/index.cjs +1 -1
  60. package/dist/index.js +1 -1
  61. package/dist/koa/index.cjs +1 -0
  62. package/dist/koa/index.cjs.map +1 -1
  63. package/dist/koa/index.js +2 -1
  64. package/dist/koa/index.js.map +1 -1
  65. package/dist/koa/middleware.cjs +1 -1
  66. package/dist/koa/middleware.cjs.map +1 -1
  67. package/dist/koa/middleware.js +1 -1
  68. package/dist/koa/middleware.js.map +1 -1
  69. package/dist/metrics.cjs +0 -3
  70. package/dist/metrics.cjs.map +1 -1
  71. package/dist/metrics.js +0 -3
  72. package/dist/metrics.js.map +1 -1
  73. package/dist/providers.cjs +10 -3
  74. package/dist/providers.cjs.map +1 -1
  75. package/dist/providers.js +10 -3
  76. package/dist/providers.js.map +1 -1
  77. package/dist/redaction.cjs +9 -5
  78. package/dist/redaction.cjs.map +1 -1
  79. package/dist/redaction.js +9 -5
  80. package/dist/redaction.js.map +1 -1
  81. package/dist/requestObservation.cjs +24 -18
  82. package/dist/requestObservation.cjs.map +1 -1
  83. package/dist/requestObservation.js +24 -18
  84. package/dist/requestObservation.js.map +1 -1
  85. package/dist/spanExporter.cjs +10 -7
  86. package/dist/spanExporter.cjs.map +1 -1
  87. package/dist/spanExporter.js +10 -7
  88. package/dist/spanExporter.js.map +1 -1
  89. package/dist/spanProcessor.cjs +9 -20
  90. package/dist/spanProcessor.cjs.map +1 -1
  91. package/dist/spanProcessor.d.cts +2 -1
  92. package/dist/spanProcessor.d.ts +2 -1
  93. package/dist/spanProcessor.js +8 -21
  94. package/dist/spanProcessor.js.map +1 -1
  95. package/dist/tracing.cjs +2 -1
  96. package/dist/tracing.cjs.map +1 -1
  97. package/dist/tracing.js +2 -1
  98. package/dist/tracing.js.map +1 -1
  99. package/package.json +14 -14
@@ -6,15 +6,16 @@ import { installConsoleCapture, installNestLoggerCapture, installPinoCapture, in
6
6
  import { ApitallyLogRecordExporter } from "./logRecordExporter.js";
7
7
  import { ApitallyLogRecordProcessor } from "./logRecordProcessor.js";
8
8
  import { MetricsPipeline } from "./metrics.js";
9
+ import { SpanPipeline, isApitallySpanProcessorDeclared, setActiveSpanPipeline, setServerSpanActivationCallback } from "./spanProcessor.js";
9
10
  import { createLoggerProvider, resolveEnvAndCreateResource, setupTracerProvider } from "./providers.js";
10
11
  import { Redaction } from "./redaction.js";
11
12
  import { installSentryEventIdRecording } from "./sentry.js";
12
13
  import { SERVER_ERROR_EVENT_NAME, drainServerErrors, resetServerErrors } from "./serverErrors.js";
13
- import { SpanPipeline, isApitallySpanProcessorDeclared, setActiveSpanPipeline, setServerSpanActivationCallback } from "./spanProcessor.js";
14
14
  import { ApitallySpanExporter } from "./spanExporter.js";
15
15
  import { Spool } from "./spool.js";
16
16
  import { emitStartupEvent } from "./startup.js";
17
17
  import { VALIDATION_ERROR_EVENT_NAME, drainValidationErrors, resetValidationErrors } from "./validationErrors.js";
18
+ import diagnosticsChannel from "node:diagnostics_channel";
18
19
  import { isMainThread } from "node:worker_threads";
19
20
  import { ROOT_CONTEXT } from "@opentelemetry/api";
20
21
  import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";
@@ -27,6 +28,7 @@ const BATCH_MAX_QUEUE_SIZE = 2048;
27
28
  const BATCH_MAX_EXPORT_BATCH_SIZE = 512;
28
29
  const BATCH_MAX_EXPORT_BATCH_SIZE_WITH_BODY_CAPTURE = 32;
29
30
  const SIGNAL_FLUSH_TIMEOUT_MILLIS = 5e3;
31
+ const NET_SERVER_LISTEN_CHANNEL = "tracing:net.server.listen:asyncEnd";
30
32
  const ACTIVATION_STATE_KEY = Symbol.for("apitally.activation");
31
33
  function configure(options = {}) {
32
34
  const config = setConfig(options);
@@ -53,6 +55,25 @@ function activate() {
53
55
  logError(`Apitally activation failed: ${String(error)}`);
54
56
  }
55
57
  }
58
+ function activateWhenHttpServerListens() {
59
+ const activationState = getActivationState();
60
+ if (activationState.httpServerListenHooks) return;
61
+ const onNetServerListen = (message) => {
62
+ if (message.server.listenerCount("request") > 0) activate();
63
+ };
64
+ diagnosticsChannel.subscribe(NET_SERVER_LISTEN_CHANNEL, onNetServerListen);
65
+ const bun = globalThis.Bun;
66
+ const originalBunServe = bun?.serve;
67
+ if (bun && originalBunServe) bun.serve = function(...args) {
68
+ const server = originalBunServe.apply(this, args);
69
+ activate();
70
+ return server;
71
+ };
72
+ activationState.httpServerListenHooks = {
73
+ onNetServerListen,
74
+ originalBunServe
75
+ };
76
+ }
56
77
  function isActivated() {
57
78
  return getActivationState().handles !== void 0;
58
79
  }
@@ -93,6 +114,12 @@ async function resetActivation() {
93
114
  resetServerErrors();
94
115
  if (!activationState) return;
95
116
  removeLifecycleHooks(activationState);
117
+ const listenHooks = activationState.httpServerListenHooks;
118
+ if (listenHooks) {
119
+ diagnosticsChannel.unsubscribe(NET_SERVER_LISTEN_CHANNEL, listenHooks.onNetServerListen);
120
+ const bun = globalThis.Bun;
121
+ if (bun && listenHooks.originalBunServe) bun.serve = listenHooks.originalBunServe;
122
+ }
96
123
  const handles = activationState.handles;
97
124
  if (!handles) return;
98
125
  handles.undiciInstrumentation?.disable();
@@ -298,6 +325,6 @@ function installBeforeExitHook(activationState) {
298
325
  process.on("beforeExit", listener);
299
326
  }
300
327
  //#endregion
301
- export { activate, activationFactories, configure, flushTelemetry, getActivationHandles, isActivated, registerStartupEventInfo, resetActivation, shutdown };
328
+ export { activate, activateWhenHttpServerListens, activationFactories, configure, flushTelemetry, getActivationHandles, isActivated, registerStartupEventInfo, resetActivation, shutdown };
302
329
 
303
330
  //# sourceMappingURL=activation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"activation.js","names":[],"sources":["../src/activation.ts"],"sourcesContent":["import { isMainThread } from \"node:worker_threads\";\nimport { ROOT_CONTEXT } from \"@opentelemetry/api\";\nimport type { AnyValueMap } from \"@opentelemetry/api-logs\";\nimport { UndiciInstrumentation } from \"@opentelemetry/instrumentation-undici\";\nimport { BatchLogRecordProcessor, type LoggerProvider } from \"@opentelemetry/sdk-logs\";\nimport { BatchSpanProcessor } from \"@opentelemetry/sdk-trace-base\";\nimport type { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport { type ApitallyConfig, type ApitallyOptions, getConfig, setConfig } from \"./config.js\";\nimport { ExportWorker, type ExportWorkerOptions } from \"./exportWorker.js\";\nimport {\n installConsoleCapture,\n installNestLoggerCapture,\n installPinoCapture,\n installWinstonCapture,\n} from \"./logCapture.js\";\nimport { logDebug, logError, logWarning } from \"./logger.js\";\nimport { ApitallyLogRecordExporter } from \"./logRecordExporter.js\";\nimport { ApitallyLogRecordProcessor } from \"./logRecordProcessor.js\";\nimport { MetricsPipeline } from \"./metrics.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\nimport {\n createLoggerProvider,\n resolveEnvAndCreateResource,\n setupTracerProvider,\n} from \"./providers.js\";\nimport { Redaction } from \"./redaction.js\";\nimport { installSentryEventIdRecording } from \"./sentry.js\";\nimport { drainServerErrors, resetServerErrors, SERVER_ERROR_EVENT_NAME } from \"./serverErrors.js\";\nimport { ApitallySpanExporter } from \"./spanExporter.js\";\nimport {\n isApitallySpanProcessorDeclared,\n SpanPipeline,\n setActiveSpanPipeline,\n setServerSpanActivationCallback,\n} from \"./spanProcessor.js\";\nimport { Spool } from \"./spool.js\";\nimport { emitStartupEvent, type StartupEventInfo } from \"./startup.js\";\nimport {\n drainValidationErrors,\n resetValidationErrors,\n VALIDATION_ERROR_EVENT_NAME,\n} from \"./validationErrors.js\";\n\n// The worker sends every 15 seconds, so batches only need to reach the spool\n// within that interval.\nconst BATCH_SCHEDULE_DELAY_MILLIS = 1_000;\nconst BATCH_EXPORT_TIMEOUT_MILLIS = 30_000;\nconst BATCH_MAX_QUEUE_SIZE = 2_048;\nconst BATCH_MAX_EXPORT_BATCH_SIZE = 512;\nconst BATCH_MAX_EXPORT_BATCH_SIZE_WITH_BODY_CAPTURE = 32;\nconst SIGNAL_FLUSH_TIMEOUT_MILLIS = 5_000;\n\ntype TerminationSignal = \"SIGTERM\" | \"SIGINT\";\ntype SignalListeners = Record<TerminationSignal, () => void>;\n\n// The ESM and CJS builds can load together, so a Symbol.for key gives both\n// copies the same activation state.\nconst ACTIVATION_STATE_KEY = Symbol.for(\"apitally.activation\");\n\ninterface ActivationState {\n sdkVersion: string;\n activationAttempted: boolean;\n startupEventInfo?: StartupEventInfo;\n handles?: ActivationHandles;\n runShutdown?: () => Promise<void>;\n shutdownPromise?: Promise<void>;\n beforeExitListener?: () => void;\n signalListeners?: SignalListeners;\n signalDrainPromise?: Promise<void>;\n}\n\ninterface ActivationHandles {\n spanPipeline: SpanPipeline;\n tracerProvider?: NodeTracerProvider;\n loggerProvider: LoggerProvider;\n // Consumer identifier to the hash of its last emitted consumer update, in LRU order.\n consumerUpdateHashes: Map<string, string>;\n metricsPipeline: MetricsPipeline;\n spool: Spool;\n worker: ExportWorker;\n undiciInstrumentation?: UndiciInstrumentation;\n}\n\n// Configuration stays synchronous; activate() defers timers and I/O.\nexport function configure(options: ApitallyOptions = {}): ApitallyConfig {\n const config = setConfig(options);\n // User instrumentations read this at initialization. `http/dup` adds stable\n // HTTP attributes alongside legacy ones; a user-set value takes precedence.\n if (!config.disabled && process.env.OTEL_SEMCONV_STABILITY_OPT_IN === undefined) {\n process.env.OTEL_SEMCONV_STABILITY_OPT_IN = \"http/dup\";\n }\n setServerSpanActivationCallback(activate);\n return config;\n}\n\n// The first integration registration wins so one process emits one startup event.\nexport function registerStartupEventInfo(info: StartupEventInfo): void {\n const activationState = getActivationState();\n activationState.startupEventInfo ??= info;\n}\n\n// Activation is synchronous and attempted once, so concurrent first requests\n// observe either no activation or completed activation.\nexport function activate(): void {\n const activationState = getActivationState();\n if (activationState.activationAttempted) {\n return;\n }\n activationState.activationAttempted = true;\n if (shouldSkipActivation()) {\n return;\n }\n try {\n const handles = startPipelines(activationState.startupEventInfo);\n activationState.handles = handles;\n activationState.runShutdown = () => drainAndStop(handles);\n installBeforeExitHook(activationState);\n installSignalHooks(activationState);\n } catch (error) {\n logError(`Apitally activation failed: ${String(error)}`);\n }\n}\n\nexport function isActivated(): boolean {\n return getActivationState().handles !== undefined;\n}\n\nexport function getActivationHandles(): ActivationHandles | undefined {\n return getActivationState().handles;\n}\n\nexport async function flushTelemetry(): Promise<void> {\n try {\n await getActivationState().handles?.worker.runCycle();\n } catch (error) {\n logDebug(`Error flushing telemetry: ${String(error)}`);\n }\n}\n\n// Concurrent calls and lifecycle hooks share one final drain; calls before\n// activation are no-ops.\nexport function shutdown(): Promise<void> {\n const activationState = getActivationState();\n const handles = activationState.handles;\n const runShutdown = activationState.runShutdown;\n if (!handles || !runShutdown) {\n return Promise.resolve();\n }\n if (!activationState.shutdownPromise) {\n removeLifecycleHooks(activationState);\n const signalDrainPromise = activationState.signalDrainPromise;\n activationState.shutdownPromise = (\n signalDrainPromise\n ? signalDrainPromise.then(() => handles.worker.waitForIdle())\n : Promise.resolve()\n ).then(runShutdown);\n }\n return activationState.shutdownPromise;\n}\n\n// Tests replace these factories to isolate spool files and worker timing.\nexport const activationFactories = {\n createSpool: (): Spool => new Spool(),\n createExportWorker: (options: ExportWorkerOptions): ExportWorker => new ExportWorker(options),\n};\nconst defaultFactories = { ...activationFactories };\n\n// Tests reset process-global activation and its side effects between cases.\nexport async function resetActivation(): Promise<void> {\n Object.assign(activationFactories, defaultFactories);\n const holder = globalThis as Record<symbol, ActivationState | undefined>;\n const activationState = holder[ACTIVATION_STATE_KEY];\n delete holder[ACTIVATION_STATE_KEY];\n hasWarnedAboutVersionSkew = false;\n resetValidationErrors();\n resetServerErrors();\n if (!activationState) {\n return;\n }\n removeLifecycleHooks(activationState);\n const handles = activationState.handles;\n if (!handles) {\n return;\n }\n handles.undiciInstrumentation?.disable();\n await handles.worker.stop();\n await handles.spanPipeline.shutdown();\n await handles.loggerProvider.shutdown();\n await handles.spool.clear();\n}\n\nlet hasWarnedAboutVersionSkew = false;\n\n// One root-context record per aggregated error, drained right before the log\n// processor flush so the records leave with the same export cycle.\nfunction emitErrorEvents(loggerProvider: LoggerProvider): void {\n const logger = loggerProvider.getLogger(\"apitally\");\n const events: [string, AnyValueMap[]][] = [\n [VALIDATION_ERROR_EVENT_NAME, drainValidationErrors()],\n [SERVER_ERROR_EVENT_NAME, drainServerErrors()],\n ];\n for (const [eventName, bodies] of events) {\n for (const body of bodies) {\n logger.emit({ timestamp: Date.now(), context: ROOT_CONTEXT, eventName, body });\n }\n }\n}\n\nfunction getActivationState(): ActivationState {\n const holder = globalThis as Record<symbol, ActivationState | undefined>;\n const existing = holder[ACTIVATION_STATE_KEY];\n if (existing) {\n if (!hasWarnedAboutVersionSkew && existing.sdkVersion !== getDistroVersion()) {\n hasWarnedAboutVersionSkew = true;\n logWarning(\n `Two copies of the apitally package with different versions (${existing.sdkVersion} and ${getDistroVersion()}) are loaded in this process; version ${existing.sdkVersion} stays active. To resolve this, deduplicate the apitally installation.`,\n );\n }\n return existing;\n }\n const activationState: ActivationState = {\n sdkVersion: getDistroVersion(),\n activationAttempted: false,\n };\n holder[ACTIVATION_STATE_KEY] = activationState;\n return activationState;\n}\n\nfunction shouldSkipActivation(): boolean {\n return (\n Boolean(process.env.JEST_WORKER_ID) ||\n Boolean(process.env.VITEST) ||\n process.env.NODE_ENV === \"test\" ||\n getConfig().disabled\n );\n}\n\nfunction startPipelines(startupEventInfo: StartupEventInfo | undefined): ActivationHandles {\n const config = getConfig();\n let hasUserProvider = isApitallySpanProcessorDeclared();\n const { env, resource } = resolveEnvAndCreateResource();\n const instanceId = resource.attributes[\"service.instance.id\"] as string;\n const spool = activationFactories.createSpool();\n const spanExporter = new ApitallySpanExporter({\n redaction: new Redaction(),\n env,\n instanceId,\n spool,\n maskRequestBody: config.maskRequestBody,\n maskResponseBody: config.maskResponseBody,\n });\n // Concrete options prevent OTel environment variables from changing batch\n // behavior. Each processor gets a fresh object because its config is mutated.\n const batchSpanProcessor = new BatchSpanProcessor(spanExporter, {\n scheduledDelayMillis: BATCH_SCHEDULE_DELAY_MILLIS,\n exportTimeoutMillis: BATCH_EXPORT_TIMEOUT_MILLIS,\n maxQueueSize: BATCH_MAX_QUEUE_SIZE,\n maxExportBatchSize:\n config.captureRequestBody || config.captureResponseBody\n ? BATCH_MAX_EXPORT_BATCH_SIZE_WITH_BODY_CAPTURE\n : BATCH_MAX_EXPORT_BATCH_SIZE,\n });\n const spanPipeline = new SpanPipeline(batchSpanProcessor, () => spanExporter.forceFlush());\n let tracerProvider: NodeTracerProvider | undefined;\n if (!hasUserProvider) {\n tracerProvider = setupTracerProvider(resource, [spanPipeline]);\n if (!tracerProvider) {\n hasUserProvider = true;\n logWarning(\n \"Apitally could not register its OpenTelemetry tracer provider because another provider is already registered. Only metrics and the startup event are sent to Apitally until you add ApitallySpanProcessor (exported by the apitally package) to your tracer provider's spanProcessors constructor option or the NodeSDK spanProcessors option.\",\n );\n }\n }\n setActiveSpanPipeline(spanPipeline);\n // BatchLogRecordProcessor requires its single-options-object form; positional\n // arguments create a broken processor.\n const batchLogProcessor = new BatchLogRecordProcessor({\n exporter: new ApitallyLogRecordExporter(spool),\n scheduledDelayMillis: BATCH_SCHEDULE_DELAY_MILLIS,\n exportTimeoutMillis: BATCH_EXPORT_TIMEOUT_MILLIS,\n maxQueueSize: BATCH_MAX_QUEUE_SIZE,\n maxExportBatchSize: BATCH_MAX_EXPORT_BATCH_SIZE,\n });\n const logRecordProcessor = new ApitallyLogRecordProcessor(\n batchLogProcessor,\n spanPipeline,\n config.maskLogRecord,\n );\n const loggerProvider = createLoggerProvider(resource, [logRecordProcessor]);\n const metricsPipeline = new MetricsPipeline(resource, spool);\n spanPipeline.metricsRecorder = (record) => metricsPipeline.recordFromRequest(record);\n const worker = activationFactories.createExportWorker({\n spool,\n otlpEndpoint: config.otlpEndpoint,\n writeToken: config.writeToken,\n env,\n });\n worker.flushCallbacks.push(\n () => metricsPipeline.collectAndExport(),\n () => spanPipeline.forceFlush(),\n () => emitErrorEvents(loggerProvider),\n () => batchLogProcessor.forceFlush(),\n );\n if (config.captureLogs) {\n installConsoleCapture(loggerProvider);\n installNestLoggerCapture(loggerProvider);\n installWinstonCapture(loggerProvider);\n installPinoCapture(loggerProvider);\n }\n installSentryEventIdRecording();\n let undiciInstrumentation: UndiciInstrumentation | undefined;\n if (!hasUserProvider) {\n undiciInstrumentation = createUndiciInstrumentation(config.otlpEndpoint);\n }\n worker.start();\n if (startupEventInfo) {\n emitStartupEvent(loggerProvider, startupEventInfo);\n }\n return {\n spanPipeline,\n tracerProvider,\n loggerProvider,\n consumerUpdateHashes: new Map(),\n metricsPipeline,\n spool,\n worker,\n undiciInstrumentation,\n };\n}\n\n// ignoreRequestHook prevents telemetry for export POSTs. The request origin can\n// be a string or URL at runtime despite its string type.\nfunction createUndiciInstrumentation(otlpEndpoint: string): UndiciInstrumentation {\n const endpointOrigin = new URL(otlpEndpoint).origin;\n return new UndiciInstrumentation({\n ignoreRequestHook: (request) => {\n try {\n return new URL(String(request.origin)).origin === endpointOrigin;\n } catch {\n return false;\n }\n },\n });\n}\n\nasync function drainAndStop(handles: ActivationHandles): Promise<void> {\n try {\n await handles.spanPipeline.shutdown();\n await handles.worker.finalDrain();\n await handles.loggerProvider.shutdown();\n await handles.worker.stop();\n } catch (error) {\n logWarning(`Error draining telemetry on shutdown: ${String(error)}`);\n }\n}\n\nfunction installSignalHooks(activationState: ActivationState): void {\n if (process.platform === \"win32\" || !isMainThread) {\n return;\n }\n let listeners: SignalListeners;\n listeners = {\n SIGTERM: () => handleTerminationSignal(activationState, listeners, \"SIGTERM\"),\n SIGINT: () => handleTerminationSignal(activationState, listeners, \"SIGINT\"),\n };\n activationState.signalListeners = listeners;\n try {\n process.prependOnceListener(\"SIGTERM\", listeners.SIGTERM);\n process.prependOnceListener(\"SIGINT\", listeners.SIGINT);\n } catch (error) {\n removeSignalHooks(activationState, listeners);\n logWarning(`Unable to install Apitally signal handlers: ${String(error)}`);\n }\n}\n\nfunction handleTerminationSignal(\n activationState: ActivationState,\n listeners: SignalListeners,\n signal: TerminationSignal,\n): void {\n const hasApplicationListener = process\n .listeners(signal)\n .some((listener) => listener !== listeners[signal]);\n removeSignalHooks(activationState, listeners);\n const drainPromise = flushAfterTerminationSignal(activationState, signal, hasApplicationListener);\n activationState.signalDrainPromise = drainPromise;\n void drainPromise.catch((error: unknown) => {\n logDebug(`Error flushing telemetry on ${signal}: ${String(error)}`);\n });\n}\n\nasync function flushAfterTerminationSignal(\n activationState: ActivationState,\n signal: TerminationSignal,\n hasApplicationListener: boolean,\n): Promise<void> {\n const handles = activationState.handles;\n if (!handles) {\n return;\n }\n try {\n if (activationState.shutdownPromise) {\n await waitForSignalDeadline(activationState.shutdownPromise);\n } else {\n await handles.worker.finalDrain(SIGNAL_FLUSH_TIMEOUT_MILLIS);\n }\n } catch (error) {\n logDebug(`Error flushing telemetry on ${signal}: ${String(error)}`);\n }\n if (hasApplicationListener || process.listeners(signal).length > 0) {\n return;\n }\n const exitCode = signal === \"SIGTERM\" ? 143 : 130;\n if (process.platform === \"linux\" && process.pid === 1) {\n process.exit(exitCode);\n }\n try {\n process.kill(process.pid, signal);\n } catch {\n process.exit(exitCode);\n }\n}\n\nasync function waitForSignalDeadline(promise: Promise<void>): Promise<void> {\n let deadlineTimer: NodeJS.Timeout | undefined;\n const deadlineReached = new Promise<void>((resolve) => {\n deadlineTimer = setTimeout(resolve, SIGNAL_FLUSH_TIMEOUT_MILLIS);\n });\n try {\n await Promise.race([promise, deadlineReached]);\n } finally {\n if (deadlineTimer) {\n clearTimeout(deadlineTimer);\n }\n }\n}\n\nfunction removeSignalHooks(\n activationState: ActivationState,\n listeners = activationState.signalListeners,\n): void {\n if (!listeners) {\n return;\n }\n process.removeListener(\"SIGTERM\", listeners.SIGTERM);\n process.removeListener(\"SIGINT\", listeners.SIGINT);\n if (activationState.signalListeners === listeners) {\n activationState.signalListeners = undefined;\n }\n}\n\nfunction removeLifecycleHooks(activationState: ActivationState): void {\n removeSignalHooks(activationState);\n if (activationState.beforeExitListener) {\n process.removeListener(\"beforeExit\", activationState.beforeExitListener);\n activationState.beforeExitListener = undefined;\n }\n}\n\nfunction installBeforeExitHook(activationState: ActivationState): void {\n const listener = () => {\n // The drain keeps the event loop alive until shutdown settles.\n void shutdown().catch((error: unknown) => {\n logDebug(`Error draining telemetry on shutdown: ${String(error)}`);\n });\n };\n activationState.beforeExitListener = listener;\n process.on(\"beforeExit\", listener);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"activation.js","names":[],"sources":["../src/activation.ts"],"sourcesContent":["import diagnosticsChannel from \"node:diagnostics_channel\";\nimport type { Server } from \"node:net\";\nimport { isMainThread } from \"node:worker_threads\";\nimport { ROOT_CONTEXT } from \"@opentelemetry/api\";\nimport type { AnyValueMap } from \"@opentelemetry/api-logs\";\nimport { UndiciInstrumentation } from \"@opentelemetry/instrumentation-undici\";\nimport { BatchLogRecordProcessor, type LoggerProvider } from \"@opentelemetry/sdk-logs\";\nimport { BatchSpanProcessor } from \"@opentelemetry/sdk-trace-base\";\nimport type { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport { type ApitallyConfig, type ApitallyOptions, getConfig, setConfig } from \"./config.js\";\nimport { ExportWorker, type ExportWorkerOptions } from \"./exportWorker.js\";\nimport {\n installConsoleCapture,\n installNestLoggerCapture,\n installPinoCapture,\n installWinstonCapture,\n} from \"./logCapture.js\";\nimport { logDebug, logError, logWarning } from \"./logger.js\";\nimport { ApitallyLogRecordExporter } from \"./logRecordExporter.js\";\nimport { ApitallyLogRecordProcessor } from \"./logRecordProcessor.js\";\nimport { MetricsPipeline } from \"./metrics.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\nimport {\n createLoggerProvider,\n resolveEnvAndCreateResource,\n setupTracerProvider,\n} from \"./providers.js\";\nimport { Redaction } from \"./redaction.js\";\nimport { installSentryEventIdRecording } from \"./sentry.js\";\nimport { drainServerErrors, resetServerErrors, SERVER_ERROR_EVENT_NAME } from \"./serverErrors.js\";\nimport { ApitallySpanExporter } from \"./spanExporter.js\";\nimport {\n isApitallySpanProcessorDeclared,\n SpanPipeline,\n setActiveSpanPipeline,\n setServerSpanActivationCallback,\n} from \"./spanProcessor.js\";\nimport { Spool } from \"./spool.js\";\nimport { emitStartupEvent, type StartupEventInfo } from \"./startup.js\";\nimport {\n drainValidationErrors,\n resetValidationErrors,\n VALIDATION_ERROR_EVENT_NAME,\n} from \"./validationErrors.js\";\n\n// The worker sends every 15 seconds, so batches only need to reach the spool\n// within that interval.\nconst BATCH_SCHEDULE_DELAY_MILLIS = 1_000;\nconst BATCH_EXPORT_TIMEOUT_MILLIS = 30_000;\nconst BATCH_MAX_QUEUE_SIZE = 2_048;\nconst BATCH_MAX_EXPORT_BATCH_SIZE = 512;\nconst BATCH_MAX_EXPORT_BATCH_SIZE_WITH_BODY_CAPTURE = 32;\nconst SIGNAL_FLUSH_TIMEOUT_MILLIS = 5_000;\n\n// Published by Node >= 20.16 and >= 22.3 once a net.Server is listening.\nconst NET_SERVER_LISTEN_CHANNEL = \"tracing:net.server.listen:asyncEnd\";\n\ntype TerminationSignal = \"SIGTERM\" | \"SIGINT\";\ntype BunServe = (this: unknown, ...args: unknown[]) => unknown;\ntype SignalListeners = Record<TerminationSignal, () => void>;\n\n// The ESM and CJS builds can load together, so a Symbol.for key gives both\n// copies the same activation state.\nconst ACTIVATION_STATE_KEY = Symbol.for(\"apitally.activation\");\n\ninterface ActivationState {\n sdkVersion: string;\n activationAttempted: boolean;\n startupEventInfo?: StartupEventInfo;\n handles?: ActivationHandles;\n runShutdown?: () => Promise<void>;\n shutdownPromise?: Promise<void>;\n beforeExitListener?: () => void;\n signalListeners?: SignalListeners;\n signalDrainPromise?: Promise<void>;\n httpServerListenHooks?: HttpServerListenHooks;\n}\n\ninterface HttpServerListenHooks {\n onNetServerListen: (message: unknown) => void;\n originalBunServe?: BunServe;\n}\n\ninterface ActivationHandles {\n spanPipeline: SpanPipeline;\n tracerProvider?: NodeTracerProvider;\n loggerProvider: LoggerProvider;\n // Consumer identifier to the hash of its last emitted consumer update, in LRU order.\n consumerUpdateHashes: Map<string, string>;\n metricsPipeline: MetricsPipeline;\n spool: Spool;\n worker: ExportWorker;\n undiciInstrumentation?: UndiciInstrumentation;\n}\n\n// Configuration stays synchronous; activate() defers timers and I/O.\nexport function configure(options: ApitallyOptions = {}): ApitallyConfig {\n const config = setConfig(options);\n // User instrumentations read this at initialization. `http/dup` adds stable\n // HTTP attributes alongside legacy ones; a user-set value takes precedence.\n if (!config.disabled && process.env.OTEL_SEMCONV_STABILITY_OPT_IN === undefined) {\n process.env.OTEL_SEMCONV_STABILITY_OPT_IN = \"http/dup\";\n }\n setServerSpanActivationCallback(activate);\n return config;\n}\n\n// The first integration registration wins so one process emits one startup event.\nexport function registerStartupEventInfo(info: StartupEventInfo): void {\n const activationState = getActivationState();\n activationState.startupEventInfo ??= info;\n}\n\n// Activation is synchronous and attempted once, so concurrent first requests\n// observe either no activation or completed activation.\nexport function activate(): void {\n const activationState = getActivationState();\n if (activationState.activationAttempted) {\n return;\n }\n activationState.activationAttempted = true;\n if (shouldSkipActivation()) {\n return;\n }\n try {\n const handles = startPipelines(activationState.startupEventInfo);\n activationState.handles = handles;\n activationState.runShutdown = () => drainAndStop(handles);\n installBeforeExitHook(activationState);\n installSignalHooks(activationState);\n } catch (error) {\n logError(`Apitally activation failed: ${String(error)}`);\n }\n}\n\n// For integrations without a framework startup hook. A server that started\n// listening before this call is missed and activates on its first request.\nexport function activateWhenHttpServerListens(): void {\n const activationState = getActivationState();\n if (activationState.httpServerListenHooks) {\n return;\n }\n // HTTP servers carry the app as a request listener; a cluster primary's\n // internal round-robin server has none, so only workers activate.\n const onNetServerListen = (message: unknown) => {\n if ((message as { server: Server }).server.listenerCount(\"request\") > 0) {\n activate();\n }\n };\n diagnosticsChannel.subscribe(NET_SERVER_LISTEN_CHANNEL, onNetServerListen);\n // Bun publishes no net listen channel, but every Bun HTTP server starts through Bun.serve.\n const bun = (globalThis as { Bun?: { serve: BunServe } }).Bun;\n const originalBunServe = bun?.serve;\n if (bun && originalBunServe) {\n bun.serve = function (this: unknown, ...args: unknown[]) {\n const server = originalBunServe.apply(this, args);\n activate();\n return server;\n };\n }\n activationState.httpServerListenHooks = { onNetServerListen, originalBunServe };\n}\n\nexport function isActivated(): boolean {\n return getActivationState().handles !== undefined;\n}\n\nexport function getActivationHandles(): ActivationHandles | undefined {\n return getActivationState().handles;\n}\n\nexport async function flushTelemetry(): Promise<void> {\n try {\n await getActivationState().handles?.worker.runCycle();\n } catch (error) {\n logDebug(`Error flushing telemetry: ${String(error)}`);\n }\n}\n\n// Concurrent calls and lifecycle hooks share one final drain; calls before\n// activation are no-ops.\nexport function shutdown(): Promise<void> {\n const activationState = getActivationState();\n const handles = activationState.handles;\n const runShutdown = activationState.runShutdown;\n if (!handles || !runShutdown) {\n return Promise.resolve();\n }\n if (!activationState.shutdownPromise) {\n removeLifecycleHooks(activationState);\n const signalDrainPromise = activationState.signalDrainPromise;\n activationState.shutdownPromise = (\n signalDrainPromise\n ? signalDrainPromise.then(() => handles.worker.waitForIdle())\n : Promise.resolve()\n ).then(runShutdown);\n }\n return activationState.shutdownPromise;\n}\n\n// Tests replace these factories to isolate spool files and worker timing.\nexport const activationFactories = {\n createSpool: (): Spool => new Spool(),\n createExportWorker: (options: ExportWorkerOptions): ExportWorker => new ExportWorker(options),\n};\nconst defaultFactories = { ...activationFactories };\n\n// Tests reset process-global activation and its side effects between cases.\nexport async function resetActivation(): Promise<void> {\n Object.assign(activationFactories, defaultFactories);\n const holder = globalThis as Record<symbol, ActivationState | undefined>;\n const activationState = holder[ACTIVATION_STATE_KEY];\n delete holder[ACTIVATION_STATE_KEY];\n hasWarnedAboutVersionSkew = false;\n resetValidationErrors();\n resetServerErrors();\n if (!activationState) {\n return;\n }\n removeLifecycleHooks(activationState);\n const listenHooks = activationState.httpServerListenHooks;\n if (listenHooks) {\n diagnosticsChannel.unsubscribe(NET_SERVER_LISTEN_CHANNEL, listenHooks.onNetServerListen);\n const bun = (globalThis as { Bun?: { serve: BunServe } }).Bun;\n if (bun && listenHooks.originalBunServe) {\n bun.serve = listenHooks.originalBunServe;\n }\n }\n const handles = activationState.handles;\n if (!handles) {\n return;\n }\n handles.undiciInstrumentation?.disable();\n await handles.worker.stop();\n await handles.spanPipeline.shutdown();\n await handles.loggerProvider.shutdown();\n await handles.spool.clear();\n}\n\nlet hasWarnedAboutVersionSkew = false;\n\n// One root-context record per aggregated error, drained right before the log\n// processor flush so the records leave with the same export cycle.\nfunction emitErrorEvents(loggerProvider: LoggerProvider): void {\n const logger = loggerProvider.getLogger(\"apitally\");\n const events: [string, AnyValueMap[]][] = [\n [VALIDATION_ERROR_EVENT_NAME, drainValidationErrors()],\n [SERVER_ERROR_EVENT_NAME, drainServerErrors()],\n ];\n for (const [eventName, bodies] of events) {\n for (const body of bodies) {\n logger.emit({ timestamp: Date.now(), context: ROOT_CONTEXT, eventName, body });\n }\n }\n}\n\nfunction getActivationState(): ActivationState {\n const holder = globalThis as Record<symbol, ActivationState | undefined>;\n const existing = holder[ACTIVATION_STATE_KEY];\n if (existing) {\n if (!hasWarnedAboutVersionSkew && existing.sdkVersion !== getDistroVersion()) {\n hasWarnedAboutVersionSkew = true;\n logWarning(\n `Two copies of the apitally package with different versions (${existing.sdkVersion} and ${getDistroVersion()}) are loaded in this process; version ${existing.sdkVersion} stays active. To resolve this, deduplicate the apitally installation.`,\n );\n }\n return existing;\n }\n const activationState: ActivationState = {\n sdkVersion: getDistroVersion(),\n activationAttempted: false,\n };\n holder[ACTIVATION_STATE_KEY] = activationState;\n return activationState;\n}\n\nfunction shouldSkipActivation(): boolean {\n return (\n Boolean(process.env.JEST_WORKER_ID) ||\n Boolean(process.env.VITEST) ||\n process.env.NODE_ENV === \"test\" ||\n getConfig().disabled\n );\n}\n\nfunction startPipelines(startupEventInfo: StartupEventInfo | undefined): ActivationHandles {\n const config = getConfig();\n let hasUserProvider = isApitallySpanProcessorDeclared();\n const { env, resource } = resolveEnvAndCreateResource();\n const instanceId = resource.attributes[\"service.instance.id\"] as string;\n const spool = activationFactories.createSpool();\n const spanExporter = new ApitallySpanExporter({\n redaction: new Redaction(),\n env,\n instanceId,\n spool,\n maskRequestBody: config.maskRequestBody,\n maskResponseBody: config.maskResponseBody,\n });\n // Concrete options prevent OTel environment variables from changing batch\n // behavior. Each processor gets a fresh object because its config is mutated.\n const batchSpanProcessor = new BatchSpanProcessor(spanExporter, {\n scheduledDelayMillis: BATCH_SCHEDULE_DELAY_MILLIS,\n exportTimeoutMillis: BATCH_EXPORT_TIMEOUT_MILLIS,\n maxQueueSize: BATCH_MAX_QUEUE_SIZE,\n maxExportBatchSize:\n config.captureRequestBody || config.captureResponseBody\n ? BATCH_MAX_EXPORT_BATCH_SIZE_WITH_BODY_CAPTURE\n : BATCH_MAX_EXPORT_BATCH_SIZE,\n });\n const spanPipeline = new SpanPipeline(batchSpanProcessor, () => spanExporter.forceFlush());\n let tracerProvider: NodeTracerProvider | undefined;\n if (!hasUserProvider) {\n tracerProvider = setupTracerProvider(resource, [spanPipeline]);\n if (!tracerProvider) {\n hasUserProvider = true;\n logWarning(\n \"Apitally could not register its OpenTelemetry tracer provider because another provider is already registered. Only metrics and the startup event are sent to Apitally until you add ApitallySpanProcessor (exported by the apitally package) to your tracer provider's spanProcessors constructor option or the NodeSDK spanProcessors option.\",\n );\n }\n }\n setActiveSpanPipeline(spanPipeline);\n // BatchLogRecordProcessor requires its single-options-object form; positional\n // arguments create a broken processor.\n const batchLogProcessor = new BatchLogRecordProcessor({\n exporter: new ApitallyLogRecordExporter(spool),\n scheduledDelayMillis: BATCH_SCHEDULE_DELAY_MILLIS,\n exportTimeoutMillis: BATCH_EXPORT_TIMEOUT_MILLIS,\n maxQueueSize: BATCH_MAX_QUEUE_SIZE,\n maxExportBatchSize: BATCH_MAX_EXPORT_BATCH_SIZE,\n });\n const logRecordProcessor = new ApitallyLogRecordProcessor(\n batchLogProcessor,\n spanPipeline,\n config.maskLogRecord,\n );\n const loggerProvider = createLoggerProvider(resource, [logRecordProcessor]);\n const metricsPipeline = new MetricsPipeline(resource, spool);\n spanPipeline.metricsRecorder = (record) => metricsPipeline.recordFromRequest(record);\n const worker = activationFactories.createExportWorker({\n spool,\n otlpEndpoint: config.otlpEndpoint,\n writeToken: config.writeToken,\n env,\n });\n worker.flushCallbacks.push(\n () => metricsPipeline.collectAndExport(),\n () => spanPipeline.forceFlush(),\n () => emitErrorEvents(loggerProvider),\n () => batchLogProcessor.forceFlush(),\n );\n if (config.captureLogs) {\n installConsoleCapture(loggerProvider);\n installNestLoggerCapture(loggerProvider);\n installWinstonCapture(loggerProvider);\n installPinoCapture(loggerProvider);\n }\n installSentryEventIdRecording();\n let undiciInstrumentation: UndiciInstrumentation | undefined;\n if (!hasUserProvider) {\n undiciInstrumentation = createUndiciInstrumentation(config.otlpEndpoint);\n }\n worker.start();\n if (startupEventInfo) {\n emitStartupEvent(loggerProvider, startupEventInfo);\n }\n return {\n spanPipeline,\n tracerProvider,\n loggerProvider,\n consumerUpdateHashes: new Map(),\n metricsPipeline,\n spool,\n worker,\n undiciInstrumentation,\n };\n}\n\n// ignoreRequestHook prevents telemetry for export POSTs. The request origin can\n// be a string or URL at runtime despite its string type.\nfunction createUndiciInstrumentation(otlpEndpoint: string): UndiciInstrumentation {\n const endpointOrigin = new URL(otlpEndpoint).origin;\n return new UndiciInstrumentation({\n ignoreRequestHook: (request) => {\n try {\n return new URL(String(request.origin)).origin === endpointOrigin;\n } catch {\n return false;\n }\n },\n });\n}\n\nasync function drainAndStop(handles: ActivationHandles): Promise<void> {\n try {\n await handles.spanPipeline.shutdown();\n await handles.worker.finalDrain();\n await handles.loggerProvider.shutdown();\n await handles.worker.stop();\n } catch (error) {\n logWarning(`Error draining telemetry on shutdown: ${String(error)}`);\n }\n}\n\nfunction installSignalHooks(activationState: ActivationState): void {\n if (process.platform === \"win32\" || !isMainThread) {\n return;\n }\n let listeners: SignalListeners;\n listeners = {\n SIGTERM: () => handleTerminationSignal(activationState, listeners, \"SIGTERM\"),\n SIGINT: () => handleTerminationSignal(activationState, listeners, \"SIGINT\"),\n };\n activationState.signalListeners = listeners;\n try {\n process.prependOnceListener(\"SIGTERM\", listeners.SIGTERM);\n process.prependOnceListener(\"SIGINT\", listeners.SIGINT);\n } catch (error) {\n removeSignalHooks(activationState, listeners);\n logWarning(`Unable to install Apitally signal handlers: ${String(error)}`);\n }\n}\n\nfunction handleTerminationSignal(\n activationState: ActivationState,\n listeners: SignalListeners,\n signal: TerminationSignal,\n): void {\n const hasApplicationListener = process\n .listeners(signal)\n .some((listener) => listener !== listeners[signal]);\n removeSignalHooks(activationState, listeners);\n const drainPromise = flushAfterTerminationSignal(activationState, signal, hasApplicationListener);\n activationState.signalDrainPromise = drainPromise;\n void drainPromise.catch((error: unknown) => {\n logDebug(`Error flushing telemetry on ${signal}: ${String(error)}`);\n });\n}\n\nasync function flushAfterTerminationSignal(\n activationState: ActivationState,\n signal: TerminationSignal,\n hasApplicationListener: boolean,\n): Promise<void> {\n const handles = activationState.handles;\n if (!handles) {\n return;\n }\n try {\n if (activationState.shutdownPromise) {\n await waitForSignalDeadline(activationState.shutdownPromise);\n } else {\n await handles.worker.finalDrain(SIGNAL_FLUSH_TIMEOUT_MILLIS);\n }\n } catch (error) {\n logDebug(`Error flushing telemetry on ${signal}: ${String(error)}`);\n }\n if (hasApplicationListener || process.listeners(signal).length > 0) {\n return;\n }\n const exitCode = signal === \"SIGTERM\" ? 143 : 130;\n if (process.platform === \"linux\" && process.pid === 1) {\n process.exit(exitCode);\n }\n try {\n process.kill(process.pid, signal);\n } catch {\n process.exit(exitCode);\n }\n}\n\nasync function waitForSignalDeadline(promise: Promise<void>): Promise<void> {\n let deadlineTimer: NodeJS.Timeout | undefined;\n const deadlineReached = new Promise<void>((resolve) => {\n deadlineTimer = setTimeout(resolve, SIGNAL_FLUSH_TIMEOUT_MILLIS);\n });\n try {\n await Promise.race([promise, deadlineReached]);\n } finally {\n if (deadlineTimer) {\n clearTimeout(deadlineTimer);\n }\n }\n}\n\nfunction removeSignalHooks(\n activationState: ActivationState,\n listeners = activationState.signalListeners,\n): void {\n if (!listeners) {\n return;\n }\n process.removeListener(\"SIGTERM\", listeners.SIGTERM);\n process.removeListener(\"SIGINT\", listeners.SIGINT);\n if (activationState.signalListeners === listeners) {\n activationState.signalListeners = undefined;\n }\n}\n\nfunction removeLifecycleHooks(activationState: ActivationState): void {\n removeSignalHooks(activationState);\n if (activationState.beforeExitListener) {\n process.removeListener(\"beforeExit\", activationState.beforeExitListener);\n activationState.beforeExitListener = undefined;\n }\n}\n\nfunction installBeforeExitHook(activationState: ActivationState): void {\n const listener = () => {\n // The drain keeps the event loop alive until shutdown settles.\n void shutdown().catch((error: unknown) => {\n logDebug(`Error draining telemetry on shutdown: ${String(error)}`);\n });\n };\n activationState.beforeExitListener = listener;\n process.on(\"beforeExit\", listener);\n}\n"],"mappings":"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"}
@@ -9,7 +9,7 @@ const require_requestObservation = require("../requestObservation.cjs");
9
9
  const require_requestObservationNode = require("../requestObservationNode.cjs");
10
10
  let _opentelemetry_api = require("@opentelemetry/api");
11
11
  //#region src/adonisjs/middleware.ts
12
- const TRACER_NAME = "apitally.adonisjs";
12
+ const TRACER_NAME = "apitally/adonisjs";
13
13
  var ApitallyMiddleware = class {
14
14
  async handle(ctx, next) {
15
15
  if (!require_activation.isActivated()) return next();
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.cjs","names":["isActivated","startNodeRequestObservation","getConfig","context"],"sources":["../../src/adonisjs/middleware.ts"],"sourcesContent":["import type { HttpContext } from \"@adonisjs/core/http\";\nimport type { NextFn } from \"@adonisjs/core/types/http\";\nimport { type Context, context } from \"@opentelemetry/api\";\n\nimport { isActivated } from \"../activation.js\";\nimport { getConfig } from \"../config.js\";\nimport { logWarning } from \"../logger.js\";\nimport { finalizeRequestObservation } from \"../requestObservation.js\";\nimport { captureNodeResponse, startNodeRequestObservation } from \"../requestObservationNode.js\";\n\nconst TRACER_NAME = \"apitally.adonisjs\";\n\nexport default class ApitallyMiddleware {\n async handle(ctx: HttpContext, next: NextFn): Promise<unknown> {\n if (!isActivated()) {\n return next();\n }\n\n let requestContext: Context | undefined;\n try {\n const request = ctx.request.request;\n const response = ctx.response.response;\n const started = startNodeRequestObservation({ request, tracerName: TRACER_NAME });\n requestContext = started.requestContext;\n context.bind(requestContext, request);\n context.bind(requestContext, response);\n captureNodeResponse(\n response,\n getConfig().captureResponseBody &&\n started.observation.requestRecord.dropReason === undefined,\n started.observation.requestBodyCapture,\n (name) => ctx.response.getHeader(name),\n )\n .then((completion) => {\n finalizeRequestObservation({\n observation: started.observation,\n completedAtMillis: completion.completedAtMillis,\n statusCode: response.statusCode,\n route: ctx.route?.pattern,\n clientAddress: ctx.request.ip(),\n requestHeaders: request.headers,\n responseHeaders: ctx.response.getHeaders(),\n capturedRequestBody: completion.requestBody,\n capturedResponseBody: completion,\n });\n })\n .catch((error: unknown) => {\n logWarning(`Error in the Apitally middleware: ${String(error)}`);\n });\n } catch (error) {\n logWarning(`Error in the Apitally middleware: ${String(error)}`);\n }\n\n return requestContext ? context.with(requestContext, next) : next();\n }\n}\n"],"mappings":";;;;;;;;;;;AAUA,MAAM,cAAc;AAEpB,IAAqB,qBAArB,MAAwC;CACtC,MAAM,OAAO,KAAkB,MAAgC;EAC7D,IAAI,CAACA,mBAAAA,YAAY,GACf,OAAO,KAAK;EAGd,IAAI;EACJ,IAAI;GACF,MAAM,UAAU,IAAI,QAAQ;GAC5B,MAAM,WAAW,IAAI,SAAS;GAC9B,MAAM,UAAUC,+BAAAA,4BAA4B;IAAE;IAAS,YAAY;GAAY,CAAC;GAChF,iBAAiB,QAAQ;GACzB,mBAAA,QAAQ,KAAK,gBAAgB,OAAO;GACpC,mBAAA,QAAQ,KAAK,gBAAgB,QAAQ;GACrC,+BAAA,oBACE,UACAC,eAAAA,UAAU,CAAC,CAAC,uBACV,QAAQ,YAAY,cAAc,eAAe,KAAA,GACnD,QAAQ,YAAY,qBACnB,SAAS,IAAI,SAAS,UAAU,IAAI,CACvC,CAAC,CACE,MAAM,eAAe;IACpB,2BAAA,2BAA2B;KACzB,aAAa,QAAQ;KACrB,mBAAmB,WAAW;KAC9B,YAAY,SAAS;KACrB,OAAO,IAAI,OAAO;KAClB,eAAe,IAAI,QAAQ,GAAG;KAC9B,gBAAgB,QAAQ;KACxB,iBAAiB,IAAI,SAAS,WAAW;KACzC,qBAAqB,WAAW;KAChC,sBAAsB;IACxB,CAAC;GACH,CAAC,CAAC,CACD,OAAO,UAAmB;IACzB,eAAA,WAAW,qCAAqC,OAAO,KAAK,GAAG;GACjE,CAAC;EACL,SAAS,OAAO;GACd,eAAA,WAAW,qCAAqC,OAAO,KAAK,GAAG;EACjE;EAEA,OAAO,iBAAiBC,mBAAAA,QAAQ,KAAK,gBAAgB,IAAI,IAAI,KAAK;CACpE;AACF"}
1
+ {"version":3,"file":"middleware.cjs","names":["isActivated","startNodeRequestObservation","getConfig","context"],"sources":["../../src/adonisjs/middleware.ts"],"sourcesContent":["import type { HttpContext } from \"@adonisjs/core/http\";\nimport type { NextFn } from \"@adonisjs/core/types/http\";\nimport { type Context, context } from \"@opentelemetry/api\";\n\nimport { isActivated } from \"../activation.js\";\nimport { getConfig } from \"../config.js\";\nimport { logWarning } from \"../logger.js\";\nimport { finalizeRequestObservation } from \"../requestObservation.js\";\nimport { captureNodeResponse, startNodeRequestObservation } from \"../requestObservationNode.js\";\n\nconst TRACER_NAME = \"apitally/adonisjs\";\n\nexport default class ApitallyMiddleware {\n async handle(ctx: HttpContext, next: NextFn): Promise<unknown> {\n if (!isActivated()) {\n return next();\n }\n\n let requestContext: Context | undefined;\n try {\n const request = ctx.request.request;\n const response = ctx.response.response;\n const started = startNodeRequestObservation({ request, tracerName: TRACER_NAME });\n requestContext = started.requestContext;\n context.bind(requestContext, request);\n context.bind(requestContext, response);\n captureNodeResponse(\n response,\n getConfig().captureResponseBody &&\n started.observation.requestRecord.dropReason === undefined,\n started.observation.requestBodyCapture,\n (name) => ctx.response.getHeader(name),\n )\n .then((completion) => {\n finalizeRequestObservation({\n observation: started.observation,\n completedAtMillis: completion.completedAtMillis,\n statusCode: response.statusCode,\n route: ctx.route?.pattern,\n clientAddress: ctx.request.ip(),\n requestHeaders: request.headers,\n responseHeaders: ctx.response.getHeaders(),\n capturedRequestBody: completion.requestBody,\n capturedResponseBody: completion,\n });\n })\n .catch((error: unknown) => {\n logWarning(`Error in the Apitally middleware: ${String(error)}`);\n });\n } catch (error) {\n logWarning(`Error in the Apitally middleware: ${String(error)}`);\n }\n\n return requestContext ? context.with(requestContext, next) : next();\n }\n}\n"],"mappings":";;;;;;;;;;;AAUA,MAAM,cAAc;AAEpB,IAAqB,qBAArB,MAAwC;CACtC,MAAM,OAAO,KAAkB,MAAgC;EAC7D,IAAI,CAACA,mBAAAA,YAAY,GACf,OAAO,KAAK;EAGd,IAAI;EACJ,IAAI;GACF,MAAM,UAAU,IAAI,QAAQ;GAC5B,MAAM,WAAW,IAAI,SAAS;GAC9B,MAAM,UAAUC,+BAAAA,4BAA4B;IAAE;IAAS,YAAY;GAAY,CAAC;GAChF,iBAAiB,QAAQ;GACzB,mBAAA,QAAQ,KAAK,gBAAgB,OAAO;GACpC,mBAAA,QAAQ,KAAK,gBAAgB,QAAQ;GACrC,+BAAA,oBACE,UACAC,eAAAA,UAAU,CAAC,CAAC,uBACV,QAAQ,YAAY,cAAc,eAAe,KAAA,GACnD,QAAQ,YAAY,qBACnB,SAAS,IAAI,SAAS,UAAU,IAAI,CACvC,CAAC,CACE,MAAM,eAAe;IACpB,2BAAA,2BAA2B;KACzB,aAAa,QAAQ;KACrB,mBAAmB,WAAW;KAC9B,YAAY,SAAS;KACrB,OAAO,IAAI,OAAO;KAClB,eAAe,IAAI,QAAQ,GAAG;KAC9B,gBAAgB,QAAQ;KACxB,iBAAiB,IAAI,SAAS,WAAW;KACzC,qBAAqB,WAAW;KAChC,sBAAsB;IACxB,CAAC;GACH,CAAC,CAAC,CACD,OAAO,UAAmB;IACzB,eAAA,WAAW,qCAAqC,OAAO,KAAK,GAAG;GACjE,CAAC;EACL,SAAS,OAAO;GACd,eAAA,WAAW,qCAAqC,OAAO,KAAK,GAAG;EACjE;EAEA,OAAO,iBAAiBC,mBAAAA,QAAQ,KAAK,gBAAgB,IAAI,IAAI,KAAK;CACpE;AACF"}
@@ -5,7 +5,7 @@ import { finalizeRequestObservation } from "../requestObservation.js";
5
5
  import { captureNodeResponse, startNodeRequestObservation } from "../requestObservationNode.js";
6
6
  import { context } from "@opentelemetry/api";
7
7
  //#region src/adonisjs/middleware.ts
8
- const TRACER_NAME = "apitally.adonisjs";
8
+ const TRACER_NAME = "apitally/adonisjs";
9
9
  var ApitallyMiddleware = class {
10
10
  async handle(ctx, next) {
11
11
  if (!isActivated()) return next();
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","names":[],"sources":["../../src/adonisjs/middleware.ts"],"sourcesContent":["import type { HttpContext } from \"@adonisjs/core/http\";\nimport type { NextFn } from \"@adonisjs/core/types/http\";\nimport { type Context, context } from \"@opentelemetry/api\";\n\nimport { isActivated } from \"../activation.js\";\nimport { getConfig } from \"../config.js\";\nimport { logWarning } from \"../logger.js\";\nimport { finalizeRequestObservation } from \"../requestObservation.js\";\nimport { captureNodeResponse, startNodeRequestObservation } from \"../requestObservationNode.js\";\n\nconst TRACER_NAME = \"apitally.adonisjs\";\n\nexport default class ApitallyMiddleware {\n async handle(ctx: HttpContext, next: NextFn): Promise<unknown> {\n if (!isActivated()) {\n return next();\n }\n\n let requestContext: Context | undefined;\n try {\n const request = ctx.request.request;\n const response = ctx.response.response;\n const started = startNodeRequestObservation({ request, tracerName: TRACER_NAME });\n requestContext = started.requestContext;\n context.bind(requestContext, request);\n context.bind(requestContext, response);\n captureNodeResponse(\n response,\n getConfig().captureResponseBody &&\n started.observation.requestRecord.dropReason === undefined,\n started.observation.requestBodyCapture,\n (name) => ctx.response.getHeader(name),\n )\n .then((completion) => {\n finalizeRequestObservation({\n observation: started.observation,\n completedAtMillis: completion.completedAtMillis,\n statusCode: response.statusCode,\n route: ctx.route?.pattern,\n clientAddress: ctx.request.ip(),\n requestHeaders: request.headers,\n responseHeaders: ctx.response.getHeaders(),\n capturedRequestBody: completion.requestBody,\n capturedResponseBody: completion,\n });\n })\n .catch((error: unknown) => {\n logWarning(`Error in the Apitally middleware: ${String(error)}`);\n });\n } catch (error) {\n logWarning(`Error in the Apitally middleware: ${String(error)}`);\n }\n\n return requestContext ? context.with(requestContext, next) : next();\n }\n}\n"],"mappings":";;;;;;;AAUA,MAAM,cAAc;AAEpB,IAAqB,qBAArB,MAAwC;CACtC,MAAM,OAAO,KAAkB,MAAgC;EAC7D,IAAI,CAAC,YAAY,GACf,OAAO,KAAK;EAGd,IAAI;EACJ,IAAI;GACF,MAAM,UAAU,IAAI,QAAQ;GAC5B,MAAM,WAAW,IAAI,SAAS;GAC9B,MAAM,UAAU,4BAA4B;IAAE;IAAS,YAAY;GAAY,CAAC;GAChF,iBAAiB,QAAQ;GACzB,QAAQ,KAAK,gBAAgB,OAAO;GACpC,QAAQ,KAAK,gBAAgB,QAAQ;GACrC,oBACE,UACA,UAAU,CAAC,CAAC,uBACV,QAAQ,YAAY,cAAc,eAAe,KAAA,GACnD,QAAQ,YAAY,qBACnB,SAAS,IAAI,SAAS,UAAU,IAAI,CACvC,CAAC,CACE,MAAM,eAAe;IACpB,2BAA2B;KACzB,aAAa,QAAQ;KACrB,mBAAmB,WAAW;KAC9B,YAAY,SAAS;KACrB,OAAO,IAAI,OAAO;KAClB,eAAe,IAAI,QAAQ,GAAG;KAC9B,gBAAgB,QAAQ;KACxB,iBAAiB,IAAI,SAAS,WAAW;KACzC,qBAAqB,WAAW;KAChC,sBAAsB;IACxB,CAAC;GACH,CAAC,CAAC,CACD,OAAO,UAAmB;IACzB,WAAW,qCAAqC,OAAO,KAAK,GAAG;GACjE,CAAC;EACL,SAAS,OAAO;GACd,WAAW,qCAAqC,OAAO,KAAK,GAAG;EACjE;EAEA,OAAO,iBAAiB,QAAQ,KAAK,gBAAgB,IAAI,IAAI,KAAK;CACpE;AACF"}
1
+ {"version":3,"file":"middleware.js","names":[],"sources":["../../src/adonisjs/middleware.ts"],"sourcesContent":["import type { HttpContext } from \"@adonisjs/core/http\";\nimport type { NextFn } from \"@adonisjs/core/types/http\";\nimport { type Context, context } from \"@opentelemetry/api\";\n\nimport { isActivated } from \"../activation.js\";\nimport { getConfig } from \"../config.js\";\nimport { logWarning } from \"../logger.js\";\nimport { finalizeRequestObservation } from \"../requestObservation.js\";\nimport { captureNodeResponse, startNodeRequestObservation } from \"../requestObservationNode.js\";\n\nconst TRACER_NAME = \"apitally/adonisjs\";\n\nexport default class ApitallyMiddleware {\n async handle(ctx: HttpContext, next: NextFn): Promise<unknown> {\n if (!isActivated()) {\n return next();\n }\n\n let requestContext: Context | undefined;\n try {\n const request = ctx.request.request;\n const response = ctx.response.response;\n const started = startNodeRequestObservation({ request, tracerName: TRACER_NAME });\n requestContext = started.requestContext;\n context.bind(requestContext, request);\n context.bind(requestContext, response);\n captureNodeResponse(\n response,\n getConfig().captureResponseBody &&\n started.observation.requestRecord.dropReason === undefined,\n started.observation.requestBodyCapture,\n (name) => ctx.response.getHeader(name),\n )\n .then((completion) => {\n finalizeRequestObservation({\n observation: started.observation,\n completedAtMillis: completion.completedAtMillis,\n statusCode: response.statusCode,\n route: ctx.route?.pattern,\n clientAddress: ctx.request.ip(),\n requestHeaders: request.headers,\n responseHeaders: ctx.response.getHeaders(),\n capturedRequestBody: completion.requestBody,\n capturedResponseBody: completion,\n });\n })\n .catch((error: unknown) => {\n logWarning(`Error in the Apitally middleware: ${String(error)}`);\n });\n } catch (error) {\n logWarning(`Error in the Apitally middleware: ${String(error)}`);\n }\n\n return requestContext ? context.with(requestContext, next) : next();\n }\n}\n"],"mappings":";;;;;;;AAUA,MAAM,cAAc;AAEpB,IAAqB,qBAArB,MAAwC;CACtC,MAAM,OAAO,KAAkB,MAAgC;EAC7D,IAAI,CAAC,YAAY,GACf,OAAO,KAAK;EAGd,IAAI;EACJ,IAAI;GACF,MAAM,UAAU,IAAI,QAAQ;GAC5B,MAAM,WAAW,IAAI,SAAS;GAC9B,MAAM,UAAU,4BAA4B;IAAE;IAAS,YAAY;GAAY,CAAC;GAChF,iBAAiB,QAAQ;GACzB,QAAQ,KAAK,gBAAgB,OAAO;GACpC,QAAQ,KAAK,gBAAgB,QAAQ;GACrC,oBACE,UACA,UAAU,CAAC,CAAC,uBACV,QAAQ,YAAY,cAAc,eAAe,KAAA,GACnD,QAAQ,YAAY,qBACnB,SAAS,IAAI,SAAS,UAAU,IAAI,CACvC,CAAC,CACE,MAAM,eAAe;IACpB,2BAA2B;KACzB,aAAa,QAAQ;KACrB,mBAAmB,WAAW;KAC9B,YAAY,SAAS;KACrB,OAAO,IAAI,OAAO;KAClB,eAAe,IAAI,QAAQ,GAAG;KAC9B,gBAAgB,QAAQ;KACxB,iBAAiB,IAAI,SAAS,WAAW;KACzC,qBAAqB,WAAW;KAChC,sBAAsB;IACxB,CAAC;GACH,CAAC,CAAC,CACD,OAAO,UAAmB;IACzB,WAAW,qCAAqC,OAAO,KAAK,GAAG;GACjE,CAAC;EACL,SAAS,OAAO;GACd,WAAW,qCAAqC,OAAO,KAAK,GAAG;EACjE;EAEA,OAAO,iBAAiB,QAAQ,KAAK,gBAAgB,IAAI,IAAI,KAAK;CACpE;AACF"}
package/dist/config.cjs CHANGED
@@ -26,6 +26,8 @@ const DEFAULT_MASK_BODY_FIELDS = [
26
26
  "auth",
27
27
  "card[-_ ]?number",
28
28
  "ccv",
29
+ "cvv",
30
+ "cvc",
29
31
  "ssn"
30
32
  ];
31
33
  const DEFAULT_EXCLUDE_PATHS = [
@@ -77,10 +79,11 @@ function isValidWriteToken(value) {
77
79
  return WRITE_TOKEN_FORMAT.test(value);
78
80
  }
79
81
  function matchesAny(patterns, value) {
80
- return patterns.some((pattern) => {
82
+ for (const pattern of patterns) {
81
83
  pattern.lastIndex = 0;
82
- return pattern.test(value);
83
- });
84
+ if (pattern.test(value)) return true;
85
+ }
86
+ return false;
84
87
  }
85
88
  function compilePatterns(defaults, userPatterns = []) {
86
89
  return [...defaults.map((pattern) => new RegExp(pattern, "i")), ...userPatterns.map((pattern) => new RegExp(pattern))];
@@ -1 +1 @@
1
- {"version":3,"file":"config.cjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["import type { ReadWriteLogRecord } from \"@opentelemetry/sdk-logs\";\nimport type { ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\nimport { logError, logWarning } from \"./logger.js\";\n\nexport type BodyMaskingCallback = (body: Buffer, span: ReadableSpan) => Buffer | null;\nexport type LogRecordMaskingCallback = (\n logRecord: ReadWriteLogRecord,\n) => ReadWriteLogRecord | null | undefined;\nexport type SamplingCallback = (span: ReadableSpan) => number | boolean | undefined;\n\nexport interface ApitallyOptions {\n writeToken?: string;\n env?: string;\n appVersion?: string;\n disabled?: boolean;\n captureLogs?: boolean;\n captureRequestHeaders?: boolean;\n captureRequestBody?: boolean;\n captureResponseHeaders?: boolean;\n captureResponseBody?: boolean;\n maskQueryParams?: RegExp[];\n maskHeaders?: RegExp[];\n maskBodyFields?: RegExp[];\n maskRequestBody?: BodyMaskingCallback;\n maskResponseBody?: BodyMaskingCallback;\n maskLogRecord?: LogRecordMaskingCallback;\n excludePaths?: RegExp[];\n sampleRate?: number;\n sampleOnRequest?: SamplingCallback;\n sampleOnResponse?: SamplingCallback;\n}\n\ntype OptionalConfigKeys =\n | \"appVersion\"\n | \"maskRequestBody\"\n | \"maskResponseBody\"\n | \"maskLogRecord\"\n | \"sampleOnRequest\"\n | \"sampleOnResponse\";\n\nexport type ApitallyConfig = Required<Omit<ApitallyOptions, OptionalConfigKeys>> &\n Pick<ApitallyOptions, OptionalConfigKeys> & { otlpEndpoint: string };\n\nconst DEFAULT_OTLP_ENDPOINT = \"https://otlp.apitally.io\";\nexport const DEFAULT_ENV = \"dev\";\n\n// User-supplied patterns extend these defaults, never replace them.\nexport const DEFAULT_MASK_QUERY_PARAMS = [\n \"auth\",\n \"api[-_]?key\",\n \"secret\",\n \"token\",\n \"password\",\n \"pwd\",\n];\nexport const DEFAULT_MASK_HEADERS = [\"auth\", \"api[-_]?key\", \"secret\", \"token\", \"cookie\"];\nexport const DEFAULT_MASK_BODY_FIELDS = [\n \"password\",\n \"pwd\",\n \"token\",\n \"secret\",\n \"auth\",\n \"card[-_ ]?number\",\n \"ccv\",\n \"ssn\",\n];\nexport const DEFAULT_EXCLUDE_PATHS = [\n \"/_?healthz?/?$\",\n \"/_?health[-_]?checks?/?$\",\n \"/_?heart[-_]?beats?/?$\",\n \"/ping/?$\",\n \"/ready/?$\",\n \"/live/?$\",\n \"/favicon(?:-[\\\\w-]+)?\\\\.(ico|png|svg)$\",\n \"/apple-touch-icon(?:-[\\\\w-]+)?\\\\.png$\",\n \"/robots\\\\.txt$\",\n \"/sitemap\\\\.xml$\",\n \"/manifest\\\\.json$\",\n \"/site\\\\.webmanifest$\",\n \"/service-worker\\\\.js$\",\n \"/sw\\\\.js$\",\n \"/\\\\.well-known/\",\n];\nexport const EXCLUDE_USER_AGENTS = [\n \"health[-_ ]?check\",\n \"microsoft-azure-application-lb\",\n \"googlehc\",\n \"kube-probe\",\n];\n\nconst WRITE_TOKEN_FORMAT = /^apt_[a-zA-Z0-9]{24}$/;\nconst TRUE_VALUES = new Set([\"1\", \"true\", \"yes\"]);\n\n// The ESM and CJS builds can load together, so a Symbol.for key gives both\n// copies the same configuration.\nconst GLOBAL_CONFIG_KEY = Symbol.for(\"apitally.config\");\nconst configHolder = globalThis as Record<symbol, ApitallyConfig | undefined>;\n\nexport function setConfig(options: ApitallyOptions = {}): ApitallyConfig {\n const { config, error } = resolveConfig(options);\n const currentConfig = configHolder[GLOBAL_CONFIG_KEY];\n if (currentConfig) {\n if (!isSameConfig(config, currentConfig)) {\n logWarning(\n \"useApitally() was called again with different options; the first call's configuration stays in effect\",\n );\n }\n return currentConfig;\n }\n if (error) {\n logError(error);\n }\n configHolder[GLOBAL_CONFIG_KEY] = config;\n return config;\n}\n\nexport function getConfig(): ApitallyConfig {\n return configHolder[GLOBAL_CONFIG_KEY] ?? resolveConfig({}).config;\n}\n\nexport function isValidWriteToken(value: string): boolean {\n return WRITE_TOKEN_FORMAT.test(value);\n}\n\nexport function matchesAny(patterns: RegExp[], value: string): boolean {\n return patterns.some((pattern) => {\n pattern.lastIndex = 0;\n return pattern.test(value);\n });\n}\n\nexport function compilePatterns(defaults: string[], userPatterns: RegExp[] = []): RegExp[] {\n return [\n ...defaults.map((pattern) => new RegExp(pattern, \"i\")),\n ...userPatterns.map((pattern) => new RegExp(pattern)),\n ];\n}\n\nfunction resolveConfig(options: ApitallyOptions): {\n config: ApitallyConfig;\n error?: string;\n} {\n const config: ApitallyConfig = {\n writeToken: options.writeToken ?? nonEmptyEnvVar(\"APITALLY_WRITE_TOKEN\") ?? \"\",\n env: options.env ?? nonEmptyEnvVar(\"APITALLY_ENV\") ?? DEFAULT_ENV,\n appVersion: options.appVersion,\n disabled:\n options.disabled === true ||\n isTruthyEnvValue(process.env.APITALLY_DISABLED) ||\n isTruthyEnvValue(process.env.OTEL_SDK_DISABLED),\n captureLogs: options.captureLogs ?? true,\n captureRequestHeaders: options.captureRequestHeaders ?? false,\n captureRequestBody: options.captureRequestBody ?? false,\n captureResponseHeaders: options.captureResponseHeaders ?? true,\n captureResponseBody: options.captureResponseBody ?? false,\n maskQueryParams: dropNonRegExpPatterns(\"maskQueryParams\", options.maskQueryParams),\n maskHeaders: dropNonRegExpPatterns(\"maskHeaders\", options.maskHeaders),\n maskBodyFields: dropNonRegExpPatterns(\"maskBodyFields\", options.maskBodyFields),\n maskRequestBody: options.maskRequestBody,\n maskResponseBody: options.maskResponseBody,\n maskLogRecord: options.maskLogRecord,\n excludePaths: dropNonRegExpPatterns(\"excludePaths\", options.excludePaths),\n // An invalid sampleRate resolves to capturing everything: no data is lost, so no warning.\n sampleRate:\n typeof options.sampleRate === \"number\" && options.sampleRate >= 0 && options.sampleRate <= 1\n ? options.sampleRate\n : 1,\n sampleOnRequest: options.sampleOnRequest,\n sampleOnResponse: options.sampleOnResponse,\n otlpEndpoint: nonEmptyEnvVar(\"APITALLY_OTLP_ENDPOINT\") ?? DEFAULT_OTLP_ENDPOINT,\n };\n if (config.disabled) {\n return { config };\n }\n if (!config.writeToken) {\n config.disabled = true;\n return {\n config,\n error:\n \"Apitally write token is missing (set the writeToken option or the APITALLY_WRITE_TOKEN environment variable)\",\n };\n }\n if (!isValidWriteToken(config.writeToken)) {\n config.disabled = true;\n // The write token is a credential and must never appear unmasked in logs.\n return {\n config,\n error: `Apitally write token has an invalid format: ${config.writeToken.slice(0, 8)}...`,\n };\n }\n if (!isHttpUrl(config.otlpEndpoint)) {\n config.disabled = true;\n return {\n config,\n error: `Apitally OTLP endpoint is not a valid HTTP or HTTPS URL: ${config.otlpEndpoint}`,\n };\n }\n return { config };\n}\n\nfunction isHttpUrl(value: string): boolean {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n}\n\n// Invalid pattern values could leave data unredacted, so they are logged and omitted.\nfunction dropNonRegExpPatterns(optionName: string, patterns: RegExp[] = []): RegExp[] {\n return patterns.filter((pattern) => {\n if (pattern instanceof RegExp) {\n return true;\n }\n logError(`Pattern in ${optionName} ignored because it is not a RegExp: ${String(pattern)}`);\n return false;\n });\n}\n\nfunction isSameConfig(a: ApitallyConfig, b: ApitallyConfig): boolean {\n return (Object.keys(a) as (keyof ApitallyConfig)[]).every((key) => {\n const left = a[key];\n const right = b[key];\n if (Array.isArray(left) && Array.isArray(right)) {\n return (\n left.length === right.length &&\n left.every((item, index) => {\n const other = right[index];\n return (\n item instanceof RegExp &&\n other instanceof RegExp &&\n item.source === other.source &&\n item.flags === other.flags\n );\n })\n );\n }\n // Callback identity says nothing about sameness; app factories create them inline.\n if (typeof left === \"function\" && typeof right === \"function\") {\n return true;\n }\n return left === right;\n });\n}\n\nfunction isTruthyEnvValue(value: string | undefined): boolean {\n return value !== undefined && TRUE_VALUES.has(value.trim().toLowerCase());\n}\n\nfunction nonEmptyEnvVar(name: string): string | undefined {\n return process.env[name]?.trim() || undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config.cjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["import type { ReadWriteLogRecord } from \"@opentelemetry/sdk-logs\";\nimport type { ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\nimport { logError, logWarning } from \"./logger.js\";\n\nexport type BodyMaskingCallback = (body: Buffer, span: ReadableSpan) => Buffer | null;\nexport type LogRecordMaskingCallback = (\n logRecord: ReadWriteLogRecord,\n) => ReadWriteLogRecord | null | undefined;\nexport type SamplingCallback = (span: ReadableSpan) => number | boolean | undefined;\n\nexport interface ApitallyOptions {\n writeToken?: string;\n env?: string;\n appVersion?: string;\n disabled?: boolean;\n captureLogs?: boolean;\n captureRequestHeaders?: boolean;\n captureRequestBody?: boolean;\n captureResponseHeaders?: boolean;\n captureResponseBody?: boolean;\n maskQueryParams?: RegExp[];\n maskHeaders?: RegExp[];\n maskBodyFields?: RegExp[];\n maskRequestBody?: BodyMaskingCallback;\n maskResponseBody?: BodyMaskingCallback;\n maskLogRecord?: LogRecordMaskingCallback;\n excludePaths?: RegExp[];\n sampleRate?: number;\n sampleOnRequest?: SamplingCallback;\n sampleOnResponse?: SamplingCallback;\n}\n\ntype OptionalConfigKeys =\n | \"appVersion\"\n | \"maskRequestBody\"\n | \"maskResponseBody\"\n | \"maskLogRecord\"\n | \"sampleOnRequest\"\n | \"sampleOnResponse\";\n\nexport type ApitallyConfig = Required<Omit<ApitallyOptions, OptionalConfigKeys>> &\n Pick<ApitallyOptions, OptionalConfigKeys> & { otlpEndpoint: string };\n\nconst DEFAULT_OTLP_ENDPOINT = \"https://otlp.apitally.io\";\nexport const DEFAULT_ENV = \"dev\";\n\n// User-supplied patterns extend these defaults, never replace them.\nexport const DEFAULT_MASK_QUERY_PARAMS = [\n \"auth\",\n \"api[-_]?key\",\n \"secret\",\n \"token\",\n \"password\",\n \"pwd\",\n];\nexport const DEFAULT_MASK_HEADERS = [\"auth\", \"api[-_]?key\", \"secret\", \"token\", \"cookie\"];\nexport const DEFAULT_MASK_BODY_FIELDS = [\n \"password\",\n \"pwd\",\n \"token\",\n \"secret\",\n \"auth\",\n \"card[-_ ]?number\",\n \"ccv\",\n \"cvv\",\n \"cvc\",\n \"ssn\",\n];\nexport const DEFAULT_EXCLUDE_PATHS = [\n \"/_?healthz?/?$\",\n \"/_?health[-_]?checks?/?$\",\n \"/_?heart[-_]?beats?/?$\",\n \"/ping/?$\",\n \"/ready/?$\",\n \"/live/?$\",\n \"/favicon(?:-[\\\\w-]+)?\\\\.(ico|png|svg)$\",\n \"/apple-touch-icon(?:-[\\\\w-]+)?\\\\.png$\",\n \"/robots\\\\.txt$\",\n \"/sitemap\\\\.xml$\",\n \"/manifest\\\\.json$\",\n \"/site\\\\.webmanifest$\",\n \"/service-worker\\\\.js$\",\n \"/sw\\\\.js$\",\n \"/\\\\.well-known/\",\n];\nexport const EXCLUDE_USER_AGENTS = [\n \"health[-_ ]?check\",\n \"microsoft-azure-application-lb\",\n \"googlehc\",\n \"kube-probe\",\n];\n\nconst WRITE_TOKEN_FORMAT = /^apt_[a-zA-Z0-9]{24}$/;\nconst TRUE_VALUES = new Set([\"1\", \"true\", \"yes\"]);\n\n// The ESM and CJS builds can load together, so a Symbol.for key gives both\n// copies the same configuration.\nconst GLOBAL_CONFIG_KEY = Symbol.for(\"apitally.config\");\nconst configHolder = globalThis as Record<symbol, ApitallyConfig | undefined>;\n\nexport function setConfig(options: ApitallyOptions = {}): ApitallyConfig {\n const { config, error } = resolveConfig(options);\n const currentConfig = configHolder[GLOBAL_CONFIG_KEY];\n if (currentConfig) {\n if (!isSameConfig(config, currentConfig)) {\n logWarning(\n \"useApitally() was called again with different options; the first call's configuration stays in effect\",\n );\n }\n return currentConfig;\n }\n if (error) {\n logError(error);\n }\n configHolder[GLOBAL_CONFIG_KEY] = config;\n return config;\n}\n\nexport function getConfig(): ApitallyConfig {\n return configHolder[GLOBAL_CONFIG_KEY] ?? resolveConfig({}).config;\n}\n\nexport function isValidWriteToken(value: string): boolean {\n return WRITE_TOKEN_FORMAT.test(value);\n}\n\nexport function matchesAny(patterns: RegExp[], value: string): boolean {\n for (const pattern of patterns) {\n pattern.lastIndex = 0;\n if (pattern.test(value)) {\n return true;\n }\n }\n return false;\n}\n\nexport function compilePatterns(defaults: string[], userPatterns: RegExp[] = []): RegExp[] {\n return [\n ...defaults.map((pattern) => new RegExp(pattern, \"i\")),\n ...userPatterns.map((pattern) => new RegExp(pattern)),\n ];\n}\n\nfunction resolveConfig(options: ApitallyOptions): {\n config: ApitallyConfig;\n error?: string;\n} {\n const config: ApitallyConfig = {\n writeToken: options.writeToken ?? nonEmptyEnvVar(\"APITALLY_WRITE_TOKEN\") ?? \"\",\n env: options.env ?? nonEmptyEnvVar(\"APITALLY_ENV\") ?? DEFAULT_ENV,\n appVersion: options.appVersion,\n disabled:\n options.disabled === true ||\n isTruthyEnvValue(process.env.APITALLY_DISABLED) ||\n isTruthyEnvValue(process.env.OTEL_SDK_DISABLED),\n captureLogs: options.captureLogs ?? true,\n captureRequestHeaders: options.captureRequestHeaders ?? false,\n captureRequestBody: options.captureRequestBody ?? false,\n captureResponseHeaders: options.captureResponseHeaders ?? true,\n captureResponseBody: options.captureResponseBody ?? false,\n maskQueryParams: dropNonRegExpPatterns(\"maskQueryParams\", options.maskQueryParams),\n maskHeaders: dropNonRegExpPatterns(\"maskHeaders\", options.maskHeaders),\n maskBodyFields: dropNonRegExpPatterns(\"maskBodyFields\", options.maskBodyFields),\n maskRequestBody: options.maskRequestBody,\n maskResponseBody: options.maskResponseBody,\n maskLogRecord: options.maskLogRecord,\n excludePaths: dropNonRegExpPatterns(\"excludePaths\", options.excludePaths),\n // An invalid sampleRate resolves to capturing everything: no data is lost, so no warning.\n sampleRate:\n typeof options.sampleRate === \"number\" && options.sampleRate >= 0 && options.sampleRate <= 1\n ? options.sampleRate\n : 1,\n sampleOnRequest: options.sampleOnRequest,\n sampleOnResponse: options.sampleOnResponse,\n otlpEndpoint: nonEmptyEnvVar(\"APITALLY_OTLP_ENDPOINT\") ?? DEFAULT_OTLP_ENDPOINT,\n };\n if (config.disabled) {\n return { config };\n }\n if (!config.writeToken) {\n config.disabled = true;\n return {\n config,\n error:\n \"Apitally write token is missing (set the writeToken option or the APITALLY_WRITE_TOKEN environment variable)\",\n };\n }\n if (!isValidWriteToken(config.writeToken)) {\n config.disabled = true;\n // The write token is a credential and must never appear unmasked in logs.\n return {\n config,\n error: `Apitally write token has an invalid format: ${config.writeToken.slice(0, 8)}...`,\n };\n }\n if (!isHttpUrl(config.otlpEndpoint)) {\n config.disabled = true;\n return {\n config,\n error: `Apitally OTLP endpoint is not a valid HTTP or HTTPS URL: ${config.otlpEndpoint}`,\n };\n }\n return { config };\n}\n\nfunction isHttpUrl(value: string): boolean {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n}\n\n// Invalid pattern values could leave data unredacted, so they are logged and omitted.\nfunction dropNonRegExpPatterns(optionName: string, patterns: RegExp[] = []): RegExp[] {\n return patterns.filter((pattern) => {\n if (pattern instanceof RegExp) {\n return true;\n }\n logError(`Pattern in ${optionName} ignored because it is not a RegExp: ${String(pattern)}`);\n return false;\n });\n}\n\nfunction isSameConfig(a: ApitallyConfig, b: ApitallyConfig): boolean {\n return (Object.keys(a) as (keyof ApitallyConfig)[]).every((key) => {\n const left = a[key];\n const right = b[key];\n if (Array.isArray(left) && Array.isArray(right)) {\n return (\n left.length === right.length &&\n left.every((item, index) => {\n const other = right[index];\n return (\n item instanceof RegExp &&\n other instanceof RegExp &&\n item.source === other.source &&\n item.flags === other.flags\n );\n })\n );\n }\n // Callback identity says nothing about sameness; app factories create them inline.\n if (typeof left === \"function\" && typeof right === \"function\") {\n return true;\n }\n return left === right;\n });\n}\n\nfunction isTruthyEnvValue(value: string | undefined): boolean {\n return value !== undefined && TRUE_VALUES.has(value.trim().toLowerCase());\n}\n\nfunction nonEmptyEnvVar(name: string): string | undefined {\n return process.env[name]?.trim() || undefined;\n}\n"],"mappings":"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"}
package/dist/config.js CHANGED
@@ -25,6 +25,8 @@ const DEFAULT_MASK_BODY_FIELDS = [
25
25
  "auth",
26
26
  "card[-_ ]?number",
27
27
  "ccv",
28
+ "cvv",
29
+ "cvc",
28
30
  "ssn"
29
31
  ];
30
32
  const DEFAULT_EXCLUDE_PATHS = [
@@ -76,10 +78,11 @@ function isValidWriteToken(value) {
76
78
  return WRITE_TOKEN_FORMAT.test(value);
77
79
  }
78
80
  function matchesAny(patterns, value) {
79
- return patterns.some((pattern) => {
81
+ for (const pattern of patterns) {
80
82
  pattern.lastIndex = 0;
81
- return pattern.test(value);
82
- });
83
+ if (pattern.test(value)) return true;
84
+ }
85
+ return false;
83
86
  }
84
87
  function compilePatterns(defaults, userPatterns = []) {
85
88
  return [...defaults.map((pattern) => new RegExp(pattern, "i")), ...userPatterns.map((pattern) => new RegExp(pattern))];
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","names":[],"sources":["../src/config.ts"],"sourcesContent":["import type { ReadWriteLogRecord } from \"@opentelemetry/sdk-logs\";\nimport type { ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\nimport { logError, logWarning } from \"./logger.js\";\n\nexport type BodyMaskingCallback = (body: Buffer, span: ReadableSpan) => Buffer | null;\nexport type LogRecordMaskingCallback = (\n logRecord: ReadWriteLogRecord,\n) => ReadWriteLogRecord | null | undefined;\nexport type SamplingCallback = (span: ReadableSpan) => number | boolean | undefined;\n\nexport interface ApitallyOptions {\n writeToken?: string;\n env?: string;\n appVersion?: string;\n disabled?: boolean;\n captureLogs?: boolean;\n captureRequestHeaders?: boolean;\n captureRequestBody?: boolean;\n captureResponseHeaders?: boolean;\n captureResponseBody?: boolean;\n maskQueryParams?: RegExp[];\n maskHeaders?: RegExp[];\n maskBodyFields?: RegExp[];\n maskRequestBody?: BodyMaskingCallback;\n maskResponseBody?: BodyMaskingCallback;\n maskLogRecord?: LogRecordMaskingCallback;\n excludePaths?: RegExp[];\n sampleRate?: number;\n sampleOnRequest?: SamplingCallback;\n sampleOnResponse?: SamplingCallback;\n}\n\ntype OptionalConfigKeys =\n | \"appVersion\"\n | \"maskRequestBody\"\n | \"maskResponseBody\"\n | \"maskLogRecord\"\n | \"sampleOnRequest\"\n | \"sampleOnResponse\";\n\nexport type ApitallyConfig = Required<Omit<ApitallyOptions, OptionalConfigKeys>> &\n Pick<ApitallyOptions, OptionalConfigKeys> & { otlpEndpoint: string };\n\nconst DEFAULT_OTLP_ENDPOINT = \"https://otlp.apitally.io\";\nexport const DEFAULT_ENV = \"dev\";\n\n// User-supplied patterns extend these defaults, never replace them.\nexport const DEFAULT_MASK_QUERY_PARAMS = [\n \"auth\",\n \"api[-_]?key\",\n \"secret\",\n \"token\",\n \"password\",\n \"pwd\",\n];\nexport const DEFAULT_MASK_HEADERS = [\"auth\", \"api[-_]?key\", \"secret\", \"token\", \"cookie\"];\nexport const DEFAULT_MASK_BODY_FIELDS = [\n \"password\",\n \"pwd\",\n \"token\",\n \"secret\",\n \"auth\",\n \"card[-_ ]?number\",\n \"ccv\",\n \"ssn\",\n];\nexport const DEFAULT_EXCLUDE_PATHS = [\n \"/_?healthz?/?$\",\n \"/_?health[-_]?checks?/?$\",\n \"/_?heart[-_]?beats?/?$\",\n \"/ping/?$\",\n \"/ready/?$\",\n \"/live/?$\",\n \"/favicon(?:-[\\\\w-]+)?\\\\.(ico|png|svg)$\",\n \"/apple-touch-icon(?:-[\\\\w-]+)?\\\\.png$\",\n \"/robots\\\\.txt$\",\n \"/sitemap\\\\.xml$\",\n \"/manifest\\\\.json$\",\n \"/site\\\\.webmanifest$\",\n \"/service-worker\\\\.js$\",\n \"/sw\\\\.js$\",\n \"/\\\\.well-known/\",\n];\nexport const EXCLUDE_USER_AGENTS = [\n \"health[-_ ]?check\",\n \"microsoft-azure-application-lb\",\n \"googlehc\",\n \"kube-probe\",\n];\n\nconst WRITE_TOKEN_FORMAT = /^apt_[a-zA-Z0-9]{24}$/;\nconst TRUE_VALUES = new Set([\"1\", \"true\", \"yes\"]);\n\n// The ESM and CJS builds can load together, so a Symbol.for key gives both\n// copies the same configuration.\nconst GLOBAL_CONFIG_KEY = Symbol.for(\"apitally.config\");\nconst configHolder = globalThis as Record<symbol, ApitallyConfig | undefined>;\n\nexport function setConfig(options: ApitallyOptions = {}): ApitallyConfig {\n const { config, error } = resolveConfig(options);\n const currentConfig = configHolder[GLOBAL_CONFIG_KEY];\n if (currentConfig) {\n if (!isSameConfig(config, currentConfig)) {\n logWarning(\n \"useApitally() was called again with different options; the first call's configuration stays in effect\",\n );\n }\n return currentConfig;\n }\n if (error) {\n logError(error);\n }\n configHolder[GLOBAL_CONFIG_KEY] = config;\n return config;\n}\n\nexport function getConfig(): ApitallyConfig {\n return configHolder[GLOBAL_CONFIG_KEY] ?? resolveConfig({}).config;\n}\n\nexport function isValidWriteToken(value: string): boolean {\n return WRITE_TOKEN_FORMAT.test(value);\n}\n\nexport function matchesAny(patterns: RegExp[], value: string): boolean {\n return patterns.some((pattern) => {\n pattern.lastIndex = 0;\n return pattern.test(value);\n });\n}\n\nexport function compilePatterns(defaults: string[], userPatterns: RegExp[] = []): RegExp[] {\n return [\n ...defaults.map((pattern) => new RegExp(pattern, \"i\")),\n ...userPatterns.map((pattern) => new RegExp(pattern)),\n ];\n}\n\nfunction resolveConfig(options: ApitallyOptions): {\n config: ApitallyConfig;\n error?: string;\n} {\n const config: ApitallyConfig = {\n writeToken: options.writeToken ?? nonEmptyEnvVar(\"APITALLY_WRITE_TOKEN\") ?? \"\",\n env: options.env ?? nonEmptyEnvVar(\"APITALLY_ENV\") ?? DEFAULT_ENV,\n appVersion: options.appVersion,\n disabled:\n options.disabled === true ||\n isTruthyEnvValue(process.env.APITALLY_DISABLED) ||\n isTruthyEnvValue(process.env.OTEL_SDK_DISABLED),\n captureLogs: options.captureLogs ?? true,\n captureRequestHeaders: options.captureRequestHeaders ?? false,\n captureRequestBody: options.captureRequestBody ?? false,\n captureResponseHeaders: options.captureResponseHeaders ?? true,\n captureResponseBody: options.captureResponseBody ?? false,\n maskQueryParams: dropNonRegExpPatterns(\"maskQueryParams\", options.maskQueryParams),\n maskHeaders: dropNonRegExpPatterns(\"maskHeaders\", options.maskHeaders),\n maskBodyFields: dropNonRegExpPatterns(\"maskBodyFields\", options.maskBodyFields),\n maskRequestBody: options.maskRequestBody,\n maskResponseBody: options.maskResponseBody,\n maskLogRecord: options.maskLogRecord,\n excludePaths: dropNonRegExpPatterns(\"excludePaths\", options.excludePaths),\n // An invalid sampleRate resolves to capturing everything: no data is lost, so no warning.\n sampleRate:\n typeof options.sampleRate === \"number\" && options.sampleRate >= 0 && options.sampleRate <= 1\n ? options.sampleRate\n : 1,\n sampleOnRequest: options.sampleOnRequest,\n sampleOnResponse: options.sampleOnResponse,\n otlpEndpoint: nonEmptyEnvVar(\"APITALLY_OTLP_ENDPOINT\") ?? DEFAULT_OTLP_ENDPOINT,\n };\n if (config.disabled) {\n return { config };\n }\n if (!config.writeToken) {\n config.disabled = true;\n return {\n config,\n error:\n \"Apitally write token is missing (set the writeToken option or the APITALLY_WRITE_TOKEN environment variable)\",\n };\n }\n if (!isValidWriteToken(config.writeToken)) {\n config.disabled = true;\n // The write token is a credential and must never appear unmasked in logs.\n return {\n config,\n error: `Apitally write token has an invalid format: ${config.writeToken.slice(0, 8)}...`,\n };\n }\n if (!isHttpUrl(config.otlpEndpoint)) {\n config.disabled = true;\n return {\n config,\n error: `Apitally OTLP endpoint is not a valid HTTP or HTTPS URL: ${config.otlpEndpoint}`,\n };\n }\n return { config };\n}\n\nfunction isHttpUrl(value: string): boolean {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n}\n\n// Invalid pattern values could leave data unredacted, so they are logged and omitted.\nfunction dropNonRegExpPatterns(optionName: string, patterns: RegExp[] = []): RegExp[] {\n return patterns.filter((pattern) => {\n if (pattern instanceof RegExp) {\n return true;\n }\n logError(`Pattern in ${optionName} ignored because it is not a RegExp: ${String(pattern)}`);\n return false;\n });\n}\n\nfunction isSameConfig(a: ApitallyConfig, b: ApitallyConfig): boolean {\n return (Object.keys(a) as (keyof ApitallyConfig)[]).every((key) => {\n const left = a[key];\n const right = b[key];\n if (Array.isArray(left) && Array.isArray(right)) {\n return (\n left.length === right.length &&\n left.every((item, index) => {\n const other = right[index];\n return (\n item instanceof RegExp &&\n other instanceof RegExp &&\n item.source === other.source &&\n item.flags === other.flags\n );\n })\n );\n }\n // Callback identity says nothing about sameness; app factories create them inline.\n if (typeof left === \"function\" && typeof right === \"function\") {\n return true;\n }\n return left === right;\n });\n}\n\nfunction isTruthyEnvValue(value: string | undefined): boolean {\n return value !== undefined && TRUE_VALUES.has(value.trim().toLowerCase());\n}\n\nfunction nonEmptyEnvVar(name: string): string | undefined {\n return process.env[name]?.trim() || undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config.js","names":[],"sources":["../src/config.ts"],"sourcesContent":["import type { ReadWriteLogRecord } from \"@opentelemetry/sdk-logs\";\nimport type { ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\nimport { logError, logWarning } from \"./logger.js\";\n\nexport type BodyMaskingCallback = (body: Buffer, span: ReadableSpan) => Buffer | null;\nexport type LogRecordMaskingCallback = (\n logRecord: ReadWriteLogRecord,\n) => ReadWriteLogRecord | null | undefined;\nexport type SamplingCallback = (span: ReadableSpan) => number | boolean | undefined;\n\nexport interface ApitallyOptions {\n writeToken?: string;\n env?: string;\n appVersion?: string;\n disabled?: boolean;\n captureLogs?: boolean;\n captureRequestHeaders?: boolean;\n captureRequestBody?: boolean;\n captureResponseHeaders?: boolean;\n captureResponseBody?: boolean;\n maskQueryParams?: RegExp[];\n maskHeaders?: RegExp[];\n maskBodyFields?: RegExp[];\n maskRequestBody?: BodyMaskingCallback;\n maskResponseBody?: BodyMaskingCallback;\n maskLogRecord?: LogRecordMaskingCallback;\n excludePaths?: RegExp[];\n sampleRate?: number;\n sampleOnRequest?: SamplingCallback;\n sampleOnResponse?: SamplingCallback;\n}\n\ntype OptionalConfigKeys =\n | \"appVersion\"\n | \"maskRequestBody\"\n | \"maskResponseBody\"\n | \"maskLogRecord\"\n | \"sampleOnRequest\"\n | \"sampleOnResponse\";\n\nexport type ApitallyConfig = Required<Omit<ApitallyOptions, OptionalConfigKeys>> &\n Pick<ApitallyOptions, OptionalConfigKeys> & { otlpEndpoint: string };\n\nconst DEFAULT_OTLP_ENDPOINT = \"https://otlp.apitally.io\";\nexport const DEFAULT_ENV = \"dev\";\n\n// User-supplied patterns extend these defaults, never replace them.\nexport const DEFAULT_MASK_QUERY_PARAMS = [\n \"auth\",\n \"api[-_]?key\",\n \"secret\",\n \"token\",\n \"password\",\n \"pwd\",\n];\nexport const DEFAULT_MASK_HEADERS = [\"auth\", \"api[-_]?key\", \"secret\", \"token\", \"cookie\"];\nexport const DEFAULT_MASK_BODY_FIELDS = [\n \"password\",\n \"pwd\",\n \"token\",\n \"secret\",\n \"auth\",\n \"card[-_ ]?number\",\n \"ccv\",\n \"cvv\",\n \"cvc\",\n \"ssn\",\n];\nexport const DEFAULT_EXCLUDE_PATHS = [\n \"/_?healthz?/?$\",\n \"/_?health[-_]?checks?/?$\",\n \"/_?heart[-_]?beats?/?$\",\n \"/ping/?$\",\n \"/ready/?$\",\n \"/live/?$\",\n \"/favicon(?:-[\\\\w-]+)?\\\\.(ico|png|svg)$\",\n \"/apple-touch-icon(?:-[\\\\w-]+)?\\\\.png$\",\n \"/robots\\\\.txt$\",\n \"/sitemap\\\\.xml$\",\n \"/manifest\\\\.json$\",\n \"/site\\\\.webmanifest$\",\n \"/service-worker\\\\.js$\",\n \"/sw\\\\.js$\",\n \"/\\\\.well-known/\",\n];\nexport const EXCLUDE_USER_AGENTS = [\n \"health[-_ ]?check\",\n \"microsoft-azure-application-lb\",\n \"googlehc\",\n \"kube-probe\",\n];\n\nconst WRITE_TOKEN_FORMAT = /^apt_[a-zA-Z0-9]{24}$/;\nconst TRUE_VALUES = new Set([\"1\", \"true\", \"yes\"]);\n\n// The ESM and CJS builds can load together, so a Symbol.for key gives both\n// copies the same configuration.\nconst GLOBAL_CONFIG_KEY = Symbol.for(\"apitally.config\");\nconst configHolder = globalThis as Record<symbol, ApitallyConfig | undefined>;\n\nexport function setConfig(options: ApitallyOptions = {}): ApitallyConfig {\n const { config, error } = resolveConfig(options);\n const currentConfig = configHolder[GLOBAL_CONFIG_KEY];\n if (currentConfig) {\n if (!isSameConfig(config, currentConfig)) {\n logWarning(\n \"useApitally() was called again with different options; the first call's configuration stays in effect\",\n );\n }\n return currentConfig;\n }\n if (error) {\n logError(error);\n }\n configHolder[GLOBAL_CONFIG_KEY] = config;\n return config;\n}\n\nexport function getConfig(): ApitallyConfig {\n return configHolder[GLOBAL_CONFIG_KEY] ?? resolveConfig({}).config;\n}\n\nexport function isValidWriteToken(value: string): boolean {\n return WRITE_TOKEN_FORMAT.test(value);\n}\n\nexport function matchesAny(patterns: RegExp[], value: string): boolean {\n for (const pattern of patterns) {\n pattern.lastIndex = 0;\n if (pattern.test(value)) {\n return true;\n }\n }\n return false;\n}\n\nexport function compilePatterns(defaults: string[], userPatterns: RegExp[] = []): RegExp[] {\n return [\n ...defaults.map((pattern) => new RegExp(pattern, \"i\")),\n ...userPatterns.map((pattern) => new RegExp(pattern)),\n ];\n}\n\nfunction resolveConfig(options: ApitallyOptions): {\n config: ApitallyConfig;\n error?: string;\n} {\n const config: ApitallyConfig = {\n writeToken: options.writeToken ?? nonEmptyEnvVar(\"APITALLY_WRITE_TOKEN\") ?? \"\",\n env: options.env ?? nonEmptyEnvVar(\"APITALLY_ENV\") ?? DEFAULT_ENV,\n appVersion: options.appVersion,\n disabled:\n options.disabled === true ||\n isTruthyEnvValue(process.env.APITALLY_DISABLED) ||\n isTruthyEnvValue(process.env.OTEL_SDK_DISABLED),\n captureLogs: options.captureLogs ?? true,\n captureRequestHeaders: options.captureRequestHeaders ?? false,\n captureRequestBody: options.captureRequestBody ?? false,\n captureResponseHeaders: options.captureResponseHeaders ?? true,\n captureResponseBody: options.captureResponseBody ?? false,\n maskQueryParams: dropNonRegExpPatterns(\"maskQueryParams\", options.maskQueryParams),\n maskHeaders: dropNonRegExpPatterns(\"maskHeaders\", options.maskHeaders),\n maskBodyFields: dropNonRegExpPatterns(\"maskBodyFields\", options.maskBodyFields),\n maskRequestBody: options.maskRequestBody,\n maskResponseBody: options.maskResponseBody,\n maskLogRecord: options.maskLogRecord,\n excludePaths: dropNonRegExpPatterns(\"excludePaths\", options.excludePaths),\n // An invalid sampleRate resolves to capturing everything: no data is lost, so no warning.\n sampleRate:\n typeof options.sampleRate === \"number\" && options.sampleRate >= 0 && options.sampleRate <= 1\n ? options.sampleRate\n : 1,\n sampleOnRequest: options.sampleOnRequest,\n sampleOnResponse: options.sampleOnResponse,\n otlpEndpoint: nonEmptyEnvVar(\"APITALLY_OTLP_ENDPOINT\") ?? DEFAULT_OTLP_ENDPOINT,\n };\n if (config.disabled) {\n return { config };\n }\n if (!config.writeToken) {\n config.disabled = true;\n return {\n config,\n error:\n \"Apitally write token is missing (set the writeToken option or the APITALLY_WRITE_TOKEN environment variable)\",\n };\n }\n if (!isValidWriteToken(config.writeToken)) {\n config.disabled = true;\n // The write token is a credential and must never appear unmasked in logs.\n return {\n config,\n error: `Apitally write token has an invalid format: ${config.writeToken.slice(0, 8)}...`,\n };\n }\n if (!isHttpUrl(config.otlpEndpoint)) {\n config.disabled = true;\n return {\n config,\n error: `Apitally OTLP endpoint is not a valid HTTP or HTTPS URL: ${config.otlpEndpoint}`,\n };\n }\n return { config };\n}\n\nfunction isHttpUrl(value: string): boolean {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n}\n\n// Invalid pattern values could leave data unredacted, so they are logged and omitted.\nfunction dropNonRegExpPatterns(optionName: string, patterns: RegExp[] = []): RegExp[] {\n return patterns.filter((pattern) => {\n if (pattern instanceof RegExp) {\n return true;\n }\n logError(`Pattern in ${optionName} ignored because it is not a RegExp: ${String(pattern)}`);\n return false;\n });\n}\n\nfunction isSameConfig(a: ApitallyConfig, b: ApitallyConfig): boolean {\n return (Object.keys(a) as (keyof ApitallyConfig)[]).every((key) => {\n const left = a[key];\n const right = b[key];\n if (Array.isArray(left) && Array.isArray(right)) {\n return (\n left.length === right.length &&\n left.every((item, index) => {\n const other = right[index];\n return (\n item instanceof RegExp &&\n other instanceof RegExp &&\n item.source === other.source &&\n item.flags === other.flags\n );\n })\n );\n }\n // Callback identity says nothing about sameness; app factories create them inline.\n if (typeof left === \"function\" && typeof right === \"function\") {\n return true;\n }\n return left === right;\n });\n}\n\nfunction isTruthyEnvValue(value: string | undefined): boolean {\n return value !== undefined && TRUE_VALUES.has(value.trim().toLowerCase());\n}\n\nfunction nonEmptyEnvVar(name: string): string | undefined {\n return process.env[name]?.trim() || undefined;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["createElysiaPlugin","Elysia"],"sources":["../../src/elysia/index.ts"],"sourcesContent":["import { type AnyElysia, Elysia } from \"elysia\";\nimport type { ApitallyOptions } from \"../config.js\";\nimport { createElysiaPlugin, installElysiaIntegration } from \"./middleware.js\";\n\nexport type { ApitallyOptions };\n\nexport function apitallyPlugin(options?: ApitallyOptions): Elysia {\n return createElysiaPlugin(Elysia, options) as Elysia;\n}\n\n// Setup stays synchronous; activation begins on the first request.\nexport function useApitally(app: AnyElysia, options?: ApitallyOptions): void {\n installElysiaIntegration(app, options);\n}\n"],"mappings":";;;;AAMA,SAAgB,eAAe,SAAmC;CAChE,OAAOA,0BAAAA,mBAAmBC,OAAAA,QAAQ,OAAO;AAC3C;AAGA,SAAgB,YAAY,KAAgB,SAAiC;CAC3E,0BAAA,yBAAyB,KAAK,OAAO;AACvC"}
1
+ {"version":3,"file":"index.cjs","names":["createElysiaPlugin","Elysia"],"sources":["../../src/elysia/index.ts"],"sourcesContent":["import { type AnyElysia, Elysia } from \"elysia\";\nimport type { ApitallyOptions } from \"../config.js\";\nimport { createElysiaPlugin, installElysiaIntegration } from \"./middleware.js\";\n\nexport type { ApitallyOptions };\n\nexport function apitallyPlugin(options?: ApitallyOptions): Elysia {\n return createElysiaPlugin(Elysia, options) as Elysia;\n}\n\n// Setup stays synchronous; activation begins when listen() starts the server,\n// or on the first request.\nexport function useApitally(app: AnyElysia, options?: ApitallyOptions): void {\n installElysiaIntegration(app, options);\n}\n"],"mappings":";;;;AAMA,SAAgB,eAAe,SAAmC;CAChE,OAAOA,0BAAAA,mBAAmBC,OAAAA,QAAQ,OAAO;AAC3C;AAIA,SAAgB,YAAY,KAAgB,SAAiC;CAC3E,0BAAA,yBAAyB,KAAK,OAAO;AACvC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/elysia/index.ts"],"sourcesContent":["import { type AnyElysia, Elysia } from \"elysia\";\nimport type { ApitallyOptions } from \"../config.js\";\nimport { createElysiaPlugin, installElysiaIntegration } from \"./middleware.js\";\n\nexport type { ApitallyOptions };\n\nexport function apitallyPlugin(options?: ApitallyOptions): Elysia {\n return createElysiaPlugin(Elysia, options) as Elysia;\n}\n\n// Setup stays synchronous; activation begins on the first request.\nexport function useApitally(app: AnyElysia, options?: ApitallyOptions): void {\n installElysiaIntegration(app, options);\n}\n"],"mappings":";;;AAMA,SAAgB,eAAe,SAAmC;CAChE,OAAO,mBAAmB,QAAQ,OAAO;AAC3C;AAGA,SAAgB,YAAY,KAAgB,SAAiC;CAC3E,yBAAyB,KAAK,OAAO;AACvC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/elysia/index.ts"],"sourcesContent":["import { type AnyElysia, Elysia } from \"elysia\";\nimport type { ApitallyOptions } from \"../config.js\";\nimport { createElysiaPlugin, installElysiaIntegration } from \"./middleware.js\";\n\nexport type { ApitallyOptions };\n\nexport function apitallyPlugin(options?: ApitallyOptions): Elysia {\n return createElysiaPlugin(Elysia, options) as Elysia;\n}\n\n// Setup stays synchronous; activation begins when listen() starts the server,\n// or on the first request.\nexport function useApitally(app: AnyElysia, options?: ApitallyOptions): void {\n installElysiaIntegration(app, options);\n}\n"],"mappings":";;;AAMA,SAAgB,eAAe,SAAmC;CAChE,OAAO,mBAAmB,QAAQ,OAAO;AAC3C;AAIA,SAAgB,YAAY,KAAgB,SAAiC;CAC3E,yBAAyB,KAAK,OAAO;AACvC"}
@@ -12,18 +12,16 @@ let _opentelemetry_api = require("@opentelemetry/api");
12
12
  //#region src/elysia/middleware.ts
13
13
  const INSTALL_MARKER = Symbol.for("apitally.elysiaInstall");
14
14
  const PLUGIN_NAME = "apitally";
15
- const TRACER_NAME = "apitally.elysia";
15
+ const TRACER_NAME = "apitally/elysia";
16
16
  function createElysiaPlugin(ElysiaClass, options) {
17
17
  require_activation.configure(options);
18
- let app;
18
+ const plugin = buildElysiaPlugin(ElysiaClass);
19
19
  require_activation.registerStartupEventInfo({
20
20
  framework: "elysia",
21
21
  frameworkVersion: require_packageVersion.resolvePackageVersion("elysia"),
22
- resolvePaths: () => app ? require_elysia_routes.resolveStartupPaths(app) : []
23
- });
24
- return buildElysiaPlugin(ElysiaClass, (startedApp) => {
25
- app = startedApp;
22
+ resolvePaths: () => require_elysia_routes.resolveStartupPaths(plugin)
26
23
  });
24
+ return plugin;
27
25
  }
28
26
  function installElysiaIntegration(app, options) {
29
27
  require_activation.configure(options);
@@ -42,7 +40,7 @@ function installElysiaIntegration(app, options) {
42
40
  const ElysiaClass = app.constructor;
43
41
  app.use(buildElysiaPlugin(ElysiaClass));
44
42
  }
45
- function buildElysiaPlugin(ElysiaClass, recordStartedApp) {
43
+ function buildElysiaPlugin(ElysiaClass) {
46
44
  const observations = /* @__PURE__ */ new WeakMap();
47
45
  const plugin = new ElysiaClass({ name: PLUGIN_NAME });
48
46
  let startedApp;
@@ -54,7 +52,7 @@ function buildElysiaPlugin(ElysiaClass, recordStartedApp) {
54
52
  const started = require_requestObservationWeb.startWebRequestObservation({
55
53
  request,
56
54
  tracerName: TRACER_NAME,
57
- clientAddress: startedApp?.server?.requestIP(request)?.address
55
+ clientAddress: startedApp?.server?.requestIP(request)?.address ?? request.ip
58
56
  });
59
57
  observation = {
60
58
  ...started.observation,
@@ -81,8 +79,8 @@ function buildElysiaPlugin(ElysiaClass, recordStartedApp) {
81
79
  }));
82
80
  plugin.onStart((app) => {
83
81
  startedApp = app;
84
- recordStartedApp?.(app);
85
- if (app.config.aot === false) require_logger.logWarning("Apitally does not observe requests on an Elysia app with aot: false. To resolve this, remove the aot: false option.");
82
+ if (app.config?.aot === false) require_logger.logWarning("Apitally does not observe requests on an Elysia app with aot: false. To resolve this, remove the aot: false option.");
83
+ require_activation.activate();
86
84
  });
87
85
  plugin.onTransform({ as: "global" }, ({ request, route }) => {
88
86
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.cjs","names":["resolvePackageVersion","resolveStartupPaths","isActivated","isWebSocketUpgrade","startWebRequestObservation","captureWebRequestBody","context","formatIssues","normalizeSource","captureException","captureWebResponse","getConfig","finalizeRequestObservation"],"sources":["../../src/elysia/middleware.ts"],"sourcesContent":["import { type Context, context } from \"@opentelemetry/api\";\nimport type { AnyElysia } from \"elysia\";\nimport {\n activate,\n configure,\n flushTelemetry,\n isActivated,\n registerStartupEventInfo,\n} from \"../activation.js\";\nimport type { ApitallyOptions } from \"../config.js\";\nimport { getConfig } from \"../config.js\";\nimport { captureException } from \"../exceptions.js\";\nimport { logDebug, logWarning } from \"../logger.js\";\nimport { resolvePackageVersion } from \"../packageVersion.js\";\nimport {\n finalizeRequestObservation,\n finalizeRequestObservationWithError,\n} from \"../requestObservation.js\";\nimport {\n captureWebRequestBody,\n captureWebResponse,\n isWebSocketUpgrade,\n startWebRequestObservation,\n type WebRequestObservation,\n} from \"../requestObservationWeb.js\";\nimport { formatIssues, normalizeSource } from \"../validationErrors.js\";\nimport { resolveStartupPaths } from \"./routes.js\";\n\nconst INSTALL_MARKER = Symbol.for(\"apitally.elysiaInstall\");\nconst PLUGIN_NAME = \"apitally\";\nconst TRACER_NAME = \"apitally.elysia\";\n\ninterface ElysiaConstructor {\n new (config?: { name?: string }): AnyElysia;\n}\n\ntype ElysiaDispatcher = (this: unknown, ...args: unknown[]) => Response | Promise<Response>;\ntype ElysiaWrap = Parameters<AnyElysia[\"wrap\"]>[0];\n\ninterface ElysiaRequestObservation extends WebRequestObservation {\n requestContext: Context;\n requestBodyCompletion: Promise<void>;\n route?: string;\n error?: unknown;\n}\n\nexport function createElysiaPlugin(\n ElysiaClass: ElysiaConstructor,\n options?: ApitallyOptions,\n): AnyElysia {\n configure(options);\n let app: AnyElysia | undefined;\n registerStartupEventInfo({\n framework: \"elysia\",\n frameworkVersion: resolvePackageVersion(\"elysia\"),\n resolvePaths: () => (app ? resolveStartupPaths(app) : []),\n });\n return buildElysiaPlugin(ElysiaClass, (startedApp) => {\n app = startedApp;\n });\n}\n\nexport function installElysiaIntegration(app: AnyElysia, options?: ApitallyOptions): void {\n configure(options);\n const markedApp = app as unknown as Record<symbol, boolean | undefined>;\n if (markedApp[INSTALL_MARKER] === true) {\n return;\n }\n if (app.routes.length > 0) {\n logWarning(\n \"useApitally() was called after routes were registered on the Elysia app, so Apitally was not installed. To resolve this, call useApitally() immediately after creating the app, before registering routes or plugins that contain routes.\",\n );\n return;\n }\n markedApp[INSTALL_MARKER] = true;\n registerStartupEventInfo({\n framework: \"elysia\",\n frameworkVersion: resolvePackageVersion(\"elysia\"),\n resolvePaths: () => resolveStartupPaths(app),\n });\n const ElysiaClass = app.constructor as ElysiaConstructor;\n app.use(buildElysiaPlugin(ElysiaClass));\n}\n\nfunction buildElysiaPlugin(\n ElysiaClass: ElysiaConstructor,\n recordStartedApp?: (app: AnyElysia) => void,\n): AnyElysia {\n const observations = new WeakMap<Request, ElysiaRequestObservation>();\n const plugin = new ElysiaClass({ name: PLUGIN_NAME });\n let startedApp: AnyElysia | undefined;\n\n plugin.wrap(\n ((dispatcher: ElysiaDispatcher, request: Request) =>\n function (this: unknown, ...args: unknown[]): Response | Promise<Response> {\n let observation: ElysiaRequestObservation;\n try {\n activate();\n if (!isActivated() || isWebSocketUpgrade(request)) {\n return dispatcher.apply(this, args);\n }\n const started = startWebRequestObservation({\n request,\n tracerName: TRACER_NAME,\n clientAddress: startedApp?.server?.requestIP(request)?.address,\n });\n observation = {\n ...started.observation,\n requestContext: started.requestContext,\n requestBodyCompletion: captureWebRequestBody(\n request,\n started.observation.requestBodyCapture,\n ),\n };\n observations.set(request, observation);\n } catch (error) {\n logWarning(`Error in the Apitally Elysia middleware: ${String(error)}`);\n return dispatcher.apply(this, args);\n }\n\n let result: Response | Promise<Response>;\n try {\n result = context.with(observation.requestContext, () => dispatcher.apply(this, args));\n } catch (error) {\n finalizeFailedRequestObservation(observations, request, error);\n throw error;\n }\n if (typeof (result as PromiseLike<Response>)?.then === \"function\") {\n return Promise.resolve(result).then(\n (response) => observeResponse(observations, request, response),\n (error: unknown) => {\n finalizeFailedRequestObservation(observations, request, error);\n throw error;\n },\n );\n }\n return observeResponse(observations, request, result as Response);\n }) as unknown as ElysiaWrap,\n );\n\n plugin.onStart((app) => {\n startedApp = app;\n recordStartedApp?.(app);\n // Elysia's dynamic handler for aot: false never applies plugin wrap() functions.\n if (app.config.aot === false) {\n logWarning(\n \"Apitally does not observe requests on an Elysia app with aot: false. To resolve this, remove the aot: false option.\",\n );\n }\n });\n plugin.onTransform({ as: \"global\" }, ({ request, route }) => {\n try {\n const observation = observations.get(request);\n if (observation && typeof route === \"string\" && route.length > 0) {\n observation.route = route;\n }\n } catch (error) {\n logDebug(`Error recording the matched Elysia route: ${String(error)}`);\n }\n });\n plugin.onError({ as: \"global\" }, ({ request, route, error }) => {\n try {\n const observation = observations.get(request);\n if (!observation) {\n return;\n }\n if (typeof route === \"string\" && route.length > 0) {\n observation.route = route;\n }\n observation.error = error;\n const { code, all, type } = error as { code?: unknown; all?: unknown; type?: unknown };\n if (code === \"VALIDATION\") {\n observation.requestRecord.validationErrors = formatIssues(all, normalizeSource(type));\n }\n } catch (hookError) {\n logDebug(`Error recording an Elysia error: ${String(hookError)}`);\n }\n });\n plugin.onStop(() => {\n flushTelemetry().catch((error: unknown) => {\n logWarning(`Error flushing Apitally telemetry: ${String(error)}`);\n });\n });\n\n return plugin;\n}\n\nfunction observeResponse(\n observations: WeakMap<Request, ElysiaRequestObservation>,\n request: Request,\n response: Response,\n): Response {\n const observation = observations.get(request);\n if (!observation) {\n return response;\n }\n observations.delete(request);\n const statusCode = response.status;\n // A non-404 response without a recorded route matched a route registered\n // before the plugin was applied; the plugin cannot detect this at setup.\n if (observation.route === undefined && statusCode !== 404) {\n logWarning(\n \"A request matched a route registered before the Apitally plugin was applied, so it is exported without a route template and is not counted in the request metrics. To resolve this, apply the Apitally plugin immediately after creating the app, before registering routes.\",\n );\n }\n let responseHeaders = response.headers;\n try {\n responseHeaders = new Headers(response.headers);\n const shouldAddContentType = shouldAddBunErrorContentType(observation, responseHeaders);\n if (shouldAddContentType) {\n responseHeaders.set(\"content-type\", \"text/plain;charset=utf-8\");\n }\n if (observation.error !== undefined && statusCode >= 500) {\n context.with(observation.requestContext, () => captureException(observation.error));\n }\n const captured = captureWebResponse(\n response,\n getConfig().captureResponseBody && observation.requestRecord.dropReason === undefined,\n );\n captured.completion\n .then(async (completion) => {\n await observation.requestBodyCompletion;\n finalizeRequestObservation({\n observation,\n completedAtMillis: completion.completedAtMillis,\n statusCode,\n route: observation.route,\n requestHeaders: observation.requestHeaders,\n responseHeaders,\n capturedRequestBody: observation.requestBodyCapture,\n capturedResponseBody: completion,\n });\n })\n .catch((error: unknown) => {\n logWarning(`Error in the Apitally Elysia middleware: ${String(error)}`);\n });\n if (!shouldAddContentType) {\n return captured.response;\n }\n // Bun adds this header after .wrap(); retain it when replacing the body stream.\n return new Response(captured.response.body, {\n status: captured.response.status,\n statusText: captured.response.statusText,\n headers: responseHeaders,\n });\n } catch (error) {\n const completedAtMillis = performance.now();\n logWarning(`Error in the Apitally Elysia middleware: ${String(error)}`);\n observation.requestBodyCompletion\n .then(() =>\n finalizeRequestObservation({\n observation,\n completedAtMillis,\n statusCode,\n route: observation.route,\n requestHeaders: observation.requestHeaders,\n responseHeaders,\n capturedRequestBody: observation.requestBodyCapture,\n }),\n )\n .catch((completionError: unknown) => {\n logWarning(`Error in the Apitally Elysia middleware: ${String(completionError)}`);\n });\n return response;\n }\n}\n\nfunction shouldAddBunErrorContentType(\n observation: ElysiaRequestObservation,\n responseHeaders: Headers,\n): boolean {\n if (\n typeof (process.versions as { bun?: unknown }).bun !== \"string\" ||\n responseHeaders.has(\"content-type\")\n ) {\n return false;\n }\n const errorCode = (observation.error as { code?: unknown } | undefined)?.code;\n return errorCode === \"NOT_FOUND\" || errorCode === \"VALIDATION\";\n}\n\nfunction finalizeFailedRequestObservation(\n observations: WeakMap<Request, ElysiaRequestObservation>,\n request: Request,\n error: unknown,\n): void {\n const observation = observations.get(request);\n if (!observation) {\n return;\n }\n observations.delete(request);\n try {\n finalizeRequestObservationWithError({\n requestRecord: observation.requestRecord,\n spanHandle: observation.spanHandle,\n error,\n durationSeconds: (performance.now() - observation.startTimeMillis) / 1000,\n });\n } catch (finalizeError) {\n logWarning(`Error in the Apitally Elysia middleware: ${String(finalizeError)}`);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"middleware.cjs","names":["resolvePackageVersion","resolveStartupPaths","isActivated","isWebSocketUpgrade","startWebRequestObservation","captureWebRequestBody","context","formatIssues","normalizeSource","captureException","captureWebResponse","getConfig","finalizeRequestObservation"],"sources":["../../src/elysia/middleware.ts"],"sourcesContent":["import { type Context, context } from \"@opentelemetry/api\";\nimport type { AnyElysia } from \"elysia\";\nimport {\n activate,\n configure,\n flushTelemetry,\n isActivated,\n registerStartupEventInfo,\n} from \"../activation.js\";\nimport type { ApitallyOptions } from \"../config.js\";\nimport { getConfig } from \"../config.js\";\nimport { captureException } from \"../exceptions.js\";\nimport { logDebug, logWarning } from \"../logger.js\";\nimport { resolvePackageVersion } from \"../packageVersion.js\";\nimport {\n finalizeRequestObservation,\n finalizeRequestObservationWithError,\n} from \"../requestObservation.js\";\nimport {\n captureWebRequestBody,\n captureWebResponse,\n isWebSocketUpgrade,\n startWebRequestObservation,\n type WebRequestObservation,\n} from \"../requestObservationWeb.js\";\nimport { formatIssues, normalizeSource } from \"../validationErrors.js\";\nimport { resolveStartupPaths } from \"./routes.js\";\n\nconst INSTALL_MARKER = Symbol.for(\"apitally.elysiaInstall\");\nconst PLUGIN_NAME = \"apitally\";\nconst TRACER_NAME = \"apitally/elysia\";\n\ninterface ElysiaConstructor {\n new (config?: { name?: string }): AnyElysia;\n}\n\ntype ElysiaDispatcher = (this: unknown, ...args: unknown[]) => Response | Promise<Response>;\ntype ElysiaWrap = Parameters<AnyElysia[\"wrap\"]>[0];\n\ninterface ElysiaRequestObservation extends WebRequestObservation {\n requestContext: Context;\n requestBodyCompletion: Promise<void>;\n route?: string;\n error?: unknown;\n}\n\nexport function createElysiaPlugin(\n ElysiaClass: ElysiaConstructor,\n options?: ApitallyOptions,\n): AnyElysia {\n configure(options);\n const plugin = buildElysiaPlugin(ElysiaClass);\n registerStartupEventInfo({\n framework: \"elysia\",\n frameworkVersion: resolvePackageVersion(\"elysia\"),\n resolvePaths: () => resolveStartupPaths(plugin),\n });\n return plugin;\n}\n\nexport function installElysiaIntegration(app: AnyElysia, options?: ApitallyOptions): void {\n configure(options);\n const markedApp = app as unknown as Record<symbol, boolean | undefined>;\n if (markedApp[INSTALL_MARKER] === true) {\n return;\n }\n if (app.routes.length > 0) {\n logWarning(\n \"useApitally() was called after routes were registered on the Elysia app, so Apitally was not installed. To resolve this, call useApitally() immediately after creating the app, before registering routes or plugins that contain routes.\",\n );\n return;\n }\n markedApp[INSTALL_MARKER] = true;\n registerStartupEventInfo({\n framework: \"elysia\",\n frameworkVersion: resolvePackageVersion(\"elysia\"),\n resolvePaths: () => resolveStartupPaths(app),\n });\n const ElysiaClass = app.constructor as ElysiaConstructor;\n app.use(buildElysiaPlugin(ElysiaClass));\n}\n\nfunction buildElysiaPlugin(ElysiaClass: ElysiaConstructor): AnyElysia {\n const observations = new WeakMap<Request, ElysiaRequestObservation>();\n const plugin = new ElysiaClass({ name: PLUGIN_NAME });\n let startedApp: AnyElysia | undefined;\n\n plugin.wrap(\n ((dispatcher: ElysiaDispatcher, request: Request) =>\n function (this: unknown, ...args: unknown[]): Response | Promise<Response> {\n let observation: ElysiaRequestObservation;\n try {\n activate();\n if (!isActivated() || isWebSocketUpgrade(request)) {\n return dispatcher.apply(this, args);\n }\n const started = startWebRequestObservation({\n request,\n tracerName: TRACER_NAME,\n // @elysiajs/node has no requestIP(); its srvx request carries the client address as ip.\n clientAddress:\n startedApp?.server?.requestIP(request)?.address ?? (request as { ip?: string }).ip,\n });\n observation = {\n ...started.observation,\n requestContext: started.requestContext,\n requestBodyCompletion: captureWebRequestBody(\n request,\n started.observation.requestBodyCapture,\n ),\n };\n observations.set(request, observation);\n } catch (error) {\n logWarning(`Error in the Apitally Elysia middleware: ${String(error)}`);\n return dispatcher.apply(this, args);\n }\n\n let result: Response | Promise<Response>;\n try {\n result = context.with(observation.requestContext, () => dispatcher.apply(this, args));\n } catch (error) {\n finalizeFailedRequestObservation(observations, request, error);\n throw error;\n }\n if (typeof (result as PromiseLike<Response>)?.then === \"function\") {\n return Promise.resolve(result).then(\n (response) => observeResponse(observations, request, response),\n (error: unknown) => {\n finalizeFailedRequestObservation(observations, request, error);\n throw error;\n },\n );\n }\n return observeResponse(observations, request, result as Response);\n }) as unknown as ElysiaWrap,\n );\n\n plugin.onStart((app) => {\n startedApp = app;\n // Elysia's dynamic handler for aot: false never applies plugin wrap() functions.\n // @elysiajs/node passes its adapter instead of the app, and the adapter has no config.\n if (app.config?.aot === false) {\n logWarning(\n \"Apitally does not observe requests on an Elysia app with aot: false. To resolve this, remove the aot: false option.\",\n );\n }\n // Elysia runs onStart hooks once listen() has started the server.\n activate();\n });\n plugin.onTransform({ as: \"global\" }, ({ request, route }) => {\n try {\n const observation = observations.get(request);\n if (observation && typeof route === \"string\" && route.length > 0) {\n observation.route = route;\n }\n } catch (error) {\n logDebug(`Error recording the matched Elysia route: ${String(error)}`);\n }\n });\n plugin.onError({ as: \"global\" }, ({ request, route, error }) => {\n try {\n const observation = observations.get(request);\n if (!observation) {\n return;\n }\n if (typeof route === \"string\" && route.length > 0) {\n observation.route = route;\n }\n observation.error = error;\n const { code, all, type } = error as { code?: unknown; all?: unknown; type?: unknown };\n if (code === \"VALIDATION\") {\n observation.requestRecord.validationErrors = formatIssues(all, normalizeSource(type));\n }\n } catch (hookError) {\n logDebug(`Error recording an Elysia error: ${String(hookError)}`);\n }\n });\n plugin.onStop(() => {\n flushTelemetry().catch((error: unknown) => {\n logWarning(`Error flushing Apitally telemetry: ${String(error)}`);\n });\n });\n\n return plugin;\n}\n\nfunction observeResponse(\n observations: WeakMap<Request, ElysiaRequestObservation>,\n request: Request,\n response: Response,\n): Response {\n const observation = observations.get(request);\n if (!observation) {\n return response;\n }\n observations.delete(request);\n const statusCode = response.status;\n // A non-404 response without a recorded route matched a route registered\n // before the plugin was applied; the plugin cannot detect this at setup.\n if (observation.route === undefined && statusCode !== 404) {\n logWarning(\n \"A request matched a route registered before the Apitally plugin was applied, so it is exported without a route template and is not counted in the request metrics. To resolve this, apply the Apitally plugin immediately after creating the app, before registering routes.\",\n );\n }\n let responseHeaders = response.headers;\n try {\n responseHeaders = new Headers(response.headers);\n const shouldAddContentType = shouldAddBunErrorContentType(observation, responseHeaders);\n if (shouldAddContentType) {\n responseHeaders.set(\"content-type\", \"text/plain;charset=utf-8\");\n }\n if (observation.error !== undefined && statusCode >= 500) {\n context.with(observation.requestContext, () => captureException(observation.error));\n }\n const captured = captureWebResponse(\n response,\n getConfig().captureResponseBody && observation.requestRecord.dropReason === undefined,\n );\n captured.completion\n .then(async (completion) => {\n await observation.requestBodyCompletion;\n finalizeRequestObservation({\n observation,\n completedAtMillis: completion.completedAtMillis,\n statusCode,\n route: observation.route,\n requestHeaders: observation.requestHeaders,\n responseHeaders,\n capturedRequestBody: observation.requestBodyCapture,\n capturedResponseBody: completion,\n });\n })\n .catch((error: unknown) => {\n logWarning(`Error in the Apitally Elysia middleware: ${String(error)}`);\n });\n if (!shouldAddContentType) {\n return captured.response;\n }\n // Bun adds this header after .wrap(); retain it when replacing the body stream.\n return new Response(captured.response.body, {\n status: captured.response.status,\n statusText: captured.response.statusText,\n headers: responseHeaders,\n });\n } catch (error) {\n const completedAtMillis = performance.now();\n logWarning(`Error in the Apitally Elysia middleware: ${String(error)}`);\n observation.requestBodyCompletion\n .then(() =>\n finalizeRequestObservation({\n observation,\n completedAtMillis,\n statusCode,\n route: observation.route,\n requestHeaders: observation.requestHeaders,\n responseHeaders,\n capturedRequestBody: observation.requestBodyCapture,\n }),\n )\n .catch((completionError: unknown) => {\n logWarning(`Error in the Apitally Elysia middleware: ${String(completionError)}`);\n });\n return response;\n }\n}\n\nfunction shouldAddBunErrorContentType(\n observation: ElysiaRequestObservation,\n responseHeaders: Headers,\n): boolean {\n if (\n typeof (process.versions as { bun?: unknown }).bun !== \"string\" ||\n responseHeaders.has(\"content-type\")\n ) {\n return false;\n }\n const errorCode = (observation.error as { code?: unknown } | undefined)?.code;\n return errorCode === \"NOT_FOUND\" || errorCode === \"VALIDATION\";\n}\n\nfunction finalizeFailedRequestObservation(\n observations: WeakMap<Request, ElysiaRequestObservation>,\n request: Request,\n error: unknown,\n): void {\n const observation = observations.get(request);\n if (!observation) {\n return;\n }\n observations.delete(request);\n try {\n finalizeRequestObservationWithError({\n requestRecord: observation.requestRecord,\n spanHandle: observation.spanHandle,\n error,\n durationSeconds: (performance.now() - observation.startTimeMillis) / 1000,\n });\n } catch (finalizeError) {\n logWarning(`Error in the Apitally Elysia middleware: ${String(finalizeError)}`);\n }\n}\n"],"mappings":"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"}