apitally 1.0.0-beta.3 → 1.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -323,29 +323,6 @@ const sdk = new NodeSDK({
323
323
 
324
324
  Your tracer provider's sampling settings also affect Apitally. Requests excluded by the sampler will not have request logs or traces in Apitally. Metrics still include all requests, regardless of sampling.
325
325
 
326
- ### Sentry
327
-
328
- Sentry's Node.js SDK configures OpenTelemetry automatically. To use Apitally alongside Sentry, add `ApitallySpanProcessor` to Sentry's configuration and call `Sentry.init()` before `useApitally()`:
329
-
330
- ```javascript
331
- import * as Sentry from "@sentry/node";
332
- import { ApitallySpanProcessor } from "apitally";
333
-
334
- Sentry.init({
335
- dsn: "your-sentry-dsn",
336
- // Enable tracing
337
- tracesSampleRate: 1.0,
338
- // Add Apitally's span processor
339
- openTelemetrySpanProcessors: [new ApitallySpanProcessor()],
340
- // Optional: send only errors to Sentry
341
- beforeSendTransaction: () => null,
342
- });
343
- ```
344
-
345
- Apitally uses the spans recorded by Sentry, so tracing must be enabled in Sentry. Requests excluded by Sentry's sampler will also be missing from Apitally's request logs and traces.
346
-
347
- Returning `null` from `beforeSendTransaction` prevents traces from being sent to Sentry without affecting Apitally's request logs and traces.
348
-
349
326
  ### Elysia's OpenTelemetry plugin
350
327
 
351
328
  When using Elysia's `@elysia/opentelemetry` plugin, register Apitally first so it adopts the OpenTelemetry SERVER span:
@@ -6,8 +6,8 @@ const require_exportWorker = require("./exportWorker.cjs");
6
6
  const require_logCapture = require("./logCapture.cjs");
7
7
  const require_logRecordExporter = require("./logRecordExporter.cjs");
8
8
  const require_logRecordProcessor = require("./logRecordProcessor.cjs");
9
- const require_providers = require("./providers.cjs");
10
9
  const require_metrics = require("./metrics.cjs");
10
+ const require_providers = require("./providers.cjs");
11
11
  const require_redaction = require("./redaction.cjs");
12
12
  const require_sentry = require("./sentry.cjs");
13
13
  const require_serverErrors = require("./serverErrors.cjs");
@@ -1 +1 @@
1
- {"version":3,"file":"activation.cjs","names":["setConfig","Spool","ExportWorker","VALIDATION_ERROR_EVENT_NAME","drainValidationErrors","SERVER_ERROR_EVENT_NAME","drainServerErrors","ROOT_CONTEXT","getDistroVersion","getConfig","isApitallySpanProcessorDeclared","resolveEnvAndCreateResource","ApitallySpanExporter","Redaction","BatchSpanProcessor","SpanPipeline","setupTracerProvider","BatchLogRecordProcessor","ApitallyLogRecordExporter","ApitallyLogRecordProcessor","createLoggerProvider","MetricsPipeline","UndiciInstrumentation","isMainThread"],"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 aggregate group, 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.cjs","names":["setConfig","Spool","ExportWorker","VALIDATION_ERROR_EVENT_NAME","drainValidationErrors","SERVER_ERROR_EVENT_NAME","drainServerErrors","ROOT_CONTEXT","getDistroVersion","getConfig","isApitallySpanProcessorDeclared","resolveEnvAndCreateResource","ApitallySpanExporter","Redaction","BatchSpanProcessor","SpanPipeline","setupTracerProvider","BatchLogRecordProcessor","ApitallyLogRecordExporter","ApitallyLogRecordProcessor","createLoggerProvider","MetricsPipeline","UndiciInstrumentation","isMainThread"],"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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"}
@@ -5,8 +5,8 @@ import { ExportWorker } from "./exportWorker.js";
5
5
  import { installConsoleCapture, installNestLoggerCapture, installPinoCapture, installWinstonCapture } from "./logCapture.js";
6
6
  import { ApitallyLogRecordExporter } from "./logRecordExporter.js";
7
7
  import { ApitallyLogRecordProcessor } from "./logRecordProcessor.js";
8
- import { createLoggerProvider, resolveEnvAndCreateResource, setupTracerProvider } from "./providers.js";
9
8
  import { MetricsPipeline } from "./metrics.js";
9
+ import { createLoggerProvider, resolveEnvAndCreateResource, setupTracerProvider } from "./providers.js";
10
10
  import { Redaction } from "./redaction.js";
11
11
  import { installSentryEventIdRecording } from "./sentry.js";
12
12
  import { SERVER_ERROR_EVENT_NAME, drainServerErrors, resetServerErrors } from "./serverErrors.js";
@@ -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 aggregate group, 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 { 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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"}
@@ -5,7 +5,7 @@ let _opentelemetry_api = require("@opentelemetry/api");
5
5
  let node_module = require("node:module");
6
6
  let _opentelemetry_core = require("@opentelemetry/core");
7
7
  //#region src/exportWorker.ts
8
- const MAX_SENDS_PER_CYCLE = 10;
8
+ const MAX_BACKLOG_SENDS_PER_CYCLE = 10;
9
9
  const DEFAULT_EXPORT_INTERVAL_MILLIS = 15e3;
10
10
  const EXPORT_INTERVAL_HEADER = "Apitally-Export-Interval";
11
11
  const INITIAL_EXPORT_DELAY_MILLIS = 2e3;
@@ -108,22 +108,23 @@ var ExportWorker = class {
108
108
  await callback();
109
109
  }
110
110
  if (signal.aborted) return;
111
+ let maxSends = Number.POSITIVE_INFINITY;
111
112
  if (final) await this.spool.closeCurrentFiles();
112
113
  else {
113
- await this.spool.rotateForExport();
114
+ maxSends = MAX_BACKLOG_SENDS_PER_CYCLE + await this.spool.rotateForExport();
114
115
  this.spool.touchFiles();
115
116
  }
116
117
  if (signal.aborted) return;
117
- await this.sendPendingFiles(final, signal);
118
+ await this.sendPendingFiles(final, maxSends, signal);
118
119
  });
119
120
  } catch (error) {
120
121
  require_logger.logDebug(`Error in Apitally export cycle: ${String(error)}`);
121
122
  }
122
123
  }
123
- async sendPendingFiles(final, signal) {
124
+ async sendPendingFiles(final, maxSends, signal) {
124
125
  let sent = 0;
125
126
  for (const file of this.spool.pendingFiles()) {
126
- if (signal.aborted || !final && sent >= MAX_SENDS_PER_CYCLE) return;
127
+ if (signal.aborted || sent >= maxSends) return;
127
128
  if (!final && sent > 0) {
128
129
  const pauseMillis = this.interSendPauseMillis();
129
130
  if (pauseMillis > 0) await new Promise((resolve) => setTimeout(resolve, pauseMillis));
@@ -1 +1 @@
1
- {"version":3,"file":"exportWorker.cjs","names":["getDistroVersion","context","suppressTracing","createRequire","logDebug"],"sources":["../src/exportWorker.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { context } from \"@opentelemetry/api\";\nimport { suppressTracing } from \"@opentelemetry/core\";\nimport { logDebug, logWarning } from \"./logger.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\nimport type { Spool, SpoolFile } from \"./spool.js\";\n\nconst MAX_SENDS_PER_CYCLE = 10;\nconst DEFAULT_EXPORT_INTERVAL_MILLIS = 15_000;\nconst EXPORT_INTERVAL_HEADER = \"Apitally-Export-Interval\";\nconst INITIAL_EXPORT_DELAY_MILLIS = 2_000;\nconst MIN_EXPORT_INTERVAL_SECONDS = 5;\nconst MAX_EXPORT_INTERVAL_SECONDS = 60;\nconst REQUEST_TIMEOUT_MILLIS = 10_000;\nconst RETRYABLE_STATUS_CODES = new Set([408, 429]);\n\nexport type FlushCallback = () => Promise<void> | void;\n\nexport interface ExportWorkerOptions {\n spool: Spool;\n otlpEndpoint: string;\n writeToken: string;\n env: string;\n initialExportDelayMillis?: number;\n requestTimeoutMillis?: number;\n interSendPauseMillis?: () => number;\n}\n\ninterface ProxyTransport {\n fetch: (url: string, init: RequestInit) => Promise<Response>;\n dispatcher: { close(): Promise<void> };\n}\n\n// Send cycles are serialized; a flush request joins the active cycle instead of\n// posting files twice.\nexport class ExportWorker {\n // Invoked at the start of every cycle, before files are rotated and sent.\n readonly flushCallbacks: FlushCallback[] = [];\n intervalMillis = DEFAULT_EXPORT_INTERVAL_MILLIS;\n private readonly spool: Spool;\n private readonly otlpEndpoint: string;\n private readonly headers: Record<string, string>;\n private readonly initialExportDelayMillis: number;\n private readonly requestTimeoutMillis: number;\n private readonly interSendPauseMillis: () => number;\n private readonly useProxy: boolean;\n private proxyTransport?: ProxyTransport;\n private readonly warnedStatuses = new Set<number>();\n private currentCycle?: Promise<void>;\n private currentCycleController?: AbortController;\n private timer?: NodeJS.Timeout;\n private started = false;\n\n constructor(options: ExportWorkerOptions) {\n this.spool = options.spool;\n this.otlpEndpoint = options.otlpEndpoint;\n this.headers = {\n Authorization: `Bearer ${options.writeToken}`,\n \"Apitally-Env\": options.env,\n \"Content-Type\": \"application/x-protobuf\",\n \"Content-Encoding\": \"gzip\",\n \"User-Agent\": `apitally-js/${getDistroVersion()}`,\n };\n this.initialExportDelayMillis = options.initialExportDelayMillis ?? INITIAL_EXPORT_DELAY_MILLIS;\n this.requestTimeoutMillis = options.requestTimeoutMillis ?? REQUEST_TIMEOUT_MILLIS;\n this.interSendPauseMillis = options.interSendPauseMillis ?? (() => 100 + Math.random() * 400);\n const env = process.env;\n this.useProxy = Boolean(env.HTTP_PROXY || env.http_proxy || env.HTTPS_PROXY || env.https_proxy);\n }\n\n start(): void {\n if (this.started) {\n return;\n }\n this.started = true;\n this.scheduleNextExportCycle(this.initialExportDelayMillis);\n }\n\n async stop(): Promise<void> {\n this.started = false;\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n await this.waitForIdle();\n if (this.proxyTransport) {\n await this.proxyTransport.dispatcher.close();\n }\n }\n\n // Runs one send cycle, or joins the cycle already running (no file posts twice).\n runCycle(): Promise<void> {\n return this.currentCycle ?? this.chainCycle(false);\n }\n\n // Closes current files and sends everything pending, optionally within one deadline.\n async finalDrain(timeoutMillis?: number): Promise<void> {\n if (timeoutMillis === undefined) {\n await this.chainCycle(true);\n return;\n }\n\n const deadlineController = new AbortController();\n let deadlineTimer: NodeJS.Timeout | undefined;\n const deadlineReached = new Promise<void>((resolve) => {\n deadlineTimer = setTimeout(() => {\n deadlineController.abort();\n resolve();\n }, timeoutMillis);\n });\n\n this.currentCycleController?.abort();\n const cycle = this.chainCycle(true, deadlineController.signal);\n try {\n await Promise.race([cycle, deadlineReached]);\n } finally {\n if (deadlineTimer) {\n clearTimeout(deadlineTimer);\n }\n }\n }\n\n waitForIdle(): Promise<void> {\n return this.currentCycle ?? Promise.resolve();\n }\n\n private chainCycle(final: boolean, deadlineSignal?: AbortSignal): Promise<void> {\n const previous = this.currentCycle ?? Promise.resolve();\n const controller = new AbortController();\n const signal = deadlineSignal\n ? AbortSignal.any([controller.signal, deadlineSignal])\n : controller.signal;\n const cycle = previous\n .then(() => this.executeCycle(final, signal))\n .finally(() => {\n if (this.currentCycle === cycle) {\n this.currentCycle = undefined;\n this.currentCycleController = undefined;\n }\n });\n this.currentCycle = cycle;\n this.currentCycleController = controller;\n return cycle;\n }\n\n private async executeCycle(final: boolean, signal: AbortSignal): Promise<void> {\n try {\n // Suppress instrumentation so worker flushes and POSTs generate no telemetry.\n await context.with(suppressTracing(context.active()), async () => {\n for (const callback of this.flushCallbacks) {\n if (signal.aborted) {\n return;\n }\n await callback();\n }\n if (signal.aborted) {\n return;\n }\n if (final) {\n await this.spool.closeCurrentFiles();\n } else {\n await this.spool.rotateForExport();\n this.spool.touchFiles();\n }\n if (signal.aborted) {\n return;\n }\n await this.sendPendingFiles(final, signal);\n });\n } catch (error) {\n logDebug(`Error in Apitally export cycle: ${String(error)}`);\n }\n }\n\n // During an outage, stopping on failure limits each cycle to one probe POST.\n private async sendPendingFiles(final: boolean, signal: AbortSignal): Promise<void> {\n let sent = 0;\n for (const file of this.spool.pendingFiles()) {\n if (signal.aborted || (!final && sent >= MAX_SENDS_PER_CYCLE)) {\n return;\n }\n if (!final && sent > 0) {\n const pauseMillis = this.interSendPauseMillis();\n if (pauseMillis > 0) {\n await new Promise<void>((resolve) => setTimeout(resolve, pauseMillis));\n }\n if (signal.aborted) {\n return;\n }\n }\n const expirationDeletion = this.spool.deleteIfExpired(file);\n if (expirationDeletion) {\n await expirationDeletion;\n continue;\n }\n sent += 1;\n if (!(await this.sendFile(file, signal))) {\n return;\n }\n }\n }\n\n // Sends the file's stored bytes verbatim. Returns false on a retryable\n // failure, which ends the cycle and keeps the file queued.\n private async sendFile(file: SpoolFile, signal: AbortSignal): Promise<boolean> {\n file.markAttempt();\n const url = `${this.otlpEndpoint}/v1/${file.signal}`;\n let body: Buffer;\n try {\n body = await file.readStoredBytes();\n } catch {\n logWarning(`Error reading buffered ${file.signal}, dropping the file`);\n await this.spool.deleteFile(file);\n return true;\n }\n if (signal.aborted) {\n return false;\n }\n let response: Response;\n try {\n try {\n response = await this.postFile(url, body, signal);\n } catch (error) {\n if (signal.aborted || isAbortError(error) || isTimeoutError(error)) {\n throw error;\n }\n // The server may close an idle keep-alive connection mid-request; retry once.\n response = await this.postFile(url, body, signal);\n }\n } catch (error) {\n if (!signal.aborted) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed (${String(error)}), will retry`,\n );\n }\n return false;\n }\n await response.arrayBuffer().catch(() => undefined);\n if (signal.aborted) {\n return false;\n }\n this.applyIntervalHeader(response);\n if (response.status >= 200 && response.status < 300) {\n await this.spool.deleteFile(file);\n return true;\n }\n if (RETRYABLE_STATUS_CODES.has(response.status) || response.status >= 500) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed with HTTP ${response.status}, will retry`,\n );\n return false;\n }\n if (!this.warnedStatuses.has(response.status)) {\n this.warnedStatuses.add(response.status);\n logWarning(\n `Apitally rejected buffered ${file.signal} with HTTP ${response.status}; they were dropped`,\n );\n }\n await this.spool.deleteFile(file);\n return true;\n }\n\n private postFile(url: string, body: Buffer, signal: AbortSignal): Promise<Response> {\n const init: RequestInit = {\n method: \"POST\",\n headers: this.headers,\n body: body as unknown as BodyInit,\n signal: AbortSignal.any([signal, AbortSignal.timeout(this.requestTimeoutMillis)]),\n };\n if (!this.useProxy) {\n return fetch(url, init);\n }\n const transport = this.getProxyTransport();\n return transport.fetch(url, {\n ...init,\n dispatcher: transport.dispatcher,\n } as RequestInit);\n }\n\n private getProxyTransport(): ProxyTransport {\n if (!this.proxyTransport) {\n // undici's fetch is required because Node's bundled fetch does not accept\n // an undici dispatcher on every supported Node version.\n const undici = createRequire(import.meta.url)(\"undici\") as {\n fetch: ProxyTransport[\"fetch\"];\n EnvHttpProxyAgent: new () => ProxyTransport[\"dispatcher\"];\n };\n this.proxyTransport = {\n fetch: undici.fetch,\n dispatcher: new undici.EnvHttpProxyAgent(),\n };\n }\n return this.proxyTransport;\n }\n\n private applyIntervalHeader(response: Response): void {\n const value = response.headers.get(EXPORT_INTERVAL_HEADER);\n if (!value) {\n return;\n }\n const seconds = Number.parseInt(value, 10);\n if (Number.isNaN(seconds)) {\n return;\n }\n this.intervalMillis =\n Math.min(Math.max(seconds, MIN_EXPORT_INTERVAL_SECONDS), MAX_EXPORT_INTERVAL_SECONDS) * 1000;\n }\n\n private scheduleNextExportCycle(delayMillis: number): void {\n this.timer = setTimeout(() => {\n void this.runCycle()\n .catch((error) => logDebug(`Error in Apitally export cycle: ${String(error)}`))\n .finally(() => {\n if (this.started) {\n // Jitter desynchronizes deployments whose processes started together.\n this.scheduleNextExportCycle(this.intervalMillis * (0.9 + Math.random() * 0.2));\n }\n });\n }, delayMillis);\n this.timer.unref();\n }\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nfunction isTimeoutError(error: unknown): boolean {\n return error instanceof Error && error.name === \"TimeoutError\";\n}\n"],"mappings":";;;;;;;AAOA,MAAM,sBAAsB;AAC5B,MAAM,iCAAiC;AACvC,MAAM,yBAAyB;AAC/B,MAAM,8BAA8B;AACpC,MAAM,8BAA8B;AACpC,MAAM,8BAA8B;AACpC,MAAM,yBAAyB;AAC/B,MAAM,yCAAyB,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC;AAqBjD,IAAa,eAAb,MAA0B;CAExB,iBAA2C,CAAC;CAC5C,iBAAiB;CACjB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,iCAAkC,IAAI,IAAY;CAClD;CACA;CACA;CACA,UAAkB;CAElB,YAAY,SAA8B;EACxC,KAAK,QAAQ,QAAQ;EACrB,KAAK,eAAe,QAAQ;EAC5B,KAAK,UAAU;GACb,eAAe,UAAU,QAAQ;GACjC,gBAAgB,QAAQ;GACxB,gBAAgB;GAChB,oBAAoB;GACpB,cAAc,eAAeA,uBAAAA,iBAAiB;EAChD;EACA,KAAK,2BAA2B,QAAQ,4BAA4B;EACpE,KAAK,uBAAuB,QAAQ,wBAAwB;EAC5D,KAAK,uBAAuB,QAAQ,+BAA+B,MAAM,KAAK,OAAO,IAAI;EACzF,MAAM,MAAM,QAAQ;EACpB,KAAK,WAAW,QAAQ,IAAI,cAAc,IAAI,cAAc,IAAI,eAAe,IAAI,WAAW;CAChG;CAEA,QAAc;EACZ,IAAI,KAAK,SACP;EAEF,KAAK,UAAU;EACf,KAAK,wBAAwB,KAAK,wBAAwB;CAC5D;CAEA,MAAM,OAAsB;EAC1B,KAAK,UAAU;EACf,IAAI,KAAK,OAAO;GACd,aAAa,KAAK,KAAK;GACvB,KAAK,QAAQ,KAAA;EACf;EACA,MAAM,KAAK,YAAY;EACvB,IAAI,KAAK,gBACP,MAAM,KAAK,eAAe,WAAW,MAAM;CAE/C;CAGA,WAA0B;EACxB,OAAO,KAAK,gBAAgB,KAAK,WAAW,KAAK;CACnD;CAGA,MAAM,WAAW,eAAuC;EACtD,IAAI,kBAAkB,KAAA,GAAW;GAC/B,MAAM,KAAK,WAAW,IAAI;GAC1B;EACF;EAEA,MAAM,qBAAqB,IAAI,gBAAgB;EAC/C,IAAI;EACJ,MAAM,kBAAkB,IAAI,SAAe,YAAY;GACrD,gBAAgB,iBAAiB;IAC/B,mBAAmB,MAAM;IACzB,QAAQ;GACV,GAAG,aAAa;EAClB,CAAC;EAED,KAAK,wBAAwB,MAAM;EACnC,MAAM,QAAQ,KAAK,WAAW,MAAM,mBAAmB,MAAM;EAC7D,IAAI;GACF,MAAM,QAAQ,KAAK,CAAC,OAAO,eAAe,CAAC;EAC7C,UAAU;GACR,IAAI,eACF,aAAa,aAAa;EAE9B;CACF;CAEA,cAA6B;EAC3B,OAAO,KAAK,gBAAgB,QAAQ,QAAQ;CAC9C;CAEA,WAAmB,OAAgB,gBAA6C;EAC9E,MAAM,WAAW,KAAK,gBAAgB,QAAQ,QAAQ;EACtD,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,SAAS,iBACX,YAAY,IAAI,CAAC,WAAW,QAAQ,cAAc,CAAC,IACnD,WAAW;EACf,MAAM,QAAQ,SACX,WAAW,KAAK,aAAa,OAAO,MAAM,CAAC,CAAC,CAC5C,cAAc;GACb,IAAI,KAAK,iBAAiB,OAAO;IAC/B,KAAK,eAAe,KAAA;IACpB,KAAK,yBAAyB,KAAA;GAChC;EACF,CAAC;EACH,KAAK,eAAe;EACpB,KAAK,yBAAyB;EAC9B,OAAO;CACT;CAEA,MAAc,aAAa,OAAgB,QAAoC;EAC7E,IAAI;GAEF,MAAMC,mBAAAA,QAAQ,MAAA,GAAKC,oBAAAA,gBAAAA,CAAgBD,mBAAAA,QAAQ,OAAO,CAAC,GAAG,YAAY;IAChE,KAAK,MAAM,YAAY,KAAK,gBAAgB;KAC1C,IAAI,OAAO,SACT;KAEF,MAAM,SAAS;IACjB;IACA,IAAI,OAAO,SACT;IAEF,IAAI,OACF,MAAM,KAAK,MAAM,kBAAkB;SAC9B;KACL,MAAM,KAAK,MAAM,gBAAgB;KACjC,KAAK,MAAM,WAAW;IACxB;IACA,IAAI,OAAO,SACT;IAEF,MAAM,KAAK,iBAAiB,OAAO,MAAM;GAC3C,CAAC;EACH,SAAS,OAAO;GACd,eAAA,SAAS,mCAAmC,OAAO,KAAK,GAAG;EAC7D;CACF;CAGA,MAAc,iBAAiB,OAAgB,QAAoC;EACjF,IAAI,OAAO;EACX,KAAK,MAAM,QAAQ,KAAK,MAAM,aAAa,GAAG;GAC5C,IAAI,OAAO,WAAY,CAAC,SAAS,QAAQ,qBACvC;GAEF,IAAI,CAAC,SAAS,OAAO,GAAG;IACtB,MAAM,cAAc,KAAK,qBAAqB;IAC9C,IAAI,cAAc,GAChB,MAAM,IAAI,SAAe,YAAY,WAAW,SAAS,WAAW,CAAC;IAEvE,IAAI,OAAO,SACT;GAEJ;GACA,MAAM,qBAAqB,KAAK,MAAM,gBAAgB,IAAI;GAC1D,IAAI,oBAAoB;IACtB,MAAM;IACN;GACF;GACA,QAAQ;GACR,IAAI,CAAE,MAAM,KAAK,SAAS,MAAM,MAAM,GACpC;EAEJ;CACF;CAIA,MAAc,SAAS,MAAiB,QAAuC;EAC7E,KAAK,YAAY;EACjB,MAAM,MAAM,GAAG,KAAK,aAAa,MAAM,KAAK;EAC5C,IAAI;EACJ,IAAI;GACF,OAAO,MAAM,KAAK,gBAAgB;EACpC,QAAQ;GACN,eAAA,WAAW,0BAA0B,KAAK,OAAO,oBAAoB;GACrE,MAAM,KAAK,MAAM,WAAW,IAAI;GAChC,OAAO;EACT;EACA,IAAI,OAAO,SACT,OAAO;EAET,IAAI;EACJ,IAAI;GACF,IAAI;IACF,WAAW,MAAM,KAAK,SAAS,KAAK,MAAM,MAAM;GAClD,SAAS,OAAO;IACd,IAAI,OAAO,WAAW,aAAa,KAAK,KAAK,eAAe,KAAK,GAC/D,MAAM;IAGR,WAAW,MAAM,KAAK,SAAS,KAAK,MAAM,MAAM;GAClD;EACF,SAAS,OAAO;GACd,IAAI,CAAC,OAAO,SACV,eAAA,SACE,oBAAoB,KAAK,OAAO,uBAAuB,OAAO,KAAK,EAAE,cACvE;GAEF,OAAO;EACT;EACA,MAAM,SAAS,YAAY,CAAC,CAAC,YAAY,KAAA,CAAS;EAClD,IAAI,OAAO,SACT,OAAO;EAET,KAAK,oBAAoB,QAAQ;EACjC,IAAI,SAAS,UAAU,OAAO,SAAS,SAAS,KAAK;GACnD,MAAM,KAAK,MAAM,WAAW,IAAI;GAChC,OAAO;EACT;EACA,IAAI,uBAAuB,IAAI,SAAS,MAAM,KAAK,SAAS,UAAU,KAAK;GACzE,eAAA,SACE,oBAAoB,KAAK,OAAO,gCAAgC,SAAS,OAAO,aAClF;GACA,OAAO;EACT;EACA,IAAI,CAAC,KAAK,eAAe,IAAI,SAAS,MAAM,GAAG;GAC7C,KAAK,eAAe,IAAI,SAAS,MAAM;GACvC,eAAA,WACE,8BAA8B,KAAK,OAAO,aAAa,SAAS,OAAO,oBACzE;EACF;EACA,MAAM,KAAK,MAAM,WAAW,IAAI;EAChC,OAAO;CACT;CAEA,SAAiB,KAAa,MAAc,QAAwC;EAClF,MAAM,OAAoB;GACxB,QAAQ;GACR,SAAS,KAAK;GACR;GACN,QAAQ,YAAY,IAAI,CAAC,QAAQ,YAAY,QAAQ,KAAK,oBAAoB,CAAC,CAAC;EAClF;EACA,IAAI,CAAC,KAAK,UACR,OAAO,MAAM,KAAK,IAAI;EAExB,MAAM,YAAY,KAAK,kBAAkB;EACzC,OAAO,UAAU,MAAM,KAAK;GAC1B,GAAG;GACH,YAAY,UAAU;EACxB,CAAgB;CAClB;CAEA,oBAA4C;EAC1C,IAAI,CAAC,KAAK,gBAAgB;GAGxB,MAAM,UAAA,GAASE,YAAAA,cAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,IAA6B,CAAC,CAAC,QAAQ;GAItD,KAAK,iBAAiB;IACpB,OAAO,OAAO;IACd,YAAY,IAAI,OAAO,kBAAkB;GAC3C;EACF;EACA,OAAO,KAAK;CACd;CAEA,oBAA4B,UAA0B;EACpD,MAAM,QAAQ,SAAS,QAAQ,IAAI,sBAAsB;EACzD,IAAI,CAAC,OACH;EAEF,MAAM,UAAU,OAAO,SAAS,OAAO,EAAE;EACzC,IAAI,OAAO,MAAM,OAAO,GACtB;EAEF,KAAK,iBACH,KAAK,IAAI,KAAK,IAAI,SAAS,2BAA2B,GAAG,2BAA2B,IAAI;CAC5F;CAEA,wBAAgC,aAA2B;EACzD,KAAK,QAAQ,iBAAiB;GAC5B,KAAU,SAAS,CAAC,CACjB,OAAO,UAAUC,eAAAA,SAAS,mCAAmC,OAAO,KAAK,GAAG,CAAC,CAAC,CAC9E,cAAc;IACb,IAAI,KAAK,SAEP,KAAK,wBAAwB,KAAK,kBAAkB,KAAM,KAAK,OAAO,IAAI,GAAI;GAElF,CAAC;EACL,GAAG,WAAW;EACd,KAAK,MAAM,MAAM;CACnB;AACF;AAEA,SAAS,aAAa,OAAyB;CAC7C,OAAO,iBAAiB,SAAS,MAAM,SAAS;AAClD;AAEA,SAAS,eAAe,OAAyB;CAC/C,OAAO,iBAAiB,SAAS,MAAM,SAAS;AAClD"}
1
+ {"version":3,"file":"exportWorker.cjs","names":["getDistroVersion","context","suppressTracing","createRequire","logDebug"],"sources":["../src/exportWorker.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { context } from \"@opentelemetry/api\";\nimport { suppressTracing } from \"@opentelemetry/core\";\nimport { logDebug, logWarning } from \"./logger.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\nimport type { Spool, SpoolFile } from \"./spool.js\";\n\nconst MAX_BACKLOG_SENDS_PER_CYCLE = 10;\nconst DEFAULT_EXPORT_INTERVAL_MILLIS = 15_000;\nconst EXPORT_INTERVAL_HEADER = \"Apitally-Export-Interval\";\nconst INITIAL_EXPORT_DELAY_MILLIS = 2_000;\nconst MIN_EXPORT_INTERVAL_SECONDS = 5;\nconst MAX_EXPORT_INTERVAL_SECONDS = 60;\nconst REQUEST_TIMEOUT_MILLIS = 10_000;\nconst RETRYABLE_STATUS_CODES = new Set([408, 429]);\n\nexport type FlushCallback = () => Promise<void> | void;\n\nexport interface ExportWorkerOptions {\n spool: Spool;\n otlpEndpoint: string;\n writeToken: string;\n env: string;\n initialExportDelayMillis?: number;\n requestTimeoutMillis?: number;\n interSendPauseMillis?: () => number;\n}\n\ninterface ProxyTransport {\n fetch: (url: string, init: RequestInit) => Promise<Response>;\n dispatcher: { close(): Promise<void> };\n}\n\n// Send cycles are serialized; a flush request joins the active cycle instead of\n// posting files twice.\nexport class ExportWorker {\n // Invoked at the start of every cycle, before files are rotated and sent.\n readonly flushCallbacks: FlushCallback[] = [];\n intervalMillis = DEFAULT_EXPORT_INTERVAL_MILLIS;\n private readonly spool: Spool;\n private readonly otlpEndpoint: string;\n private readonly headers: Record<string, string>;\n private readonly initialExportDelayMillis: number;\n private readonly requestTimeoutMillis: number;\n private readonly interSendPauseMillis: () => number;\n private readonly useProxy: boolean;\n private proxyTransport?: ProxyTransport;\n private readonly warnedStatuses = new Set<number>();\n private currentCycle?: Promise<void>;\n private currentCycleController?: AbortController;\n private timer?: NodeJS.Timeout;\n private started = false;\n\n constructor(options: ExportWorkerOptions) {\n this.spool = options.spool;\n this.otlpEndpoint = options.otlpEndpoint;\n this.headers = {\n Authorization: `Bearer ${options.writeToken}`,\n \"Apitally-Env\": options.env,\n \"Content-Type\": \"application/x-protobuf\",\n \"Content-Encoding\": \"gzip\",\n \"User-Agent\": `apitally-js/${getDistroVersion()}`,\n };\n this.initialExportDelayMillis = options.initialExportDelayMillis ?? INITIAL_EXPORT_DELAY_MILLIS;\n this.requestTimeoutMillis = options.requestTimeoutMillis ?? REQUEST_TIMEOUT_MILLIS;\n this.interSendPauseMillis = options.interSendPauseMillis ?? (() => 100 + Math.random() * 400);\n const env = process.env;\n this.useProxy = Boolean(env.HTTP_PROXY || env.http_proxy || env.HTTPS_PROXY || env.https_proxy);\n }\n\n start(): void {\n if (this.started) {\n return;\n }\n this.started = true;\n this.scheduleNextExportCycle(this.initialExportDelayMillis);\n }\n\n async stop(): Promise<void> {\n this.started = false;\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n await this.waitForIdle();\n if (this.proxyTransport) {\n await this.proxyTransport.dispatcher.close();\n }\n }\n\n // Runs one send cycle, or joins the cycle already running (no file posts twice).\n runCycle(): Promise<void> {\n return this.currentCycle ?? this.chainCycle(false);\n }\n\n // Closes current files and sends everything pending, optionally within one deadline.\n async finalDrain(timeoutMillis?: number): Promise<void> {\n if (timeoutMillis === undefined) {\n await this.chainCycle(true);\n return;\n }\n\n const deadlineController = new AbortController();\n let deadlineTimer: NodeJS.Timeout | undefined;\n const deadlineReached = new Promise<void>((resolve) => {\n deadlineTimer = setTimeout(() => {\n deadlineController.abort();\n resolve();\n }, timeoutMillis);\n });\n\n this.currentCycleController?.abort();\n const cycle = this.chainCycle(true, deadlineController.signal);\n try {\n await Promise.race([cycle, deadlineReached]);\n } finally {\n if (deadlineTimer) {\n clearTimeout(deadlineTimer);\n }\n }\n }\n\n waitForIdle(): Promise<void> {\n return this.currentCycle ?? Promise.resolve();\n }\n\n private chainCycle(final: boolean, deadlineSignal?: AbortSignal): Promise<void> {\n const previous = this.currentCycle ?? Promise.resolve();\n const controller = new AbortController();\n const signal = deadlineSignal\n ? AbortSignal.any([controller.signal, deadlineSignal])\n : controller.signal;\n const cycle = previous\n .then(() => this.executeCycle(final, signal))\n .finally(() => {\n if (this.currentCycle === cycle) {\n this.currentCycle = undefined;\n this.currentCycleController = undefined;\n }\n });\n this.currentCycle = cycle;\n this.currentCycleController = controller;\n return cycle;\n }\n\n private async executeCycle(final: boolean, signal: AbortSignal): Promise<void> {\n try {\n // Suppress instrumentation so worker flushes and POSTs generate no telemetry.\n await context.with(suppressTracing(context.active()), async () => {\n for (const callback of this.flushCallbacks) {\n if (signal.aborted) {\n return;\n }\n await callback();\n }\n if (signal.aborted) {\n return;\n }\n let maxSends = Number.POSITIVE_INFINITY;\n if (final) {\n await this.spool.closeCurrentFiles();\n } else {\n maxSends = MAX_BACKLOG_SENDS_PER_CYCLE + (await this.spool.rotateForExport());\n this.spool.touchFiles();\n }\n if (signal.aborted) {\n return;\n }\n await this.sendPendingFiles(final, maxSends, signal);\n });\n } catch (error) {\n logDebug(`Error in Apitally export cycle: ${String(error)}`);\n }\n }\n\n // During an outage, stopping on failure limits each cycle to one probe POST.\n private async sendPendingFiles(\n final: boolean,\n maxSends: number,\n signal: AbortSignal,\n ): Promise<void> {\n let sent = 0;\n for (const file of this.spool.pendingFiles()) {\n if (signal.aborted || sent >= maxSends) {\n return;\n }\n if (!final && sent > 0) {\n const pauseMillis = this.interSendPauseMillis();\n if (pauseMillis > 0) {\n await new Promise<void>((resolve) => setTimeout(resolve, pauseMillis));\n }\n if (signal.aborted) {\n return;\n }\n }\n const expirationDeletion = this.spool.deleteIfExpired(file);\n if (expirationDeletion) {\n await expirationDeletion;\n continue;\n }\n sent += 1;\n if (!(await this.sendFile(file, signal))) {\n return;\n }\n }\n }\n\n // Sends the file's stored bytes verbatim. Returns false on a retryable\n // failure, which ends the cycle and keeps the file queued.\n private async sendFile(file: SpoolFile, signal: AbortSignal): Promise<boolean> {\n file.markAttempt();\n const url = `${this.otlpEndpoint}/v1/${file.signal}`;\n let body: Buffer;\n try {\n body = await file.readStoredBytes();\n } catch {\n logWarning(`Error reading buffered ${file.signal}, dropping the file`);\n await this.spool.deleteFile(file);\n return true;\n }\n if (signal.aborted) {\n return false;\n }\n let response: Response;\n try {\n try {\n response = await this.postFile(url, body, signal);\n } catch (error) {\n if (signal.aborted || isAbortError(error) || isTimeoutError(error)) {\n throw error;\n }\n // The server may close an idle keep-alive connection mid-request; retry once.\n response = await this.postFile(url, body, signal);\n }\n } catch (error) {\n if (!signal.aborted) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed (${String(error)}), will retry`,\n );\n }\n return false;\n }\n await response.arrayBuffer().catch(() => undefined);\n if (signal.aborted) {\n return false;\n }\n this.applyIntervalHeader(response);\n if (response.status >= 200 && response.status < 300) {\n await this.spool.deleteFile(file);\n return true;\n }\n if (RETRYABLE_STATUS_CODES.has(response.status) || response.status >= 500) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed with HTTP ${response.status}, will retry`,\n );\n return false;\n }\n if (!this.warnedStatuses.has(response.status)) {\n this.warnedStatuses.add(response.status);\n logWarning(\n `Apitally rejected buffered ${file.signal} with HTTP ${response.status}; they were dropped`,\n );\n }\n await this.spool.deleteFile(file);\n return true;\n }\n\n private postFile(url: string, body: Buffer, signal: AbortSignal): Promise<Response> {\n const init: RequestInit = {\n method: \"POST\",\n headers: this.headers,\n body: body as unknown as BodyInit,\n signal: AbortSignal.any([signal, AbortSignal.timeout(this.requestTimeoutMillis)]),\n };\n if (!this.useProxy) {\n return fetch(url, init);\n }\n const transport = this.getProxyTransport();\n return transport.fetch(url, {\n ...init,\n dispatcher: transport.dispatcher,\n } as RequestInit);\n }\n\n private getProxyTransport(): ProxyTransport {\n if (!this.proxyTransport) {\n // undici's fetch is required because Node's bundled fetch does not accept\n // an undici dispatcher on every supported Node version.\n const undici = createRequire(import.meta.url)(\"undici\") as {\n fetch: ProxyTransport[\"fetch\"];\n EnvHttpProxyAgent: new () => ProxyTransport[\"dispatcher\"];\n };\n this.proxyTransport = {\n fetch: undici.fetch,\n dispatcher: new undici.EnvHttpProxyAgent(),\n };\n }\n return this.proxyTransport;\n }\n\n private applyIntervalHeader(response: Response): void {\n const value = response.headers.get(EXPORT_INTERVAL_HEADER);\n if (!value) {\n return;\n }\n const seconds = Number.parseInt(value, 10);\n if (Number.isNaN(seconds)) {\n return;\n }\n this.intervalMillis =\n Math.min(Math.max(seconds, MIN_EXPORT_INTERVAL_SECONDS), MAX_EXPORT_INTERVAL_SECONDS) * 1000;\n }\n\n private scheduleNextExportCycle(delayMillis: number): void {\n this.timer = setTimeout(() => {\n void this.runCycle()\n .catch((error) => logDebug(`Error in Apitally export cycle: ${String(error)}`))\n .finally(() => {\n if (this.started) {\n // Jitter desynchronizes deployments whose processes started together.\n this.scheduleNextExportCycle(this.intervalMillis * (0.9 + Math.random() * 0.2));\n }\n });\n }, delayMillis);\n this.timer.unref();\n }\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nfunction isTimeoutError(error: unknown): boolean {\n return error instanceof Error && error.name === \"TimeoutError\";\n}\n"],"mappings":"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"}
@@ -4,7 +4,7 @@ import { createRequire } from "node:module";
4
4
  import { context } from "@opentelemetry/api";
5
5
  import { suppressTracing } from "@opentelemetry/core";
6
6
  //#region src/exportWorker.ts
7
- const MAX_SENDS_PER_CYCLE = 10;
7
+ const MAX_BACKLOG_SENDS_PER_CYCLE = 10;
8
8
  const DEFAULT_EXPORT_INTERVAL_MILLIS = 15e3;
9
9
  const EXPORT_INTERVAL_HEADER = "Apitally-Export-Interval";
10
10
  const INITIAL_EXPORT_DELAY_MILLIS = 2e3;
@@ -107,22 +107,23 @@ var ExportWorker = class {
107
107
  await callback();
108
108
  }
109
109
  if (signal.aborted) return;
110
+ let maxSends = Number.POSITIVE_INFINITY;
110
111
  if (final) await this.spool.closeCurrentFiles();
111
112
  else {
112
- await this.spool.rotateForExport();
113
+ maxSends = MAX_BACKLOG_SENDS_PER_CYCLE + await this.spool.rotateForExport();
113
114
  this.spool.touchFiles();
114
115
  }
115
116
  if (signal.aborted) return;
116
- await this.sendPendingFiles(final, signal);
117
+ await this.sendPendingFiles(final, maxSends, signal);
117
118
  });
118
119
  } catch (error) {
119
120
  logDebug(`Error in Apitally export cycle: ${String(error)}`);
120
121
  }
121
122
  }
122
- async sendPendingFiles(final, signal) {
123
+ async sendPendingFiles(final, maxSends, signal) {
123
124
  let sent = 0;
124
125
  for (const file of this.spool.pendingFiles()) {
125
- if (signal.aborted || !final && sent >= MAX_SENDS_PER_CYCLE) return;
126
+ if (signal.aborted || sent >= maxSends) return;
126
127
  if (!final && sent > 0) {
127
128
  const pauseMillis = this.interSendPauseMillis();
128
129
  if (pauseMillis > 0) await new Promise((resolve) => setTimeout(resolve, pauseMillis));
@@ -1 +1 @@
1
- {"version":3,"file":"exportWorker.js","names":[],"sources":["../src/exportWorker.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { context } from \"@opentelemetry/api\";\nimport { suppressTracing } from \"@opentelemetry/core\";\nimport { logDebug, logWarning } from \"./logger.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\nimport type { Spool, SpoolFile } from \"./spool.js\";\n\nconst MAX_SENDS_PER_CYCLE = 10;\nconst DEFAULT_EXPORT_INTERVAL_MILLIS = 15_000;\nconst EXPORT_INTERVAL_HEADER = \"Apitally-Export-Interval\";\nconst INITIAL_EXPORT_DELAY_MILLIS = 2_000;\nconst MIN_EXPORT_INTERVAL_SECONDS = 5;\nconst MAX_EXPORT_INTERVAL_SECONDS = 60;\nconst REQUEST_TIMEOUT_MILLIS = 10_000;\nconst RETRYABLE_STATUS_CODES = new Set([408, 429]);\n\nexport type FlushCallback = () => Promise<void> | void;\n\nexport interface ExportWorkerOptions {\n spool: Spool;\n otlpEndpoint: string;\n writeToken: string;\n env: string;\n initialExportDelayMillis?: number;\n requestTimeoutMillis?: number;\n interSendPauseMillis?: () => number;\n}\n\ninterface ProxyTransport {\n fetch: (url: string, init: RequestInit) => Promise<Response>;\n dispatcher: { close(): Promise<void> };\n}\n\n// Send cycles are serialized; a flush request joins the active cycle instead of\n// posting files twice.\nexport class ExportWorker {\n // Invoked at the start of every cycle, before files are rotated and sent.\n readonly flushCallbacks: FlushCallback[] = [];\n intervalMillis = DEFAULT_EXPORT_INTERVAL_MILLIS;\n private readonly spool: Spool;\n private readonly otlpEndpoint: string;\n private readonly headers: Record<string, string>;\n private readonly initialExportDelayMillis: number;\n private readonly requestTimeoutMillis: number;\n private readonly interSendPauseMillis: () => number;\n private readonly useProxy: boolean;\n private proxyTransport?: ProxyTransport;\n private readonly warnedStatuses = new Set<number>();\n private currentCycle?: Promise<void>;\n private currentCycleController?: AbortController;\n private timer?: NodeJS.Timeout;\n private started = false;\n\n constructor(options: ExportWorkerOptions) {\n this.spool = options.spool;\n this.otlpEndpoint = options.otlpEndpoint;\n this.headers = {\n Authorization: `Bearer ${options.writeToken}`,\n \"Apitally-Env\": options.env,\n \"Content-Type\": \"application/x-protobuf\",\n \"Content-Encoding\": \"gzip\",\n \"User-Agent\": `apitally-js/${getDistroVersion()}`,\n };\n this.initialExportDelayMillis = options.initialExportDelayMillis ?? INITIAL_EXPORT_DELAY_MILLIS;\n this.requestTimeoutMillis = options.requestTimeoutMillis ?? REQUEST_TIMEOUT_MILLIS;\n this.interSendPauseMillis = options.interSendPauseMillis ?? (() => 100 + Math.random() * 400);\n const env = process.env;\n this.useProxy = Boolean(env.HTTP_PROXY || env.http_proxy || env.HTTPS_PROXY || env.https_proxy);\n }\n\n start(): void {\n if (this.started) {\n return;\n }\n this.started = true;\n this.scheduleNextExportCycle(this.initialExportDelayMillis);\n }\n\n async stop(): Promise<void> {\n this.started = false;\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n await this.waitForIdle();\n if (this.proxyTransport) {\n await this.proxyTransport.dispatcher.close();\n }\n }\n\n // Runs one send cycle, or joins the cycle already running (no file posts twice).\n runCycle(): Promise<void> {\n return this.currentCycle ?? this.chainCycle(false);\n }\n\n // Closes current files and sends everything pending, optionally within one deadline.\n async finalDrain(timeoutMillis?: number): Promise<void> {\n if (timeoutMillis === undefined) {\n await this.chainCycle(true);\n return;\n }\n\n const deadlineController = new AbortController();\n let deadlineTimer: NodeJS.Timeout | undefined;\n const deadlineReached = new Promise<void>((resolve) => {\n deadlineTimer = setTimeout(() => {\n deadlineController.abort();\n resolve();\n }, timeoutMillis);\n });\n\n this.currentCycleController?.abort();\n const cycle = this.chainCycle(true, deadlineController.signal);\n try {\n await Promise.race([cycle, deadlineReached]);\n } finally {\n if (deadlineTimer) {\n clearTimeout(deadlineTimer);\n }\n }\n }\n\n waitForIdle(): Promise<void> {\n return this.currentCycle ?? Promise.resolve();\n }\n\n private chainCycle(final: boolean, deadlineSignal?: AbortSignal): Promise<void> {\n const previous = this.currentCycle ?? Promise.resolve();\n const controller = new AbortController();\n const signal = deadlineSignal\n ? AbortSignal.any([controller.signal, deadlineSignal])\n : controller.signal;\n const cycle = previous\n .then(() => this.executeCycle(final, signal))\n .finally(() => {\n if (this.currentCycle === cycle) {\n this.currentCycle = undefined;\n this.currentCycleController = undefined;\n }\n });\n this.currentCycle = cycle;\n this.currentCycleController = controller;\n return cycle;\n }\n\n private async executeCycle(final: boolean, signal: AbortSignal): Promise<void> {\n try {\n // Suppress instrumentation so worker flushes and POSTs generate no telemetry.\n await context.with(suppressTracing(context.active()), async () => {\n for (const callback of this.flushCallbacks) {\n if (signal.aborted) {\n return;\n }\n await callback();\n }\n if (signal.aborted) {\n return;\n }\n if (final) {\n await this.spool.closeCurrentFiles();\n } else {\n await this.spool.rotateForExport();\n this.spool.touchFiles();\n }\n if (signal.aborted) {\n return;\n }\n await this.sendPendingFiles(final, signal);\n });\n } catch (error) {\n logDebug(`Error in Apitally export cycle: ${String(error)}`);\n }\n }\n\n // During an outage, stopping on failure limits each cycle to one probe POST.\n private async sendPendingFiles(final: boolean, signal: AbortSignal): Promise<void> {\n let sent = 0;\n for (const file of this.spool.pendingFiles()) {\n if (signal.aborted || (!final && sent >= MAX_SENDS_PER_CYCLE)) {\n return;\n }\n if (!final && sent > 0) {\n const pauseMillis = this.interSendPauseMillis();\n if (pauseMillis > 0) {\n await new Promise<void>((resolve) => setTimeout(resolve, pauseMillis));\n }\n if (signal.aborted) {\n return;\n }\n }\n const expirationDeletion = this.spool.deleteIfExpired(file);\n if (expirationDeletion) {\n await expirationDeletion;\n continue;\n }\n sent += 1;\n if (!(await this.sendFile(file, signal))) {\n return;\n }\n }\n }\n\n // Sends the file's stored bytes verbatim. Returns false on a retryable\n // failure, which ends the cycle and keeps the file queued.\n private async sendFile(file: SpoolFile, signal: AbortSignal): Promise<boolean> {\n file.markAttempt();\n const url = `${this.otlpEndpoint}/v1/${file.signal}`;\n let body: Buffer;\n try {\n body = await file.readStoredBytes();\n } catch {\n logWarning(`Error reading buffered ${file.signal}, dropping the file`);\n await this.spool.deleteFile(file);\n return true;\n }\n if (signal.aborted) {\n return false;\n }\n let response: Response;\n try {\n try {\n response = await this.postFile(url, body, signal);\n } catch (error) {\n if (signal.aborted || isAbortError(error) || isTimeoutError(error)) {\n throw error;\n }\n // The server may close an idle keep-alive connection mid-request; retry once.\n response = await this.postFile(url, body, signal);\n }\n } catch (error) {\n if (!signal.aborted) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed (${String(error)}), will retry`,\n );\n }\n return false;\n }\n await response.arrayBuffer().catch(() => undefined);\n if (signal.aborted) {\n return false;\n }\n this.applyIntervalHeader(response);\n if (response.status >= 200 && response.status < 300) {\n await this.spool.deleteFile(file);\n return true;\n }\n if (RETRYABLE_STATUS_CODES.has(response.status) || response.status >= 500) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed with HTTP ${response.status}, will retry`,\n );\n return false;\n }\n if (!this.warnedStatuses.has(response.status)) {\n this.warnedStatuses.add(response.status);\n logWarning(\n `Apitally rejected buffered ${file.signal} with HTTP ${response.status}; they were dropped`,\n );\n }\n await this.spool.deleteFile(file);\n return true;\n }\n\n private postFile(url: string, body: Buffer, signal: AbortSignal): Promise<Response> {\n const init: RequestInit = {\n method: \"POST\",\n headers: this.headers,\n body: body as unknown as BodyInit,\n signal: AbortSignal.any([signal, AbortSignal.timeout(this.requestTimeoutMillis)]),\n };\n if (!this.useProxy) {\n return fetch(url, init);\n }\n const transport = this.getProxyTransport();\n return transport.fetch(url, {\n ...init,\n dispatcher: transport.dispatcher,\n } as RequestInit);\n }\n\n private getProxyTransport(): ProxyTransport {\n if (!this.proxyTransport) {\n // undici's fetch is required because Node's bundled fetch does not accept\n // an undici dispatcher on every supported Node version.\n const undici = createRequire(import.meta.url)(\"undici\") as {\n fetch: ProxyTransport[\"fetch\"];\n EnvHttpProxyAgent: new () => ProxyTransport[\"dispatcher\"];\n };\n this.proxyTransport = {\n fetch: undici.fetch,\n dispatcher: new undici.EnvHttpProxyAgent(),\n };\n }\n return this.proxyTransport;\n }\n\n private applyIntervalHeader(response: Response): void {\n const value = response.headers.get(EXPORT_INTERVAL_HEADER);\n if (!value) {\n return;\n }\n const seconds = Number.parseInt(value, 10);\n if (Number.isNaN(seconds)) {\n return;\n }\n this.intervalMillis =\n Math.min(Math.max(seconds, MIN_EXPORT_INTERVAL_SECONDS), MAX_EXPORT_INTERVAL_SECONDS) * 1000;\n }\n\n private scheduleNextExportCycle(delayMillis: number): void {\n this.timer = setTimeout(() => {\n void this.runCycle()\n .catch((error) => logDebug(`Error in Apitally export cycle: ${String(error)}`))\n .finally(() => {\n if (this.started) {\n // Jitter desynchronizes deployments whose processes started together.\n this.scheduleNextExportCycle(this.intervalMillis * (0.9 + Math.random() * 0.2));\n }\n });\n }, delayMillis);\n this.timer.unref();\n }\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nfunction isTimeoutError(error: unknown): boolean {\n return error instanceof Error && error.name === \"TimeoutError\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"exportWorker.js","names":[],"sources":["../src/exportWorker.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { context } from \"@opentelemetry/api\";\nimport { suppressTracing } from \"@opentelemetry/core\";\nimport { logDebug, logWarning } from \"./logger.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\nimport type { Spool, SpoolFile } from \"./spool.js\";\n\nconst MAX_BACKLOG_SENDS_PER_CYCLE = 10;\nconst DEFAULT_EXPORT_INTERVAL_MILLIS = 15_000;\nconst EXPORT_INTERVAL_HEADER = \"Apitally-Export-Interval\";\nconst INITIAL_EXPORT_DELAY_MILLIS = 2_000;\nconst MIN_EXPORT_INTERVAL_SECONDS = 5;\nconst MAX_EXPORT_INTERVAL_SECONDS = 60;\nconst REQUEST_TIMEOUT_MILLIS = 10_000;\nconst RETRYABLE_STATUS_CODES = new Set([408, 429]);\n\nexport type FlushCallback = () => Promise<void> | void;\n\nexport interface ExportWorkerOptions {\n spool: Spool;\n otlpEndpoint: string;\n writeToken: string;\n env: string;\n initialExportDelayMillis?: number;\n requestTimeoutMillis?: number;\n interSendPauseMillis?: () => number;\n}\n\ninterface ProxyTransport {\n fetch: (url: string, init: RequestInit) => Promise<Response>;\n dispatcher: { close(): Promise<void> };\n}\n\n// Send cycles are serialized; a flush request joins the active cycle instead of\n// posting files twice.\nexport class ExportWorker {\n // Invoked at the start of every cycle, before files are rotated and sent.\n readonly flushCallbacks: FlushCallback[] = [];\n intervalMillis = DEFAULT_EXPORT_INTERVAL_MILLIS;\n private readonly spool: Spool;\n private readonly otlpEndpoint: string;\n private readonly headers: Record<string, string>;\n private readonly initialExportDelayMillis: number;\n private readonly requestTimeoutMillis: number;\n private readonly interSendPauseMillis: () => number;\n private readonly useProxy: boolean;\n private proxyTransport?: ProxyTransport;\n private readonly warnedStatuses = new Set<number>();\n private currentCycle?: Promise<void>;\n private currentCycleController?: AbortController;\n private timer?: NodeJS.Timeout;\n private started = false;\n\n constructor(options: ExportWorkerOptions) {\n this.spool = options.spool;\n this.otlpEndpoint = options.otlpEndpoint;\n this.headers = {\n Authorization: `Bearer ${options.writeToken}`,\n \"Apitally-Env\": options.env,\n \"Content-Type\": \"application/x-protobuf\",\n \"Content-Encoding\": \"gzip\",\n \"User-Agent\": `apitally-js/${getDistroVersion()}`,\n };\n this.initialExportDelayMillis = options.initialExportDelayMillis ?? INITIAL_EXPORT_DELAY_MILLIS;\n this.requestTimeoutMillis = options.requestTimeoutMillis ?? REQUEST_TIMEOUT_MILLIS;\n this.interSendPauseMillis = options.interSendPauseMillis ?? (() => 100 + Math.random() * 400);\n const env = process.env;\n this.useProxy = Boolean(env.HTTP_PROXY || env.http_proxy || env.HTTPS_PROXY || env.https_proxy);\n }\n\n start(): void {\n if (this.started) {\n return;\n }\n this.started = true;\n this.scheduleNextExportCycle(this.initialExportDelayMillis);\n }\n\n async stop(): Promise<void> {\n this.started = false;\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n await this.waitForIdle();\n if (this.proxyTransport) {\n await this.proxyTransport.dispatcher.close();\n }\n }\n\n // Runs one send cycle, or joins the cycle already running (no file posts twice).\n runCycle(): Promise<void> {\n return this.currentCycle ?? this.chainCycle(false);\n }\n\n // Closes current files and sends everything pending, optionally within one deadline.\n async finalDrain(timeoutMillis?: number): Promise<void> {\n if (timeoutMillis === undefined) {\n await this.chainCycle(true);\n return;\n }\n\n const deadlineController = new AbortController();\n let deadlineTimer: NodeJS.Timeout | undefined;\n const deadlineReached = new Promise<void>((resolve) => {\n deadlineTimer = setTimeout(() => {\n deadlineController.abort();\n resolve();\n }, timeoutMillis);\n });\n\n this.currentCycleController?.abort();\n const cycle = this.chainCycle(true, deadlineController.signal);\n try {\n await Promise.race([cycle, deadlineReached]);\n } finally {\n if (deadlineTimer) {\n clearTimeout(deadlineTimer);\n }\n }\n }\n\n waitForIdle(): Promise<void> {\n return this.currentCycle ?? Promise.resolve();\n }\n\n private chainCycle(final: boolean, deadlineSignal?: AbortSignal): Promise<void> {\n const previous = this.currentCycle ?? Promise.resolve();\n const controller = new AbortController();\n const signal = deadlineSignal\n ? AbortSignal.any([controller.signal, deadlineSignal])\n : controller.signal;\n const cycle = previous\n .then(() => this.executeCycle(final, signal))\n .finally(() => {\n if (this.currentCycle === cycle) {\n this.currentCycle = undefined;\n this.currentCycleController = undefined;\n }\n });\n this.currentCycle = cycle;\n this.currentCycleController = controller;\n return cycle;\n }\n\n private async executeCycle(final: boolean, signal: AbortSignal): Promise<void> {\n try {\n // Suppress instrumentation so worker flushes and POSTs generate no telemetry.\n await context.with(suppressTracing(context.active()), async () => {\n for (const callback of this.flushCallbacks) {\n if (signal.aborted) {\n return;\n }\n await callback();\n }\n if (signal.aborted) {\n return;\n }\n let maxSends = Number.POSITIVE_INFINITY;\n if (final) {\n await this.spool.closeCurrentFiles();\n } else {\n maxSends = MAX_BACKLOG_SENDS_PER_CYCLE + (await this.spool.rotateForExport());\n this.spool.touchFiles();\n }\n if (signal.aborted) {\n return;\n }\n await this.sendPendingFiles(final, maxSends, signal);\n });\n } catch (error) {\n logDebug(`Error in Apitally export cycle: ${String(error)}`);\n }\n }\n\n // During an outage, stopping on failure limits each cycle to one probe POST.\n private async sendPendingFiles(\n final: boolean,\n maxSends: number,\n signal: AbortSignal,\n ): Promise<void> {\n let sent = 0;\n for (const file of this.spool.pendingFiles()) {\n if (signal.aborted || sent >= maxSends) {\n return;\n }\n if (!final && sent > 0) {\n const pauseMillis = this.interSendPauseMillis();\n if (pauseMillis > 0) {\n await new Promise<void>((resolve) => setTimeout(resolve, pauseMillis));\n }\n if (signal.aborted) {\n return;\n }\n }\n const expirationDeletion = this.spool.deleteIfExpired(file);\n if (expirationDeletion) {\n await expirationDeletion;\n continue;\n }\n sent += 1;\n if (!(await this.sendFile(file, signal))) {\n return;\n }\n }\n }\n\n // Sends the file's stored bytes verbatim. Returns false on a retryable\n // failure, which ends the cycle and keeps the file queued.\n private async sendFile(file: SpoolFile, signal: AbortSignal): Promise<boolean> {\n file.markAttempt();\n const url = `${this.otlpEndpoint}/v1/${file.signal}`;\n let body: Buffer;\n try {\n body = await file.readStoredBytes();\n } catch {\n logWarning(`Error reading buffered ${file.signal}, dropping the file`);\n await this.spool.deleteFile(file);\n return true;\n }\n if (signal.aborted) {\n return false;\n }\n let response: Response;\n try {\n try {\n response = await this.postFile(url, body, signal);\n } catch (error) {\n if (signal.aborted || isAbortError(error) || isTimeoutError(error)) {\n throw error;\n }\n // The server may close an idle keep-alive connection mid-request; retry once.\n response = await this.postFile(url, body, signal);\n }\n } catch (error) {\n if (!signal.aborted) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed (${String(error)}), will retry`,\n );\n }\n return false;\n }\n await response.arrayBuffer().catch(() => undefined);\n if (signal.aborted) {\n return false;\n }\n this.applyIntervalHeader(response);\n if (response.status >= 200 && response.status < 300) {\n await this.spool.deleteFile(file);\n return true;\n }\n if (RETRYABLE_STATUS_CODES.has(response.status) || response.status >= 500) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed with HTTP ${response.status}, will retry`,\n );\n return false;\n }\n if (!this.warnedStatuses.has(response.status)) {\n this.warnedStatuses.add(response.status);\n logWarning(\n `Apitally rejected buffered ${file.signal} with HTTP ${response.status}; they were dropped`,\n );\n }\n await this.spool.deleteFile(file);\n return true;\n }\n\n private postFile(url: string, body: Buffer, signal: AbortSignal): Promise<Response> {\n const init: RequestInit = {\n method: \"POST\",\n headers: this.headers,\n body: body as unknown as BodyInit,\n signal: AbortSignal.any([signal, AbortSignal.timeout(this.requestTimeoutMillis)]),\n };\n if (!this.useProxy) {\n return fetch(url, init);\n }\n const transport = this.getProxyTransport();\n return transport.fetch(url, {\n ...init,\n dispatcher: transport.dispatcher,\n } as RequestInit);\n }\n\n private getProxyTransport(): ProxyTransport {\n if (!this.proxyTransport) {\n // undici's fetch is required because Node's bundled fetch does not accept\n // an undici dispatcher on every supported Node version.\n const undici = createRequire(import.meta.url)(\"undici\") as {\n fetch: ProxyTransport[\"fetch\"];\n EnvHttpProxyAgent: new () => ProxyTransport[\"dispatcher\"];\n };\n this.proxyTransport = {\n fetch: undici.fetch,\n dispatcher: new undici.EnvHttpProxyAgent(),\n };\n }\n return this.proxyTransport;\n }\n\n private applyIntervalHeader(response: Response): void {\n const value = response.headers.get(EXPORT_INTERVAL_HEADER);\n if (!value) {\n return;\n }\n const seconds = Number.parseInt(value, 10);\n if (Number.isNaN(seconds)) {\n return;\n }\n this.intervalMillis =\n Math.min(Math.max(seconds, MIN_EXPORT_INTERVAL_SECONDS), MAX_EXPORT_INTERVAL_SECONDS) * 1000;\n }\n\n private scheduleNextExportCycle(delayMillis: number): void {\n this.timer = setTimeout(() => {\n void this.runCycle()\n .catch((error) => logDebug(`Error in Apitally export cycle: ${String(error)}`))\n .finally(() => {\n if (this.started) {\n // Jitter desynchronizes deployments whose processes started together.\n this.scheduleNextExportCycle(this.intervalMillis * (0.9 + Math.random() * 0.2));\n }\n });\n }, delayMillis);\n this.timer.unref();\n }\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nfunction isTimeoutError(error: unknown): boolean {\n return error instanceof Error && error.name === \"TimeoutError\";\n}\n"],"mappings":"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"}