Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .mocharc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
require: `${__dirname}/ts-node.js`,
};
1 change: 0 additions & 1 deletion .mocharc.yml

This file was deleted.

2 changes: 2 additions & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2

### :rocket: Features

* feat(exporter-otlp-\*): support custom HTTP agents [#5719](https://github.com/open-telemetry/opentelemetry-js/pull/5719) @raphael-theriault-swi

Comment thread
raphael-theriault-swi marked this conversation as resolved.
### :bug: Bug Fixes

### :books: Documentation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,22 @@
import { OTLPExporterNodeConfigBase } from './legacy-node-configuration';
import {
getHttpConfigurationDefaults,
HttpAgentFactory,
httpAgentFactoryFromOptions,
mergeOtlpHttpConfigurationWithDefaults,
OtlpHttpConfiguration,
} from './otlp-http-configuration';
import { getHttpConfigurationFromEnvironment } from './otlp-http-env-configuration';
import type * as http from 'http';
import type * as https from 'https';
import { diag } from '@opentelemetry/api';
import { wrapStaticHeadersInFunction } from './shared-configuration';

function convertLegacyAgentOptions(
config: OTLPExporterNodeConfigBase
): http.AgentOptions | https.AgentOptions | undefined {
): HttpAgentFactory | undefined {
if (typeof config.httpAgentOptions === 'function') {
return config.httpAgentOptions;
}

// populate keepAlive for use with new settings
if (config?.keepAlive != null) {
if (config.httpAgentOptions != null) {
Expand All @@ -44,7 +48,11 @@ function convertLegacyAgentOptions(
}
}

return config.httpAgentOptions;
if (config.httpAgentOptions != null) {
return httpAgentFactoryFromOptions(config.httpAgentOptions);
} else {
return undefined;
}
}

/**
Expand Down Expand Up @@ -72,7 +80,7 @@ export function convertLegacyHttpOptions(
concurrencyLimit: config.concurrencyLimit,
timeoutMillis: config.timeoutMillis,
compression: config.compression,
agentOptions: convertLegacyAgentOptions(config),
agent: convertLegacyAgentOptions(config),
Comment thread
raphael-theriault-swi marked this conversation as resolved.
Outdated
},
getHttpConfigurationFromEnvironment(signalIdentifier, signalResourcePath),
getHttpConfigurationDefaults(requiredHeaders, signalResourcePath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@
import type * as http from 'http';
import type * as https from 'https';

import { OTLPExporterConfigBase } from './legacy-base-configuration';
import type { OTLPExporterConfigBase } from './legacy-base-configuration';
import type { HttpAgentFactory } from './otlp-http-configuration';

/**
* Collector Exporter node base config
*/
export interface OTLPExporterNodeConfigBase extends OTLPExporterConfigBase {
keepAlive?: boolean;
compression?: CompressionAlgorithm;
httpAgentOptions?: http.AgentOptions | https.AgentOptions;
httpAgentOptions?: http.AgentOptions | https.AgentOptions | HttpAgentFactory;
Comment thread
raphael-theriault-swi marked this conversation as resolved.
}

export enum CompressionAlgorithm {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,16 @@ import { validateAndNormalizeHeaders } from '../util';
import type * as http from 'http';
import type * as https from 'https';

export type HttpAgentFactory =
| ((protocol: string) => http.Agent)
| ((protocol: string) => https.Agent)
| ((protocol: string) => Promise<http.Agent>)
| ((protocol: string) => Promise<https.Agent>);

export interface OtlpHttpConfiguration extends OtlpSharedConfiguration {
url: string;
headers: () => Record<string, string>;
agentOptions: http.AgentOptions | https.AgentOptions;
agent: HttpAgentFactory;
}

function mergeHeaders(
Expand Down Expand Up @@ -71,6 +77,16 @@ function validateUserProvidedUrl(url: string | undefined): string | undefined {
}
}

export function httpAgentFactoryFromOptions(
options: http.AgentOptions | https.AgentOptions
): HttpAgentFactory {
return async protocol => {
const { Agent } =
protocol === 'http:' ? await import('http') : await import('https');
return new Agent(options);
};
}

/**
* @param userProvidedConfiguration Configuration options provided by the user in code.
* @param fallbackConfiguration Fallback to use when the {@link userProvidedConfiguration} does not specify an option.
Expand All @@ -96,10 +112,10 @@ export function mergeOtlpHttpConfigurationWithDefaults(
validateUserProvidedUrl(userProvidedConfiguration.url) ??
fallbackConfiguration.url ??
defaultConfiguration.url,
agentOptions:
userProvidedConfiguration.agentOptions ??
fallbackConfiguration.agentOptions ??
defaultConfiguration.agentOptions,
agent:
userProvidedConfiguration.agent ??
fallbackConfiguration.agent ??
defaultConfiguration.agent,
};
}

Expand All @@ -111,6 +127,6 @@ export function getHttpConfigurationDefaults(
...getSharedConfigurationDefaults(),
headers: () => requiredHeaders,
url: 'http://localhost:4318/' + signalResourcePath,
agentOptions: { keepAlive: true },
agent: httpAgentFactoryFromOptions({ keepAlive: true }),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

export { httpAgentFactoryFromOptions } from './configuration/otlp-http-configuration';
export { createOtlpHttpExportDelegate } from './otlp-http-export-delegate';
export { getSharedConfigurationFromEnvironment } from './configuration/shared-env-configuration';
export { convertLegacyHttpOptions } from './configuration/convert-legacy-node-http-options';
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class HttpExporterTransport implements IExporterTransport {
constructor(private _parameters: HttpRequestParameters) {}

async send(data: Uint8Array, timeoutMillis: number): Promise<ExportResponse> {
const { agent, send } = this._loadUtils();
const { agent, send } = await this._loadUtils();

return new Promise<ExportResponse>(resolve => {
send(
Expand All @@ -56,23 +56,20 @@ class HttpExporterTransport implements IExporterTransport {
// intentionally left empty, nothing to do.
}

private _loadUtils(): Utils {
private async _loadUtils(): Promise<Utils> {
let utils = this._utils;

if (utils === null) {
// Lazy require to ensure that http/https is not required before instrumentations can wrap it.
const {
sendWithHttp,
createHttpAgent,
// eslint-disable-next-line @typescript-eslint/no-require-imports
} = require('./http-transport-utils');
// Lazy import to ensure that http/https is not required before instrumentations can wrap it.
const imported = await import('./http-transport-utils.js');
Comment thread
pichlermarc marked this conversation as resolved.
Outdated

utils = this._utils = {
agent: createHttpAgent(
this._parameters.url,
this._parameters.agentOptions
agent: await this._parameters.agent(
new URL(this._parameters.url).protocol
Comment thread
pichlermarc marked this conversation as resolved.
Outdated
),
send: sendWithHttp,
// @ts-expect-error dynamic imports are never transpiled, but named exports only work when
// dynamically importing an esm file, and the utils file might be transpiled to cjs
send: imported.sendWithHttp ?? imported.default.sendWithHttp,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import type * as http from 'http';
import type * as https from 'https';
import { ExportResponse } from '../export-response';
import { HttpAgentFactory } from '../configuration/otlp-http-configuration';

export type sendWithHttp = (
params: HttpRequestParameters,
Expand All @@ -30,5 +31,5 @@ export interface HttpRequestParameters {
url: string;
headers: () => Record<string, string>;
compression: 'gzip' | 'none';
agentOptions: http.AgentOptions | https.AgentOptions;
agent: HttpAgentFactory;
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,3 @@ function readableFromUint8Array(buff: string | Uint8Array): Readable {

return readable;
}

export function createHttpAgent(
rawUrl: string,
agentOptions: http.AgentOptions | https.AgentOptions
) {
const parsedUrl = new URL(rawUrl);
const Agent = parsedUrl.protocol === 'http:' ? http.Agent : https.Agent;
return new Agent(agentOptions);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('mergeOtlpHttpConfigurationWithDefaults', function () {
compression: 'none',
concurrencyLimit: 2,
headers: () => ({ 'User-Agent': 'default-user-agent' }),
agentOptions: { keepAlive: true },
agent: () => null!,
};

describe('headers', function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import * as sinon from 'sinon';
import * as assert from 'assert';
import type * as https from 'https';
import { convertLegacyHttpOptions } from '../../../src/configuration/convert-legacy-node-http-options';
import { registerMockDiagLogger } from '../../common/test-utils';

Expand All @@ -40,7 +41,21 @@ describe('convertLegacyHttpOptions', function () {
);
});

it('should keep specific keepAlive', () => {
it('should keep agent factory as-is', function () {
// act
const factory = () => null!;
const options = convertLegacyHttpOptions(
{ httpAgentOptions: factory },
'SIGNAL',
'v1/signal',
{}
);

// assert
assert.strictEqual(options.agent, factory);
});

it('should keep specific keepAlive', async () => {
// act
const options = convertLegacyHttpOptions(
{
Expand All @@ -50,12 +65,13 @@ describe('convertLegacyHttpOptions', function () {
'v1/signal',
{}
);
const agent = (await options.agent('https:')) as https.Agent;

// assert
assert.ok(options.agentOptions.keepAlive);
assert.ok(agent.options.keepAlive);
});

it('should set keepAlive on AgentOptions when not explicitly set in AgentOptions but set in config', () => {
it('should set keepAlive on AgentOptions when not explicitly set in AgentOptions but set in config', async () => {
// act
const options = convertLegacyHttpOptions(
{
Expand All @@ -69,9 +85,10 @@ describe('convertLegacyHttpOptions', function () {
'v1/signal',
{}
);
const agent = (await options.agent('https:')) as https.Agent;

// assert
assert.ok(options.agentOptions.keepAlive);
assert.strictEqual(options.agentOptions.port, 1234);
assert.ok(agent.options.keepAlive);
assert.strictEqual(agent.options.port, 1234);
});
});
Loading