| import { DiagLogLevel, diag, trace } from '@opentelemetry/api' |
| import { logs } from '@opentelemetry/api-logs' |
| |
| |
| |
| import { |
| envDetector, |
| hostDetector, |
| osDetector, |
| resourceFromAttributes, |
| } from '@opentelemetry/resources' |
| import { |
| BatchLogRecordProcessor, |
| ConsoleLogRecordExporter, |
| LoggerProvider, |
| } from '@opentelemetry/sdk-logs' |
| import { |
| ConsoleMetricExporter, |
| MeterProvider, |
| PeriodicExportingMetricReader, |
| } from '@opentelemetry/sdk-metrics' |
| import { |
| BasicTracerProvider, |
| BatchSpanProcessor, |
| ConsoleSpanExporter, |
| } from '@opentelemetry/sdk-trace-base' |
| import { |
| ATTR_SERVICE_NAME, |
| ATTR_SERVICE_VERSION, |
| SEMRESATTRS_HOST_ARCH, |
| } from '@opentelemetry/semantic-conventions' |
| import { HttpsProxyAgent } from 'https-proxy-agent' |
| import { |
| getLoggerProvider, |
| getMeterProvider, |
| getTracerProvider, |
| setEventLogger, |
| setLoggerProvider, |
| setMeterProvider, |
| setTracerProvider, |
| } from 'src/bootstrap/state.js' |
| import { |
| getOtelHeadersFromHelper, |
| getSubscriptionType, |
| is1PApiCustomer, |
| isClaudeAISubscriber, |
| } from 'src/utils/auth.js' |
| import { getPlatform, getWslVersion } from 'src/utils/platform.js' |
|
|
| import { getCACertificates } from '../caCerts.js' |
| import { registerCleanup } from '../cleanupRegistry.js' |
| import { getHasFormattedOutput, logForDebugging } from '../debug.js' |
| import { isEnvTruthy } from '../envUtils.js' |
| import { errorMessage } from '../errors.js' |
| import { getMTLSConfig } from '../mtls.js' |
| import { getProxyUrl, shouldBypassProxy } from '../proxy.js' |
| import { getSettings_DEPRECATED } from '../settings/settings.js' |
| import { jsonStringify } from '../slowOperations.js' |
| import { profileCheckpoint } from '../startupProfiler.js' |
| import { isBetaTracingEnabled } from './betaSessionTracing.js' |
| import { BigQueryMetricsExporter } from './bigqueryExporter.js' |
| import { ClaudeCodeDiagLogger } from './logger.js' |
| import { initializePerfettoTracing } from './perfettoTracing.js' |
| import { |
| endInteractionSpan, |
| isEnhancedTelemetryEnabled, |
| } from './sessionTracing.js' |
|
|
| const DEFAULT_METRICS_EXPORT_INTERVAL_MS = 60000 |
| const DEFAULT_LOGS_EXPORT_INTERVAL_MS = 5000 |
| const DEFAULT_TRACES_EXPORT_INTERVAL_MS = 5000 |
|
|
| class TelemetryTimeoutError extends Error {} |
|
|
| function telemetryTimeout(ms: number, message: string): Promise<never> { |
| return new Promise((_, reject) => { |
| setTimeout( |
| (rej: (e: Error) => void, msg: string) => |
| rej(new TelemetryTimeoutError(msg)), |
| ms, |
| reject, |
| message, |
| ).unref() |
| }) |
| } |
|
|
| export function bootstrapTelemetry() { |
| if (process.env.USER_TYPE === 'ant') { |
| |
| if (process.env.ANT_OTEL_METRICS_EXPORTER) { |
| process.env.OTEL_METRICS_EXPORTER = process.env.ANT_OTEL_METRICS_EXPORTER |
| } |
| if (process.env.ANT_OTEL_LOGS_EXPORTER) { |
| process.env.OTEL_LOGS_EXPORTER = process.env.ANT_OTEL_LOGS_EXPORTER |
| } |
| if (process.env.ANT_OTEL_TRACES_EXPORTER) { |
| process.env.OTEL_TRACES_EXPORTER = process.env.ANT_OTEL_TRACES_EXPORTER |
| } |
| if (process.env.ANT_OTEL_EXPORTER_OTLP_PROTOCOL) { |
| process.env.OTEL_EXPORTER_OTLP_PROTOCOL = |
| process.env.ANT_OTEL_EXPORTER_OTLP_PROTOCOL |
| } |
| if (process.env.ANT_OTEL_EXPORTER_OTLP_ENDPOINT) { |
| process.env.OTEL_EXPORTER_OTLP_ENDPOINT = |
| process.env.ANT_OTEL_EXPORTER_OTLP_ENDPOINT |
| } |
| if (process.env.ANT_OTEL_EXPORTER_OTLP_HEADERS) { |
| process.env.OTEL_EXPORTER_OTLP_HEADERS = |
| process.env.ANT_OTEL_EXPORTER_OTLP_HEADERS |
| } |
| } |
|
|
| |
| if (!process.env.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE) { |
| process.env.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE = 'delta' |
| } |
| } |
|
|
| |
| |
| export function parseExporterTypes(value: string | undefined): string[] { |
| return (value || '') |
| .trim() |
| .split(',') |
| .filter(Boolean) |
| .map(t => t.trim()) |
| .filter(t => t !== 'none') |
| } |
|
|
| async function getOtlpReaders() { |
| const exporterTypes = parseExporterTypes(process.env.OTEL_METRICS_EXPORTER) |
| const exportInterval = parseInt( |
| process.env.OTEL_METRIC_EXPORT_INTERVAL || |
| DEFAULT_METRICS_EXPORT_INTERVAL_MS.toString(), |
| ) |
|
|
| const exporters = [] |
| for (const exporterType of exporterTypes) { |
| if (exporterType === 'console') { |
| |
| const consoleExporter = new ConsoleMetricExporter() |
| const originalExport = consoleExporter.export.bind(consoleExporter) |
|
|
| consoleExporter.export = (metrics, callback) => { |
| |
| if (metrics.resource && metrics.resource.attributes) { |
| |
|
|
| logForDebugging('\n=== Resource Attributes ===') |
| logForDebugging(jsonStringify(metrics.resource.attributes)) |
| logForDebugging('===========================\n') |
| } |
|
|
| return originalExport(metrics, callback) |
| } |
|
|
| exporters.push(consoleExporter) |
| } else if (exporterType === 'otlp') { |
| const protocol = |
| process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL?.trim() || |
| process.env.OTEL_EXPORTER_OTLP_PROTOCOL?.trim() |
|
|
| const httpConfig = getOTLPExporterConfig() |
|
|
| switch (protocol) { |
| case 'grpc': { |
| |
| |
| const { OTLPMetricExporter } = await import( |
| '@opentelemetry/exporter-metrics-otlp-grpc' |
| ) |
| exporters.push(new OTLPMetricExporter()) |
| break |
| } |
| case 'http/json': { |
| const { OTLPMetricExporter } = await import( |
| '@opentelemetry/exporter-metrics-otlp-http' |
| ) |
| exporters.push(new OTLPMetricExporter(httpConfig)) |
| break |
| } |
| case 'http/protobuf': { |
| const { OTLPMetricExporter } = await import( |
| '@opentelemetry/exporter-metrics-otlp-proto' |
| ) |
| exporters.push(new OTLPMetricExporter(httpConfig)) |
| break |
| } |
| default: |
| throw new Error( |
| `Unknown protocol set in OTEL_EXPORTER_OTLP_METRICS_PROTOCOL or OTEL_EXPORTER_OTLP_PROTOCOL env var: ${protocol}`, |
| ) |
| } |
| } else if (exporterType === 'prometheus') { |
| const { PrometheusExporter } = await import( |
| '@opentelemetry/exporter-prometheus' |
| ) |
| exporters.push(new PrometheusExporter()) |
| } else { |
| throw new Error( |
| `Unknown exporter type set in OTEL_EXPORTER_OTLP_METRICS_PROTOCOL or OTEL_EXPORTER_OTLP_PROTOCOL env var: ${exporterType}`, |
| ) |
| } |
| } |
|
|
| return exporters.map(exporter => { |
| if ('export' in exporter) { |
| return new PeriodicExportingMetricReader({ |
| exporter, |
| exportIntervalMillis: exportInterval, |
| }) |
| } |
| return exporter |
| }) |
| } |
|
|
| async function getOtlpLogExporters() { |
| const exporterTypes = parseExporterTypes(process.env.OTEL_LOGS_EXPORTER) |
|
|
| const protocol = |
| process.env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL?.trim() || |
| process.env.OTEL_EXPORTER_OTLP_PROTOCOL?.trim() |
| const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT |
|
|
| logForDebugging( |
| `[3P telemetry] getOtlpLogExporters: types=${jsonStringify(exporterTypes)}, protocol=${protocol}, endpoint=${endpoint}`, |
| ) |
|
|
| const exporters = [] |
| for (const exporterType of exporterTypes) { |
| if (exporterType === 'console') { |
| exporters.push(new ConsoleLogRecordExporter()) |
| } else if (exporterType === 'otlp') { |
| const httpConfig = getOTLPExporterConfig() |
|
|
| switch (protocol) { |
| case 'grpc': { |
| const { OTLPLogExporter } = await import( |
| '@opentelemetry/exporter-logs-otlp-grpc' |
| ) |
| exporters.push(new OTLPLogExporter()) |
| break |
| } |
| case 'http/json': { |
| const { OTLPLogExporter } = await import( |
| '@opentelemetry/exporter-logs-otlp-http' |
| ) |
| exporters.push(new OTLPLogExporter(httpConfig)) |
| break |
| } |
| case 'http/protobuf': { |
| const { OTLPLogExporter } = await import( |
| '@opentelemetry/exporter-logs-otlp-proto' |
| ) |
| exporters.push(new OTLPLogExporter(httpConfig)) |
| break |
| } |
| default: |
| throw new Error( |
| `Unknown protocol set in OTEL_EXPORTER_OTLP_LOGS_PROTOCOL or OTEL_EXPORTER_OTLP_PROTOCOL env var: ${protocol}`, |
| ) |
| } |
| } else { |
| throw new Error( |
| `Unknown exporter type set in OTEL_LOGS_EXPORTER env var: ${exporterType}`, |
| ) |
| } |
| } |
|
|
| return exporters |
| } |
|
|
| async function getOtlpTraceExporters() { |
| const exporterTypes = parseExporterTypes(process.env.OTEL_TRACES_EXPORTER) |
|
|
| const exporters = [] |
| for (const exporterType of exporterTypes) { |
| if (exporterType === 'console') { |
| exporters.push(new ConsoleSpanExporter()) |
| } else if (exporterType === 'otlp') { |
| const protocol = |
| process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL?.trim() || |
| process.env.OTEL_EXPORTER_OTLP_PROTOCOL?.trim() |
|
|
| const httpConfig = getOTLPExporterConfig() |
|
|
| switch (protocol) { |
| case 'grpc': { |
| const { OTLPTraceExporter } = await import( |
| '@opentelemetry/exporter-trace-otlp-grpc' |
| ) |
| exporters.push(new OTLPTraceExporter()) |
| break |
| } |
| case 'http/json': { |
| const { OTLPTraceExporter } = await import( |
| '@opentelemetry/exporter-trace-otlp-http' |
| ) |
| exporters.push(new OTLPTraceExporter(httpConfig)) |
| break |
| } |
| case 'http/protobuf': { |
| const { OTLPTraceExporter } = await import( |
| '@opentelemetry/exporter-trace-otlp-proto' |
| ) |
| exporters.push(new OTLPTraceExporter(httpConfig)) |
| break |
| } |
| default: |
| throw new Error( |
| `Unknown protocol set in OTEL_EXPORTER_OTLP_TRACES_PROTOCOL or OTEL_EXPORTER_OTLP_PROTOCOL env var: ${protocol}`, |
| ) |
| } |
| } else { |
| throw new Error( |
| `Unknown exporter type set in OTEL_TRACES_EXPORTER env var: ${exporterType}`, |
| ) |
| } |
| } |
|
|
| return exporters |
| } |
|
|
| export function isTelemetryEnabled() { |
| return isEnvTruthy(process.env.CLAUDE_CODE_ENABLE_TELEMETRY) |
| } |
|
|
| function getBigQueryExportingReader() { |
| const bigqueryExporter = new BigQueryMetricsExporter() |
| return new PeriodicExportingMetricReader({ |
| exporter: bigqueryExporter, |
| exportIntervalMillis: 5 * 60 * 1000, |
| }) |
| } |
|
|
| function isBigQueryMetricsEnabled() { |
| |
| |
| |
| |
| const subscriptionType = getSubscriptionType() |
| const isC4EOrTeamUser = |
| isClaudeAISubscriber() && |
| (subscriptionType === 'enterprise' || subscriptionType === 'team') |
|
|
| return is1PApiCustomer() || isC4EOrTeamUser |
| } |
|
|
| |
| |
| |
| |
| async function initializeBetaTracing( |
| resource: ReturnType<typeof resourceFromAttributes>, |
| ): Promise<void> { |
| const endpoint = process.env.BETA_TRACING_ENDPOINT |
| if (!endpoint) { |
| return |
| } |
|
|
| const [{ OTLPTraceExporter }, { OTLPLogExporter }] = await Promise.all([ |
| import('@opentelemetry/exporter-trace-otlp-http'), |
| import('@opentelemetry/exporter-logs-otlp-http'), |
| ]) |
|
|
| const httpConfig = { |
| url: `${endpoint}/v1/traces`, |
| } |
|
|
| const logHttpConfig = { |
| url: `${endpoint}/v1/logs`, |
| } |
|
|
| |
| const traceExporter = new OTLPTraceExporter(httpConfig) |
| const spanProcessor = new BatchSpanProcessor(traceExporter, { |
| scheduledDelayMillis: DEFAULT_TRACES_EXPORT_INTERVAL_MS, |
| }) |
|
|
| const tracerProvider = new BasicTracerProvider({ |
| resource, |
| spanProcessors: [spanProcessor], |
| }) |
|
|
| trace.setGlobalTracerProvider(tracerProvider) |
| setTracerProvider(tracerProvider) |
|
|
| |
| const logExporter = new OTLPLogExporter(logHttpConfig) |
| const loggerProvider = new LoggerProvider({ |
| resource, |
| processors: [ |
| new BatchLogRecordProcessor(logExporter, { |
| scheduledDelayMillis: DEFAULT_LOGS_EXPORT_INTERVAL_MS, |
| }), |
| ], |
| }) |
|
|
| logs.setGlobalLoggerProvider(loggerProvider) |
| setLoggerProvider(loggerProvider) |
|
|
| |
| const eventLogger = logs.getLogger( |
| 'com.anthropic.claude_code.events', |
| MACRO.VERSION, |
| ) |
| setEventLogger(eventLogger) |
|
|
| |
| process.on('beforeExit', async () => { |
| await loggerProvider?.forceFlush() |
| await tracerProvider?.forceFlush() |
| }) |
|
|
| process.on('exit', () => { |
| void loggerProvider?.forceFlush() |
| void tracerProvider?.forceFlush() |
| }) |
| } |
|
|
| export async function initializeTelemetry() { |
| profileCheckpoint('telemetry_init_start') |
| bootstrapTelemetry() |
|
|
| |
| |
| |
| |
| |
| |
| |
| if (getHasFormattedOutput()) { |
| for (const key of [ |
| 'OTEL_METRICS_EXPORTER', |
| 'OTEL_LOGS_EXPORTER', |
| 'OTEL_TRACES_EXPORTER', |
| ] as const) { |
| const v = process.env[key] |
| if (v?.includes('console')) { |
| process.env[key] = v |
| .split(',') |
| .map(s => s.trim()) |
| .filter(s => s !== 'console') |
| .join(',') |
| } |
| } |
| } |
|
|
| diag.setLogger(new ClaudeCodeDiagLogger(), DiagLogLevel.ERROR) |
|
|
| |
| |
| initializePerfettoTracing() |
|
|
| const readers = [] |
|
|
| |
| const telemetryEnabled = isTelemetryEnabled() |
| logForDebugging( |
| `[3P telemetry] isTelemetryEnabled=${telemetryEnabled} (CLAUDE_CODE_ENABLE_TELEMETRY=${process.env.CLAUDE_CODE_ENABLE_TELEMETRY})`, |
| ) |
| if (telemetryEnabled) { |
| readers.push(...(await getOtlpReaders())) |
| } |
|
|
| |
| if (isBigQueryMetricsEnabled()) { |
| readers.push(getBigQueryExportingReader()) |
| } |
|
|
| |
| const platform = getPlatform() |
| const baseAttributes: Record<string, string> = { |
| [ATTR_SERVICE_NAME]: 'claude-code', |
| [ATTR_SERVICE_VERSION]: MACRO.VERSION, |
| } |
|
|
| |
| if (platform === 'wsl') { |
| const wslVersion = getWslVersion() |
| if (wslVersion) { |
| baseAttributes['wsl.version'] = wslVersion |
| } |
| } |
|
|
| const baseResource = resourceFromAttributes(baseAttributes) |
|
|
| |
| const osResource = resourceFromAttributes( |
| osDetector.detect().attributes || {}, |
| ) |
|
|
| |
| const hostDetected = hostDetector.detect() |
| const hostArchAttributes = hostDetected.attributes?.[SEMRESATTRS_HOST_ARCH] |
| ? { |
| [SEMRESATTRS_HOST_ARCH]: hostDetected.attributes[SEMRESATTRS_HOST_ARCH], |
| } |
| : {} |
| const hostArchResource = resourceFromAttributes(hostArchAttributes) |
|
|
| const envResource = resourceFromAttributes( |
| envDetector.detect().attributes || {}, |
| ) |
|
|
| |
| const resource = baseResource |
| .merge(osResource) |
| .merge(hostArchResource) |
| .merge(envResource) |
|
|
| |
| |
| if (isBetaTracingEnabled()) { |
| void initializeBetaTracing(resource).catch(e => |
| logForDebugging(`Beta tracing init failed: ${e}`, { level: 'error' }), |
| ) |
| |
| const meterProvider = new MeterProvider({ |
| resource, |
| views: [], |
| readers, |
| }) |
| setMeterProvider(meterProvider) |
|
|
| |
| const shutdownTelemetry = async () => { |
| const timeoutMs = parseInt( |
| process.env.CLAUDE_CODE_OTEL_SHUTDOWN_TIMEOUT_MS || '2000', |
| ) |
| try { |
| endInteractionSpan() |
|
|
| |
| |
| |
| |
| const loggerProvider = getLoggerProvider() |
| const tracerProvider = getTracerProvider() |
|
|
| const chains: Promise<void>[] = [meterProvider.shutdown()] |
| if (loggerProvider) { |
| chains.push( |
| loggerProvider.forceFlush().then(() => loggerProvider.shutdown()), |
| ) |
| } |
| if (tracerProvider) { |
| chains.push( |
| tracerProvider.forceFlush().then(() => tracerProvider.shutdown()), |
| ) |
| } |
|
|
| await Promise.race([ |
| Promise.all(chains), |
| telemetryTimeout(timeoutMs, 'OpenTelemetry shutdown timeout'), |
| ]) |
| } catch { |
| |
| } |
| } |
| registerCleanup(shutdownTelemetry) |
|
|
| return meterProvider.getMeter('com.anthropic.claude_code', MACRO.VERSION) |
| } |
|
|
| const meterProvider = new MeterProvider({ |
| resource, |
| views: [], |
| readers, |
| }) |
|
|
| |
| setMeterProvider(meterProvider) |
|
|
| |
| if (telemetryEnabled) { |
| const logExporters = await getOtlpLogExporters() |
| logForDebugging( |
| `[3P telemetry] Created ${logExporters.length} log exporter(s)`, |
| ) |
|
|
| if (logExporters.length > 0) { |
| const loggerProvider = new LoggerProvider({ |
| resource, |
| |
| processors: logExporters.map( |
| exporter => |
| new BatchLogRecordProcessor(exporter, { |
| scheduledDelayMillis: parseInt( |
| process.env.OTEL_LOGS_EXPORT_INTERVAL || |
| DEFAULT_LOGS_EXPORT_INTERVAL_MS.toString(), |
| ), |
| }), |
| ), |
| }) |
|
|
| |
| logs.setGlobalLoggerProvider(loggerProvider) |
| setLoggerProvider(loggerProvider) |
|
|
| |
| const eventLogger = logs.getLogger( |
| 'com.anthropic.claude_code.events', |
| MACRO.VERSION, |
| ) |
| setEventLogger(eventLogger) |
| logForDebugging('[3P telemetry] Event logger set successfully') |
|
|
| |
| |
| |
| process.on('beforeExit', async () => { |
| await loggerProvider?.forceFlush() |
| |
| const tracerProvider = getTracerProvider() |
| await tracerProvider?.forceFlush() |
| }) |
|
|
| process.on('exit', () => { |
| |
| void loggerProvider?.forceFlush() |
| void getTracerProvider()?.forceFlush() |
| }) |
| } |
| } |
|
|
| |
| if (telemetryEnabled && isEnhancedTelemetryEnabled()) { |
| const traceExporters = await getOtlpTraceExporters() |
| if (traceExporters.length > 0) { |
| |
| const spanProcessors = traceExporters.map( |
| exporter => |
| new BatchSpanProcessor(exporter, { |
| scheduledDelayMillis: parseInt( |
| process.env.OTEL_TRACES_EXPORT_INTERVAL || |
| DEFAULT_TRACES_EXPORT_INTERVAL_MS.toString(), |
| ), |
| }), |
| ) |
|
|
| const tracerProvider = new BasicTracerProvider({ |
| resource, |
| spanProcessors, |
| }) |
|
|
| |
| trace.setGlobalTracerProvider(tracerProvider) |
| setTracerProvider(tracerProvider) |
| } |
| } |
|
|
| |
| const shutdownTelemetry = async () => { |
| const timeoutMs = parseInt( |
| process.env.CLAUDE_CODE_OTEL_SHUTDOWN_TIMEOUT_MS || '2000', |
| ) |
|
|
| try { |
| |
| endInteractionSpan() |
|
|
| const shutdownPromises = [meterProvider.shutdown()] |
| const loggerProvider = getLoggerProvider() |
| if (loggerProvider) { |
| shutdownPromises.push(loggerProvider.shutdown()) |
| } |
| const tracerProvider = getTracerProvider() |
| if (tracerProvider) { |
| shutdownPromises.push(tracerProvider.shutdown()) |
| } |
|
|
| await Promise.race([ |
| Promise.all(shutdownPromises), |
| telemetryTimeout(timeoutMs, 'OpenTelemetry shutdown timeout'), |
| ]) |
| } catch (error) { |
| if (error instanceof Error && error.message.includes('timeout')) { |
| logForDebugging( |
| ` |
| OpenTelemetry telemetry flush timed out after ${timeoutMs}ms |
| |
| To resolve this issue, you can: |
| 1. Increase the timeout by setting CLAUDE_CODE_OTEL_SHUTDOWN_TIMEOUT_MS env var (e.g., 5000 for 5 seconds) |
| 2. Check if your OpenTelemetry backend is experiencing scalability issues |
| 3. Disable OpenTelemetry by unsetting CLAUDE_CODE_ENABLE_TELEMETRY env var |
| |
| Current timeout: ${timeoutMs}ms |
| `, |
| { level: 'error' }, |
| ) |
| } |
| throw error |
| } |
| } |
|
|
| |
| registerCleanup(shutdownTelemetry) |
|
|
| return meterProvider.getMeter('com.anthropic.claude_code', MACRO.VERSION) |
| } |
|
|
| |
| |
| |
| |
| export async function flushTelemetry(): Promise<void> { |
| const meterProvider = getMeterProvider() |
| if (!meterProvider) { |
| return |
| } |
|
|
| const timeoutMs = parseInt( |
| process.env.CLAUDE_CODE_OTEL_FLUSH_TIMEOUT_MS || '5000', |
| ) |
|
|
| try { |
| const flushPromises = [meterProvider.forceFlush()] |
| const loggerProvider = getLoggerProvider() |
| if (loggerProvider) { |
| flushPromises.push(loggerProvider.forceFlush()) |
| } |
| const tracerProvider = getTracerProvider() |
| if (tracerProvider) { |
| flushPromises.push(tracerProvider.forceFlush()) |
| } |
|
|
| await Promise.race([ |
| Promise.all(flushPromises), |
| telemetryTimeout(timeoutMs, 'OpenTelemetry flush timeout'), |
| ]) |
|
|
| logForDebugging('Telemetry flushed successfully') |
| } catch (error) { |
| if (error instanceof TelemetryTimeoutError) { |
| logForDebugging( |
| `Telemetry flush timed out after ${timeoutMs}ms. Some metrics may not be exported.`, |
| { level: 'warn' }, |
| ) |
| } else { |
| logForDebugging(`Telemetry flush failed: ${errorMessage(error)}`, { |
| level: 'error', |
| }) |
| } |
| |
| } |
| } |
|
|
| function parseOtelHeadersEnvVar(): Record<string, string> { |
| const headers: Record<string, string> = {} |
| const envHeaders = process.env.OTEL_EXPORTER_OTLP_HEADERS |
| if (envHeaders) { |
| for (const pair of envHeaders.split(',')) { |
| const [key, ...valueParts] = pair.split('=') |
| if (key && valueParts.length > 0) { |
| headers[key.trim()] = valueParts.join('=').trim() |
| } |
| } |
| } |
| return headers |
| } |
|
|
| |
| |
| |
| |
| |
| function getOTLPExporterConfig() { |
| const proxyUrl = getProxyUrl() |
| const mtlsConfig = getMTLSConfig() |
| const settings = getSettings_DEPRECATED() |
|
|
| |
| const config: Record<string, unknown> = {} |
|
|
| |
| const staticHeaders = parseOtelHeadersEnvVar() |
|
|
| |
| |
| if (settings?.otelHeadersHelper) { |
| config.headers = async (): Promise<Record<string, string>> => { |
| const dynamicHeaders = getOtelHeadersFromHelper() |
| return { ...staticHeaders, ...dynamicHeaders } |
| } |
| } else if (Object.keys(staticHeaders).length > 0) { |
| config.headers = async (): Promise<Record<string, string>> => staticHeaders |
| } |
|
|
| |
| const otelEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT |
| if (!proxyUrl || (otelEndpoint && shouldBypassProxy(otelEndpoint))) { |
| |
| const caCerts = getCACertificates() |
| if (mtlsConfig || caCerts) { |
| config.httpAgentOptions = { |
| ...mtlsConfig, |
| ...(caCerts && { ca: caCerts }), |
| } |
| } |
| return config |
| } |
|
|
| |
| const caCerts = getCACertificates() |
| const agentFactory = (_protocol: string) => { |
| |
| const proxyAgent = |
| mtlsConfig || caCerts |
| ? new HttpsProxyAgent(proxyUrl, { |
| ...(mtlsConfig && { |
| cert: mtlsConfig.cert, |
| key: mtlsConfig.key, |
| passphrase: mtlsConfig.passphrase, |
| }), |
| ...(caCerts && { ca: caCerts }), |
| }) |
| : new HttpsProxyAgent(proxyUrl) |
|
|
| return proxyAgent |
| } |
|
|
| config.httpAgentOptions = agentFactory |
| return config |
| } |
|
|