diff --git a/packages/dd-trace/src/exporters/common/proxy.js b/packages/dd-trace/src/exporters/common/proxy.js index 85274f42575..c928749803f 100644 --- a/packages/dd-trace/src/exporters/common/proxy.js +++ b/packages/dd-trace/src/exporters/common/proxy.js @@ -24,6 +24,8 @@ function getHttpsProxyAgent (url, directAgent) { const proxyUrl = getProxyForUrl(target) if (!proxyUrl) return directAgent + directAgent ??= require('node:https').globalAgent + if (proxyAgents === undefined) { defaultAgentKey = {} proxyAgents = new WeakMap() diff --git a/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js b/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js index b5034396c7d..c98892027d0 100644 --- a/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +++ b/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js @@ -6,7 +6,9 @@ const { URL } = require('node:url') const { storage } = require('../../../../datadog-core') const log = require('../../log') const { createServerlessDeliveryTracker } = require('../../serverless') +const { getHttpsProxyAgent } = require('../../exporters/common/proxy') const telemetryMetrics = require('../../telemetry/metrics') +const { version: tracerVersion } = require('../../../../../package.json') const tracerMetrics = telemetryMetrics.manager.namespace('tracers') const legacyStorage = storage('legacy') @@ -48,8 +50,10 @@ class OtlpHttpExporterBase { hostname: parsedUrl.hostname, port: parsedUrl.port, path: parsedUrl.pathname + parsedUrl.search, + agent: parsedUrl.protocol === 'https:' ? getHttpsProxyAgent(parsedUrl) : undefined, headers: { 'Content-Type': isJson ? 'application/json' : 'application/x-protobuf', + 'User-Agent': `dd-trace-js/${tracerVersion}`, ...headers, }, } @@ -169,6 +173,7 @@ class OtlpHttpExporterBase { this.options.hostname = parsedUrl.hostname this.options.port = parsedUrl.port this.options.path = parsedUrl.pathname + parsedUrl.search + this.options.agent = parsedUrl.protocol === 'https:' ? getHttpsProxyAgent(parsedUrl) : undefined this.telemetryTags[0] = `protocol:${this.#transport === https ? 'https' : 'http'}` } diff --git a/packages/dd-trace/test/opentelemetry/otlp/otlp_http_exporter_base.spec.js b/packages/dd-trace/test/opentelemetry/otlp/otlp_http_exporter_base.spec.js new file mode 100644 index 00000000000..629af20ac13 --- /dev/null +++ b/packages/dd-trace/test/opentelemetry/otlp/otlp_http_exporter_base.spec.js @@ -0,0 +1,100 @@ +'use strict' + +const assert = require('node:assert/strict') +const { describe, it, afterEach, beforeEach } = require('mocha') + +require('../../setup/core') + +const OtlpHttpExporterBase = require('../../../src/opentelemetry/otlp/otlp_http_exporter_base') +const { version: tracerVersion } = require('../../../../../package.json') + +const proxyEnvironmentNames = [ + 'ALL_PROXY', + 'HTTPS_PROXY', + 'HTTP_PROXY', + 'NO_PROXY', + 'all_proxy', + 'https_proxy', + 'http_proxy', + 'no_proxy', +] + +describe('OtlpHttpExporterBase', () => { + let originalEnvironment + + beforeEach(() => { + originalEnvironment = new Map() + for (const name of proxyEnvironmentNames) { + originalEnvironment.set(name, process.env[name]) + delete process.env[name] + } + }) + + afterEach(() => { + for (const [name, value] of originalEnvironment) { + if (value === undefined) { + delete process.env[name] + } else { + process.env[name] = value + } + } + }) + + it('sends a User-Agent header identifying the tracer version', () => { + const exporter = new OtlpHttpExporterBase('https://intake.example/path', undefined, 1000, 'http/protobuf', 'traces') + + assert.strictEqual(exporter.options.headers['User-Agent'], `dd-trace-js/${tracerVersion}`) + }) + + it('does not set an agent for an HTTPS endpoint when no proxy is configured', () => { + const exporter = new OtlpHttpExporterBase('https://intake.example/path', undefined, 1000, 'http/protobuf', 'traces') + + assert.strictEqual(exporter.options.agent, undefined) + }) + + it('does not set an agent for an HTTP endpoint even when a proxy is configured', () => { + process.env.https_proxy = 'http://127.0.0.1:9999' + + const exporter = new OtlpHttpExporterBase('http://intake.example/path', undefined, 1000, 'http/protobuf', 'traces') + + assert.strictEqual(exporter.options.agent, undefined) + }) + + it('routes an HTTPS endpoint through the configured proxy agent', () => { + process.env.https_proxy = 'http://127.0.0.1:9999' + + const exporter = new OtlpHttpExporterBase('https://intake.example/path', undefined, 1000, 'http/protobuf', 'traces') + + assert.ok(exporter.options.agent) + assert.strictEqual(exporter.options.agent.proxy.hostname, '127.0.0.1') + assert.strictEqual(exporter.options.agent.proxy.port, '9999') + }) + + describe('setUrl', () => { + it('picks up a proxy agent when re-targeted to an HTTPS endpoint', () => { + const exporter = new OtlpHttpExporterBase( + 'http://intake.example/path', undefined, 1000, 'http/protobuf', 'traces' + ) + assert.strictEqual(exporter.options.agent, undefined) + + process.env.https_proxy = 'http://127.0.0.1:9999' + exporter.setUrl('https://intake.example/other-path') + + assert.ok(exporter.options.agent) + assert.strictEqual(exporter.options.agent.proxy.hostname, '127.0.0.1') + assert.strictEqual(exporter.options.agent.proxy.port, '9999') + }) + + it('clears the agent when re-targeted from HTTPS to HTTP', () => { + process.env.https_proxy = 'http://127.0.0.1:9999' + const exporter = new OtlpHttpExporterBase( + 'https://intake.example/path', undefined, 1000, 'http/protobuf', 'traces' + ) + assert.ok(exporter.options.agent) + + exporter.setUrl('http://intake.example/other-path') + + assert.strictEqual(exporter.options.agent, undefined) + }) + }) +})