autotel
Version:
Write Once, Observe Anywhere
1 lines • 13.2 kB
Source Map (JSON)
{"version":3,"file":"instrumentation.cjs","names":["processDetector","hostDetector","ATTR_SERVICE_NAME","ATTR_SERVICE_VERSION","requireModule","TailSamplingSpanProcessor","BatchSpanProcessor","OTLPTraceExporter","NodeSDK","PeriodicExportingMetricReader","OTLPMetricExporter","BatchLogRecordProcessor","OTLPLogExporter"],"sources":["../src/instrumentation.ts"],"sourcesContent":["import { NodeSDK } from '@opentelemetry/sdk-node';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';\nimport { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';\nimport { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';\nimport { TailSamplingSpanProcessor } from './tail-sampling-processor';\nimport { getLogger } from './init';\nimport {\n resourceFromAttributes,\n detectResources,\n processDetector,\n hostDetector,\n type Resource,\n type ResourceDetector,\n} from '@opentelemetry/resources';\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n} from '@opentelemetry/semantic-conventions/incubating';\nimport { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';\nimport { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';\nimport { requireModule } from './node-require';\n\n/**\n * Parse OTLP headers string into object format\n * @param headersString - Headers as \"key1=value1,key2=value2\" or \"Authorization=Basic ...\"\n * @returns Headers object for OTLP exporters\n */\nfunction parseOtlpHeaders(headersString?: string): Record<string, string> {\n if (!headersString) return {};\n\n const headers: Record<string, string> = {};\n const pairs = headersString.split(',');\n\n for (const pair of pairs) {\n const [key, ...valueParts] = pair.split('=');\n if (key && valueParts.length > 0) {\n headers[key.trim()] = valueParts.join('=').trim();\n }\n }\n\n return headers;\n}\n\n/**\n * Parse resource attributes string into object format\n * @param attributesString - Attributes as \"key1=value1,key2=value2\"\n * @returns Resource attributes object\n */\nfunction parseResourceAttributes(\n attributesString?: string,\n): Record<string, string> {\n if (!attributesString) return {};\n\n const attributes: Record<string, string> = {};\n const pairs = attributesString.split(',');\n\n for (const pair of pairs) {\n const [key, ...valueParts] = pair.split('=');\n if (key && valueParts.length > 0) {\n attributes[key.trim()] = valueParts.join('=').trim();\n }\n }\n\n return attributes;\n}\n\nexport interface InstrumentationConfig {\n serviceName: string;\n serviceVersion?: string;\n deploymentEnvironment?: string;\n otlpEndpoint?: string;\n /** Headers for authentication (e.g., Grafana Cloud, Honeycomb) */\n headers?: string;\n /** Resource attributes as comma-separated key=value pairs */\n resourceAttributes?: string;\n /** Enable async resource detection for process/host info (default: false) */\n detectResources?: boolean;\n /**\n * Use selective instrumentation instead of full auto-instrumentation\n * **Default: true** (performance-first)\n *\n * When true, auto-instrumentation is disabled. You can manually add\n * specific instrumentations via the `instrumentations` field.\n * This reduces overhead from ~81% to near-zero based on Platformatic benchmarks.\n *\n * Set to false to enable full auto-instrumentation (not recommended for production).\n *\n * @see https://blogger.platformatic.dev/the-hidden-cost-of-context\n */\n selectiveInstrumentation?: boolean;\n\n /**\n * Custom instrumentations to use (only when selectiveInstrumentation is true)\n * @example\n * ```typescript\n * import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'\n *\n * initInstrumentation({\n * serviceName: 'api',\n * selectiveInstrumentation: true,\n * instrumentations: [new HttpInstrumentation()]\n * })\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n instrumentations?: any[];\n}\n\n/**\n * Initialize OpenTelemetry instrumentation with OTLP exporters\n *\n * This sets up:\n * - Traces (OTLP HTTP)\n * - Metrics (OTLP HTTP)\n * - Logs (OTLP HTTP)\n * - Auto-instrumentation for common Node.js libraries\n *\n * @example\n * // Call this at the very start of your application\n * import { initInstrumentation } from '@your-org/otel-decorators'\n *\n * initInstrumentation({\n * serviceName: 'my-service' }\n * serviceVersion: '1.0.0',\n * deploymentEnvironment: 'production',\n * otlpEndpoint: 'http://localhost:4318'\n * })\n *\n * // Or with async resource detection (top-level await required)\n * await initInstrumentation({\n * serviceName: 'my-service' }\n * detectResources: true\n * })\n */\n// Enables graceful shutdown and prevents SDK leaks on hot-reload\nlet currentSDK: NodeSDK | null = null;\nlet shutdownHandlerRegistered = false;\n\n/**\n * Shutdown the OpenTelemetry SDK gracefully\n * Call this before process exit or during hot-reloads\n */\nexport async function shutdownInstrumentation(sdk?: NodeSDK): Promise<void> {\n const sdkToShutdown = sdk || currentSDK;\n if (!sdkToShutdown) {\n getLogger().warn({}, 'No SDK to shutdown');\n return;\n }\n\n try {\n await sdkToShutdown.shutdown();\n getLogger().info({}, 'OpenTelemetry terminated successfully');\n if (sdkToShutdown === currentSDK) {\n currentSDK = null;\n }\n } catch (error) {\n getLogger().error(\n {\n err: error instanceof Error ? error : undefined,\n },\n 'Error terminating OpenTelemetry',\n );\n throw error;\n }\n}\n\nexport async function initInstrumentation(\n config: InstrumentationConfig,\n): Promise<NodeSDK> {\n // Prevents resource leaks on hot-reload or multiple init calls\n if (currentSDK) {\n getLogger().info(\n {},\n 'Shutting down existing OpenTelemetry SDK before reinitializing...',\n );\n await shutdownInstrumentation(currentSDK);\n }\n\n // Parse headers and resource attributes\n const otlpHeaders = parseOtlpHeaders(config.headers);\n const customResourceAttributes = parseResourceAttributes(\n config.resourceAttributes,\n );\n\n let resource: Resource;\n\n // Dynamically load optional resource detectors\n const detectors: ResourceDetector[] = [processDetector, hostDetector];\n try {\n const awsDetectors = await import('@opentelemetry/resource-detector-aws');\n detectors.push(\n awsDetectors.awsEc2Detector,\n awsDetectors.awsEcsDetector,\n awsDetectors.awsEksDetector,\n );\n } catch {\n // ignore\n }\n try {\n const gcpDetectors = await import('@opentelemetry/resource-detector-gcp');\n detectors.push(gcpDetectors.gcpDetector);\n } catch {\n // ignore\n }\n try {\n const containerDetectors =\n await import('@opentelemetry/resource-detector-container');\n detectors.push(containerDetectors.containerDetector);\n } catch {\n // ignore\n }\n\n if (config.detectResources) {\n const detectedResource = await detectResources({\n detectors,\n });\n\n resource = detectedResource.merge(\n resourceFromAttributes({\n [ATTR_SERVICE_NAME]: config.serviceName,\n [ATTR_SERVICE_VERSION]: config.serviceVersion || '1.0.0',\n 'deployment.environment': config.deploymentEnvironment || 'development',\n ...customResourceAttributes, // Merge custom resource attributes\n }),\n );\n } else {\n resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: config.serviceName,\n [ATTR_SERVICE_VERSION]: config.serviceVersion || '1.0.0',\n 'deployment.environment': config.deploymentEnvironment || 'development',\n ...customResourceAttributes, // Merge custom resource attributes\n });\n }\n\n // Default to selective (near-zero overhead) vs full auto (~81% overhead)\n // Lazy-load to avoid importing ~40+ packages at module evaluation time\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let instrumentations: any[] = config.instrumentations || [];\n if (config.selectiveInstrumentation === false) {\n const mod = requireModule<{\n getNodeAutoInstrumentations: () => unknown[];\n }>('@opentelemetry/auto-instrumentations-node');\n instrumentations = [mod.getNodeAutoInstrumentations()];\n }\n\n const traceExporter = new OTLPTraceExporter({\n url: `${config.otlpEndpoint || 'http://localhost:4318'}/v1/traces`,\n headers: otlpHeaders,\n });\n\n // Enables tail sampling via autotel.sampling.tail.keep attribute\n const spanProcessor = new TailSamplingSpanProcessor(\n new BatchSpanProcessor(traceExporter),\n );\n\n const sdk = new NodeSDK({\n resource,\n spanProcessor, // Use our wrapped processor instead of traceExporter directly\n metricReader: new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({\n url: `${config.otlpEndpoint || 'http://localhost:4318'}/v1/metrics`,\n headers: otlpHeaders,\n }),\n }),\n logRecordProcessors: [\n new BatchLogRecordProcessor(\n new OTLPLogExporter({\n url: `${config.otlpEndpoint || 'http://localhost:4318'}/v1/logs`,\n headers: otlpHeaders,\n }),\n ),\n ],\n instrumentations,\n });\n\n try {\n await sdk.start();\n getLogger().info({}, 'OpenTelemetry instrumentation started successfully');\n } catch (error) {\n getLogger().error(\n {\n err: error instanceof Error ? error : undefined,\n },\n 'Failed to start OpenTelemetry SDK',\n );\n throw error;\n }\n\n // Track current SDK for shutdown handler\n currentSDK = sdk;\n\n if (!shutdownHandlerRegistered) {\n shutdownHandlerRegistered = true;\n\n const shutdownHandler = () => {\n shutdownInstrumentation()\n .then(() => {\n // eslint-disable-next-line unicorn/no-process-exit\n process.exit(0);\n })\n .catch((error) => {\n getLogger().error(\n {\n err: error instanceof Error ? error : undefined,\n },\n 'Shutdown error',\n );\n // eslint-disable-next-line unicorn/no-process-exit\n process.exit(1);\n });\n };\n\n process.on('SIGTERM', shutdownHandler);\n process.on('SIGINT', shutdownHandler);\n }\n\n return sdk;\n}\n"],"mappings":"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"}