diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index 864f5bae16d..29a55b58910 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -15,6 +15,7 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2 ### :bug: Bug Fixes +* fix(otlp-exporter-base): honor Node.js environment proxy settings in HTTP agent factory [#6660](https://github.com/open-telemetry/opentelemetry-js/pull/6660) @cyphercodes * fix(configuration): do not validate `OTEL_CONFIG_FILE` value before using it for file config [#6643](https://github.com/open-telemetry/opentelemetry-js/pull/6643) @trentm * fix(configuration): improve how 'additionalProperties' in JSON schema is translated to TS types [#6650](https://github.com/open-telemetry/opentelemetry-js/pull/6650) @trentm * fix(sampler-jaeger-remote): add missing axios dep [#6656](https://github.com/open-telemetry/opentelemetry-js/pull/6656) @trentm diff --git a/experimental/packages/otlp-exporter-base/src/configuration/otlp-node-http-configuration.ts b/experimental/packages/otlp-exporter-base/src/configuration/otlp-node-http-configuration.ts index 922b18f9dc0..3ccc439796d 100644 --- a/experimental/packages/otlp-exporter-base/src/configuration/otlp-node-http-configuration.ts +++ b/experimental/packages/otlp-exporter-base/src/configuration/otlp-node-http-configuration.ts @@ -36,6 +36,32 @@ export interface OtlpNodeHttpConfiguration extends OtlpHttpConfiguration { userAgent?: string; } +function hasEnvProxyEnvVar(name: string): boolean { + return process.env[name] != null && process.env[name] !== ''; +} + +function hasEnvProxyConfiguration(): boolean { + return ( + process.env.NODE_USE_ENV_PROXY === '1' || + hasEnvProxyEnvVar('HTTP_PROXY') || + hasEnvProxyEnvVar('http_proxy') || + hasEnvProxyEnvVar('HTTPS_PROXY') || + hasEnvProxyEnvVar('https_proxy') + ); +} + +function getEnvProxyAgentOptions(): + | { proxyEnv: NodeJS.ProcessEnv } + | undefined { + if (hasEnvProxyConfiguration()) { + // `proxyEnv` is used by Node.js' HTTP agents to honor HTTP(S)_PROXY + // while preserving exporter-specific agent options such as keepAlive. + return { proxyEnv: process.env }; + } + + return undefined; +} + export function httpAgentFactoryFromOptions( options: http.AgentOptions | https.AgentOptions ): HttpAgentFactory { @@ -43,14 +69,21 @@ export function httpAgentFactoryFromOptions( const isInsecure = protocol === 'http:'; const module = isInsecure ? import('http') : import('https'); const { Agent } = await module; + const envProxyAgentOptions = getEnvProxyAgentOptions(); if (isInsecure) { // eslint-disable-next-line @typescript-eslint/no-unused-vars -- these props should not be used in agent options const { ca, cert, key, ...insecureOptions } = options as https.AgentOptions; - return new Agent(insecureOptions); + return new Agent({ + ...insecureOptions, + ...envProxyAgentOptions, + } as http.AgentOptions); } - return new Agent(options); + return new Agent({ + ...options, + ...envProxyAgentOptions, + } as https.AgentOptions); }; } diff --git a/experimental/packages/otlp-exporter-base/test/node/configuration/otlp-http-configuration.test.ts b/experimental/packages/otlp-exporter-base/test/node/configuration/otlp-http-configuration.test.ts index 9532a4c1427..3cf209fe67f 100644 --- a/experimental/packages/otlp-exporter-base/test/node/configuration/otlp-http-configuration.test.ts +++ b/experimental/packages/otlp-exporter-base/test/node/configuration/otlp-http-configuration.test.ts @@ -3,10 +3,93 @@ * SPDX-License-Identifier: Apache-2.0 */ import * as assert from 'assert'; -import { mergeOtlpNodeHttpConfigurationWithDefaults } from '../../../src/configuration/otlp-node-http-configuration'; +import * as http from 'http'; +import * as https from 'https'; +import { + httpAgentFactoryFromOptions, + mergeOtlpNodeHttpConfigurationWithDefaults, +} from '../../../src/configuration/otlp-node-http-configuration'; import type { OtlpNodeHttpConfiguration } from '../../../src/configuration/otlp-node-http-configuration'; import { VERSION } from '../../../src/version'; +const ENV_PROXY_VARIABLES = [ + 'NODE_USE_ENV_PROXY', + 'HTTP_PROXY', + 'http_proxy', + 'HTTPS_PROXY', + 'https_proxy', +]; + +function agentOptions(agent: http.Agent | https.Agent): any { + return (agent as any).options; +} + +describe('httpAgentFactoryFromOptions', function () { + let originalEnv: Record; + + beforeEach(function () { + originalEnv = {}; + for (const envVar of ENV_PROXY_VARIABLES) { + originalEnv[envVar] = process.env[envVar]; + delete process.env[envVar]; + } + }); + + afterEach(function () { + for (const envVar of ENV_PROXY_VARIABLES) { + if (originalEnv[envVar] == null) { + delete process.env[envVar]; + } else { + process.env[envVar] = originalEnv[envVar]; + } + } + }); + + it('creates protocol-specific agents with the provided options', async function () { + const factory = httpAgentFactoryFromOptions({ keepAlive: true }); + + const httpAgent = (await factory('http:')) as http.Agent; + const httpsAgent = (await factory('https:')) as https.Agent; + + assert.ok(httpAgent instanceof http.Agent); + assert.ok(httpsAgent instanceof https.Agent); + assert.strictEqual(agentOptions(httpAgent).keepAlive, true); + assert.strictEqual(agentOptions(httpsAgent).keepAlive, true); + assert.strictEqual(agentOptions(httpAgent).proxyEnv, undefined); + assert.strictEqual(agentOptions(httpsAgent).proxyEnv, undefined); + }); + + it('passes proxyEnv to http agents when HTTP_PROXY is configured', async function () { + process.env.HTTP_PROXY = 'http://proxy.example:3128'; + const factory = httpAgentFactoryFromOptions({ keepAlive: true }); + + const agent = (await factory('http:')) as http.Agent; + + assert.strictEqual(agentOptions(agent).keepAlive, true); + assert.strictEqual(agentOptions(agent).proxyEnv, process.env); + }); + + it('passes proxyEnv to https agents when HTTPS_PROXY is configured', async function () { + process.env.HTTPS_PROXY = 'http://proxy.example:3128'; + const factory = httpAgentFactoryFromOptions({ keepAlive: true }); + + const agent = (await factory('https:')) as https.Agent; + + assert.strictEqual(agentOptions(agent).keepAlive, true); + assert.strictEqual(agentOptions(agent).proxyEnv, process.env); + }); + + it('passes proxyEnv to agents when NODE_USE_ENV_PROXY is enabled', async function () { + process.env.NODE_USE_ENV_PROXY = '1'; + const factory = httpAgentFactoryFromOptions({ keepAlive: true }); + + const agent = (await factory('https:')) as https.Agent; + + assert.strictEqual(agentOptions(agent).keepAlive, true); + assert.strictEqual(agentOptions(agent).proxyEnv, process.env); + }); +}); + describe('mergeOtlpNodeHttpConfigurationWithDefaults', function () { const testDefaults: OtlpNodeHttpConfiguration = { url: 'http://default.example.test',