diff --git a/experimental/packages/exporter-logs-otlp-grpc/src/OTLPLogExporter.ts b/experimental/packages/exporter-logs-otlp-grpc/src/OTLPLogExporter.ts index 1172126cbd4..31f1daf95ea 100644 --- a/experimental/packages/exporter-logs-otlp-grpc/src/OTLPLogExporter.ts +++ b/experimental/packages/exporter-logs-otlp-grpc/src/OTLPLogExporter.ts @@ -15,22 +15,14 @@ */ import { LogRecordExporter, ReadableLogRecord } from '@opentelemetry/sdk-logs'; -import { baggageUtils, getEnv } from '@opentelemetry/core'; import { OTLPGRPCExporterConfigNode, OTLPGRPCExporterNodeBase, - validateAndNormalizeUrl, - DEFAULT_COLLECTOR_URL, } from '@opentelemetry/otlp-grpc-exporter-base'; import { IExportLogsServiceResponse, ProtobufLogsSerializer, } from '@opentelemetry/otlp-transformer'; -import { VERSION } from './version'; - -const USER_AGENT = { - 'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`, -}; /** * OTLP Logs Exporter for Node @@ -43,34 +35,12 @@ export class OTLPLogExporter implements LogRecordExporter { constructor(config: OTLPGRPCExporterConfigNode = {}) { - const signalSpecificMetadata = { - ...USER_AGENT, - ...baggageUtils.parseKeyPairsIntoRecord( - getEnv().OTEL_EXPORTER_OTLP_LOGS_HEADERS - ), - }; super( config, - signalSpecificMetadata, + ProtobufLogsSerializer, 'LogsExportService', '/opentelemetry.proto.collector.logs.v1.LogsService/Export', - ProtobufLogsSerializer - ); - } - - getDefaultUrl(config: OTLPGRPCExporterConfigNode) { - return validateAndNormalizeUrl(this.getUrlFromConfig(config)); - } - - getUrlFromConfig(config: OTLPGRPCExporterConfigNode): string { - if (typeof config.url === 'string') { - return config.url; - } - - return ( - getEnv().OTEL_EXPORTER_OTLP_LOGS_ENDPOINT || - getEnv().OTEL_EXPORTER_OTLP_ENDPOINT || - DEFAULT_COLLECTOR_URL + 'LOGS' ); } } diff --git a/experimental/packages/exporter-logs-otlp-grpc/test/OTLPLogExporter.test.ts b/experimental/packages/exporter-logs-otlp-grpc/test/OTLPLogExporter.test.ts index a0d3c7eb661..996b5180acf 100644 --- a/experimental/packages/exporter-logs-otlp-grpc/test/OTLPLogExporter.test.ts +++ b/experimental/packages/exporter-logs-otlp-grpc/test/OTLPLogExporter.test.ts @@ -37,7 +37,6 @@ import { IExportLogsServiceRequest, IResourceLogs, } from '@opentelemetry/otlp-transformer'; -import { VERSION } from '../src/version'; const logsServiceProtoPath = 'opentelemetry/proto/collector/logs/v1/logs_service.proto'; @@ -294,104 +293,9 @@ const testCollectorExporter = (params: TestParams) => { }, 500); }); }); - describe('Logs Exporter with compression', () => { - const envSource = process.env; - it('should return gzip compression algorithm on exporter', () => { - const credentials = useTLS - ? grpc.credentials.createSsl( - fs.readFileSync('./test/certs/ca.crt'), - fs.readFileSync('./test/certs/client.key'), - fs.readFileSync('./test/certs/client.crt') - ) - : grpc.credentials.createInsecure(); - - envSource.OTEL_EXPORTER_OTLP_COMPRESSION = 'gzip'; - collectorExporter = new OTLPLogExporter({ - url: address, - credentials, - metadata: metadata, - }); - assert.strictEqual( - collectorExporter.compression, - CompressionAlgorithm.GZIP - ); - delete envSource.OTEL_EXPORTER_OTLP_COMPRESSION; - }); - }); }); }; -describe('OTLPLogExporter - node (getDefaultUrl)', () => { - it('should default to localhost', done => { - const collectorExporter = new OTLPLogExporter({}); - setTimeout(() => { - assert.strictEqual(collectorExporter['url'], 'localhost:4317'); - done(); - }); - }); - it('should keep the URL if included', done => { - const url = 'http://foo.bar.com'; - const collectorExporter = new OTLPLogExporter({ url }); - setTimeout(() => { - assert.strictEqual(collectorExporter['url'], 'foo.bar.com'); - done(); - }); - }); -}); - -describe('when configuring via environment', () => { - const envSource = process.env; - - afterEach(function () { - // Ensure we don't pollute other tests if assertions fail - delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT; - delete envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT; - delete envSource.OTEL_EXPORTER_OTLP_HEADERS; - delete envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS; - sinon.restore(); - }); - - it('should use url defined in env', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; - const collectorExporter = new OTLPLogExporter(); - assert.strictEqual(collectorExporter.url, 'foo.bar'); - }); - it('should override global exporter url with signal url defined in env', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; - envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.logs'; - const collectorExporter = new OTLPLogExporter(); - assert.strictEqual(collectorExporter.url, 'foo.logs'); - }); - it('should include user-agent header by default', () => { - const collectorExporter = new OTLPLogExporter(); - const actualMetadata = - collectorExporter['_transport']['_parameters'].metadata(); - assert.deepStrictEqual(actualMetadata.get('User-Agent'), [ - `OTel-OTLP-Exporter-JavaScript/${VERSION}`, - ]); - }); - it('should use headers defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar'; - const collectorExporter = new OTLPLogExporter(); - const actualMetadata = - collectorExporter['_transport']['_parameters'].metadata(); - assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']); - }); - it('should not override hard-coded headers config with headers defined via env', () => { - const metadata = new grpc.Metadata(); - metadata.set('foo', 'bar'); - metadata.set('goo', 'lol'); - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=jar,bar=foo'; - envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = 'foo=boo'; - const collectorExporter = new OTLPLogExporter({ metadata }); - const actualMetadata = - collectorExporter['_transport']['_parameters'].metadata(); - assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']); - assert.deepStrictEqual(actualMetadata.get('goo'), ['lol']); - assert.deepStrictEqual(actualMetadata.get('bar'), ['foo']); - }); -}); - testCollectorExporter({ useTLS: true }); testCollectorExporter({ useTLS: false }); testCollectorExporter({ metadata }); diff --git a/experimental/packages/exporter-logs-otlp-http/src/platform/browser/OTLPLogExporter.ts b/experimental/packages/exporter-logs-otlp-http/src/platform/browser/OTLPLogExporter.ts index 4a9ce917e25..dbe4d0c2031 100644 --- a/experimental/packages/exporter-logs-otlp-http/src/platform/browser/OTLPLogExporter.ts +++ b/experimental/packages/exporter-logs-otlp-http/src/platform/browser/OTLPLogExporter.ts @@ -38,7 +38,10 @@ export class OTLPLogExporter ...config, }, JsonLogsSerializer, - 'application/json' + { + 'Content-Type': 'application/json', + }, + 'v1/logs' ); } diff --git a/experimental/packages/exporter-logs-otlp-http/src/platform/node/OTLPLogExporter.ts b/experimental/packages/exporter-logs-otlp-http/src/platform/node/OTLPLogExporter.ts index 1837993e911..5851a653dcb 100644 --- a/experimental/packages/exporter-logs-otlp-http/src/platform/node/OTLPLogExporter.ts +++ b/experimental/packages/exporter-logs-otlp-http/src/platform/node/OTLPLogExporter.ts @@ -20,14 +20,11 @@ import type { } from '@opentelemetry/sdk-logs'; import type { OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base'; import type { IExportLogsServiceResponse } from '@opentelemetry/otlp-transformer'; -import { getEnv, baggageUtils } from '@opentelemetry/core'; import { OTLPExporterNodeBase, - parseHeaders, } from '@opentelemetry/otlp-exporter-base'; import { JsonLogsSerializer } from '@opentelemetry/otlp-transformer'; -import { getDefaultUrl } from '../config'; import { VERSION } from '../../version'; const USER_AGENT = { @@ -42,25 +39,17 @@ export class OTLPLogExporter implements LogRecordExporter { constructor(config: OTLPExporterNodeConfigBase = {}) { - // load OTEL_EXPORTER_OTLP_LOGS_TIMEOUT env super( { - timeoutMillis: getEnv().OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, ...config, }, JsonLogsSerializer, { - ...baggageUtils.parseKeyPairsIntoRecord( - getEnv().OTEL_EXPORTER_OTLP_LOGS_HEADERS - ), - ...parseHeaders(config?.headers), ...USER_AGENT, 'Content-Type': 'application/json', - } + }, + 'LOGS', + 'v1/logs' ); } - - getDefaultUrl(config: OTLPExporterNodeConfigBase): string { - return getDefaultUrl(config); - } } diff --git a/experimental/packages/exporter-logs-otlp-http/test/browser/OTLPLogExporter.test.ts b/experimental/packages/exporter-logs-otlp-http/test/browser/OTLPLogExporter.test.ts index 362a3d65d23..69f93c6ea9f 100644 --- a/experimental/packages/exporter-logs-otlp-http/test/browser/OTLPLogExporter.test.ts +++ b/experimental/packages/exporter-logs-otlp-http/test/browser/OTLPLogExporter.test.ts @@ -16,7 +16,6 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import * as Config from '../../src/platform/config'; import { OTLPLogExporter } from '../../src/platform/browser'; import { OTLPExporterConfigBase } from '@opentelemetry/otlp-exporter-base'; import { ReadableLogRecord } from '@opentelemetry/sdk-logs'; @@ -38,16 +37,6 @@ describe('OTLPLogExporter', () => { }); }); - describe('getDefaultUrl', () => { - it('should call getDefaultUrl', () => { - const getDefaultUrl = sinon.stub(Config, 'getDefaultUrl'); - const exporter = new OTLPLogExporter(); - exporter.getDefaultUrl({}); - // this callCount is 2, because new OTLPLogExporter also call it - assert.strictEqual(getDefaultUrl.callCount, 2); - }); - }); - describe('export - common', () => { let spySend: any; beforeEach(() => { diff --git a/experimental/packages/exporter-logs-otlp-http/test/node/OTLPLogExporter.test.ts b/experimental/packages/exporter-logs-otlp-http/test/node/OTLPLogExporter.test.ts index 8e6b076ed60..726be702fa3 100644 --- a/experimental/packages/exporter-logs-otlp-http/test/node/OTLPLogExporter.test.ts +++ b/experimental/packages/exporter-logs-otlp-http/test/node/OTLPLogExporter.test.ts @@ -18,7 +18,6 @@ import { diag } from '@opentelemetry/api'; import * as assert from 'assert'; import * as http from 'http'; import * as sinon from 'sinon'; -import * as Config from '../../src/platform/config'; import { OTLPLogExporter } from '../../src/platform/node'; import { OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base'; @@ -103,15 +102,6 @@ describe('OTLPLogExporter', () => { delete envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS; }); - it('should use timeout defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = ''; - envSource.OTEL_EXPORTER_OTLP_LOGS_TIMEOUT = 30000; - const exporter = new OTLPLogExporter(); - assert.strictEqual(exporter.timeoutMillis, 30000); - delete envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS; - delete envSource.OTEL_EXPORTER_OTLP_LOGS_TIMEOUT; - }); - it('should override headers defined via env with headers defined in constructor', () => { envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; const exporter = new OTLPLogExporter({ @@ -131,23 +121,12 @@ describe('OTLPLogExporter', () => { }); }); - describe('getDefaultUrl', () => { - it('should call getDefaultUrl', () => { - const getDefaultUrl = sinon.stub(Config, 'getDefaultUrl'); - const exporter = new OTLPLogExporter(); - exporter.getDefaultUrl({}); - // this callCount is 2, because new OTLPLogExporter also call it - assert.strictEqual(getDefaultUrl.callCount, 2); - }); - }); - describe('export', () => { beforeEach(() => { collectorExporterConfig = { headers: { foo: 'bar', }, - hostname: 'foo', url: 'http://foo.bar.com', keepAlive: true, httpAgentOptions: { keepAliveMsecs: 2000 }, diff --git a/experimental/packages/exporter-logs-otlp-proto/src/platform/browser/OTLPLogExporter.ts b/experimental/packages/exporter-logs-otlp-proto/src/platform/browser/OTLPLogExporter.ts index ede7ae6c156..470a40f5fb5 100644 --- a/experimental/packages/exporter-logs-otlp-proto/src/platform/browser/OTLPLogExporter.ts +++ b/experimental/packages/exporter-logs-otlp-proto/src/platform/browser/OTLPLogExporter.ts @@ -25,9 +25,6 @@ import { import { ReadableLogRecord, LogRecordExporter } from '@opentelemetry/sdk-logs'; -const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/logs'; -const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; - /** * Collector Trace Exporter for Web */ @@ -36,14 +33,11 @@ export class OTLPLogExporter implements LogRecordExporter { constructor(config: OTLPExporterConfigBase = {}) { - super(config, ProtobufLogsSerializer, 'application/x-protobuf'); - } - - getDefaultUrl(config: OTLPExporterConfigBase): string { - if (typeof config.url === 'string') { - return config.url; - } - - return DEFAULT_COLLECTOR_URL; + super( + config, + ProtobufLogsSerializer, + { 'Content-Type': 'application/x-protobuf' }, + 'v1/logs' + ); } } diff --git a/experimental/packages/exporter-logs-otlp-proto/src/platform/node/OTLPLogExporter.ts b/experimental/packages/exporter-logs-otlp-proto/src/platform/node/OTLPLogExporter.ts index 893a06b4ab1..828a11cbc6d 100644 --- a/experimental/packages/exporter-logs-otlp-proto/src/platform/node/OTLPLogExporter.ts +++ b/experimental/packages/exporter-logs-otlp-proto/src/platform/node/OTLPLogExporter.ts @@ -14,13 +14,9 @@ * limitations under the License. */ -import { getEnv, baggageUtils } from '@opentelemetry/core'; import { OTLPExporterConfigBase, - appendResourcePathToUrl, - appendRootPathToUrlIfNeeded, OTLPExporterNodeBase, - parseHeaders, } from '@opentelemetry/otlp-exporter-base'; import { IExportLogsServiceResponse, @@ -34,9 +30,6 @@ const USER_AGENT = { 'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`, }; -const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/logs'; -const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; - /** * Collector Trace Exporter for Node */ @@ -45,33 +38,15 @@ export class OTLPLogExporter implements LogRecordExporter { constructor(config: OTLPExporterConfigBase = {}) { - super(config, ProtobufLogsSerializer, { - ...baggageUtils.parseKeyPairsIntoRecord( - getEnv().OTEL_EXPORTER_OTLP_LOGS_HEADERS - ), - ...parseHeaders(config?.headers), - ...USER_AGENT, - 'Content-Type': 'application/x-protobuf', - }); - } - - getDefaultUrl(config: OTLPExporterConfigBase): string { - if (typeof config.url === 'string') { - return config.url; - } - - const env = getEnv(); - if (env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT.length > 0) { - return appendRootPathToUrlIfNeeded(env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT); - } - - if (env.OTEL_EXPORTER_OTLP_ENDPOINT.length > 0) { - return appendResourcePathToUrl( - env.OTEL_EXPORTER_OTLP_ENDPOINT, - DEFAULT_COLLECTOR_RESOURCE_PATH - ); - } - - return DEFAULT_COLLECTOR_URL; + super( + config, + ProtobufLogsSerializer, + { + ...USER_AGENT, + 'Content-Type': 'application/x-protobuf', + }, + 'LOGS', + 'v1/logs' + ); } } diff --git a/experimental/packages/exporter-logs-otlp-proto/test/browser/OTLPLogExporter.test.ts b/experimental/packages/exporter-logs-otlp-proto/test/browser/OTLPLogExporter.test.ts index 6a76ed24a09..29ed48ff4a8 100644 --- a/experimental/packages/exporter-logs-otlp-proto/test/browser/OTLPLogExporter.test.ts +++ b/experimental/packages/exporter-logs-otlp-proto/test/browser/OTLPLogExporter.test.ts @@ -21,9 +21,7 @@ import { OTLPLogExporter } from '../../src/platform/browser/index'; describe('OTLPLogExporter - web', () => { let collectorLogsExporter: OTLPLogExporter; describe('constructor', () => { - let onInitSpy: any; beforeEach(() => { - onInitSpy = sinon.stub(OTLPLogExporter.prototype, 'onInit'); const collectorExporterConfig = { hostname: 'foo', url: 'http://foo.bar.com', @@ -36,15 +34,5 @@ describe('OTLPLogExporter - web', () => { it('should create an instance', () => { assert.ok(typeof collectorLogsExporter !== 'undefined'); }); - it('should call onInit', () => { - assert.strictEqual(onInitSpy.callCount, 1); - }); - it('should set hostname', () => { - assert.strictEqual(collectorLogsExporter.hostname, 'foo'); - }); - - it('should set url', () => { - assert.strictEqual(collectorLogsExporter.url, 'http://foo.bar.com'); - }); }); }); diff --git a/experimental/packages/exporter-logs-otlp-proto/test/node/OTLPLogExporter.test.ts b/experimental/packages/exporter-logs-otlp-proto/test/node/OTLPLogExporter.test.ts index 9cf3961fe5d..475b00ad741 100644 --- a/experimental/packages/exporter-logs-otlp-proto/test/node/OTLPLogExporter.test.ts +++ b/experimental/packages/exporter-logs-otlp-proto/test/node/OTLPLogExporter.test.ts @@ -35,7 +35,6 @@ import { } from '@opentelemetry/otlp-exporter-base'; import { IExportLogsServiceRequest } from '@opentelemetry/otlp-transformer'; import { ReadableLogRecord } from '@opentelemetry/sdk-logs'; -import { VERSION } from '../../src/version'; import { Root } from 'protobufjs'; import * as path from 'path'; @@ -67,150 +66,12 @@ describe('OTLPLogExporter - node with proto over http', () => { sinon.restore(); }); - describe('when configuring via environment', () => { - const envSource = process.env; - it('should use url defined in env that ends with root path and append version and signal path', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; - const collectorExporter = new OTLPLogExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}v1/logs` - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should use url defined in env without checking if path is already present', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/v1/logs'; - const collectorExporter = new OTLPLogExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/logs` - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should use url defined in env and append version and signal', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; - const collectorExporter = new OTLPLogExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/logs` - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should override global exporter url with signal url defined in env', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; - envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.logs/'; - const collectorExporter = new OTLPLogExporter(); - assert.strictEqual( - collectorExporter.url, - envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = ''; - }); - it('should override url defined in env with url defined in constructor', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; - const constructorDefinedEndpoint = 'http://constructor/v1/logs'; - const collectorExporter = new OTLPLogExporter({ - url: constructorDefinedEndpoint, - }); - assert.strictEqual(collectorExporter.url, constructorDefinedEndpoint); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should add root path when signal url defined in env contains no path and no root path', () => { - envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.bar'; - const collectorExporter = new OTLPLogExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}/` - ); - envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = ''; - }); - it('should not add root path when signal url defined in env contains root path but no path', () => { - envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.bar/'; - const collectorExporter = new OTLPLogExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}` - ); - envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = ''; - }); - it('should not add root path when signal url defined in env contains path', () => { - envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.bar/v1/logs'; - const collectorExporter = new OTLPLogExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}` - ); - envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = ''; - }); - it('should not add root path when signal url defined in env contains path and ends in /', () => { - envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.bar/v1/logs/'; - const collectorExporter = new OTLPLogExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}` - ); - envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = ''; - }); - it('should include user-agent header by default', () => { - const exporter = new OTLPLogExporter(); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers'][ - 'User-Agent' - ], - `OTel-OTLP-Exporter-JavaScript/${VERSION}` - ); - }); - it('should use headers defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = 'foo=bar'; - const exporter = new OTLPLogExporter(); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers']['foo'], - 'bar' - ); - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - it('should override global headers config with signal headers defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; - envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = 'foo=boo'; - const exporter = new OTLPLogExporter(); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers']['foo'], - 'boo' - ); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers']['bar'], - 'foo' - ); - envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = ''; - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - it('should override headers defined via env with headers defined in constructor', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; - const exporter = new OTLPLogExporter({ - headers: { - foo: 'constructor', - }, - }); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers']['foo'], - 'constructor' - ); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers']['bar'], - 'foo' - ); - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - }); - describe('export', () => { beforeEach(() => { collectorExporterConfig = { headers: { foo: 'bar', }, - hostname: 'foo', url: 'http://foo.bar.com', keepAlive: true, httpAgentOptions: { keepAliveMsecs: 2000 }, @@ -346,7 +207,6 @@ describe('OTLPLogExporter - node with proto over http', () => { headers: { foo: 'bar', }, - hostname: 'foo', url: 'http://foo.bar.com', keepAlive: true, compression: CompressionAlgorithm.GZIP, diff --git a/experimental/packages/exporter-trace-otlp-grpc/src/OTLPTraceExporter.ts b/experimental/packages/exporter-trace-otlp-grpc/src/OTLPTraceExporter.ts index cffa9b07498..e785475a1d2 100644 --- a/experimental/packages/exporter-trace-otlp-grpc/src/OTLPTraceExporter.ts +++ b/experimental/packages/exporter-trace-otlp-grpc/src/OTLPTraceExporter.ts @@ -15,22 +15,14 @@ */ import { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'; -import { baggageUtils, getEnv } from '@opentelemetry/core'; import { OTLPGRPCExporterConfigNode, OTLPGRPCExporterNodeBase, - validateAndNormalizeUrl, - DEFAULT_COLLECTOR_URL, } from '@opentelemetry/otlp-grpc-exporter-base'; import { IExportTraceServiceResponse, ProtobufTraceSerializer, } from '@opentelemetry/otlp-transformer'; -import { VERSION } from './version'; - -const USER_AGENT = { - 'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`, -}; /** * OTLP Trace Exporter for Node @@ -40,34 +32,12 @@ export class OTLPTraceExporter implements SpanExporter { constructor(config: OTLPGRPCExporterConfigNode = {}) { - const signalSpecificMetadata = { - ...USER_AGENT, - ...baggageUtils.parseKeyPairsIntoRecord( - getEnv().OTEL_EXPORTER_OTLP_TRACES_HEADERS - ), - }; super( config, - signalSpecificMetadata, + ProtobufTraceSerializer, 'TraceExportService', '/opentelemetry.proto.collector.trace.v1.TraceService/Export', - ProtobufTraceSerializer - ); - } - - getDefaultUrl(config: OTLPGRPCExporterConfigNode) { - return validateAndNormalizeUrl(this.getUrlFromConfig(config)); - } - - getUrlFromConfig(config: OTLPGRPCExporterConfigNode): string { - if (typeof config.url === 'string') { - return config.url; - } - - return ( - getEnv().OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || - getEnv().OTEL_EXPORTER_OTLP_ENDPOINT || - DEFAULT_COLLECTOR_URL + 'TRACES' ); } } diff --git a/experimental/packages/exporter-trace-otlp-grpc/test/OTLPTraceExporter.test.ts b/experimental/packages/exporter-trace-otlp-grpc/test/OTLPTraceExporter.test.ts index 60f250d7f60..801f85a36b9 100644 --- a/experimental/packages/exporter-trace-otlp-grpc/test/OTLPTraceExporter.test.ts +++ b/experimental/packages/exporter-trace-otlp-grpc/test/OTLPTraceExporter.test.ts @@ -28,7 +28,6 @@ import * as grpc from '@grpc/grpc-js'; import * as path from 'path'; import * as sinon from 'sinon'; import { OTLPTraceExporter } from '../src'; -import { VERSION } from '../src/version'; import { ensureExportedSpanIsCorrect, @@ -299,104 +298,9 @@ const testCollectorExporter = (params: TestParams) => { }, 500); }); }); - describe('Trace Exporter with compression', () => { - const envSource = process.env; - it('should return gzip compression algorithm on exporter', () => { - const credentials = useTLS - ? grpc.credentials.createSsl( - fs.readFileSync('./test/certs/ca.crt'), - fs.readFileSync('./test/certs/client.key'), - fs.readFileSync('./test/certs/client.crt') - ) - : grpc.credentials.createInsecure(); - - envSource.OTEL_EXPORTER_OTLP_COMPRESSION = 'gzip'; - collectorExporter = new OTLPTraceExporter({ - url: address, - credentials, - metadata: metadata, - }); - assert.strictEqual( - collectorExporter.compression, - CompressionAlgorithm.GZIP - ); - delete envSource.OTEL_EXPORTER_OTLP_COMPRESSION; - }); - }); }); }; -describe('OTLPTraceExporter - node (getDefaultUrl)', () => { - it('should default to localhost', done => { - const collectorExporter = new OTLPTraceExporter({}); - setTimeout(() => { - assert.strictEqual(collectorExporter['url'], 'localhost:4317'); - done(); - }); - }); - it('should keep the URL if included', done => { - const url = 'http://foo.bar.com'; - const collectorExporter = new OTLPTraceExporter({ url }); - setTimeout(() => { - assert.strictEqual(collectorExporter['url'], 'foo.bar.com'); - done(); - }); - }); -}); - -describe('when configuring via environment', () => { - const envSource = process.env; - - afterEach(function () { - // Ensure we don't pollute other tests if assertions fail - delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT; - delete envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT; - delete envSource.OTEL_EXPORTER_OTLP_HEADERS; - delete envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS; - sinon.restore(); - }); - - it('should use url defined in env', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual(collectorExporter.url, 'foo.bar'); - }); - it('should override global exporter url with signal url defined in env', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://foo.traces'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual(collectorExporter.url, 'foo.traces'); - }); - it('should use headers defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar'; - const collectorExporter = new OTLPTraceExporter(); - const actualMetadata = - collectorExporter['_transport']['_parameters'].metadata(); - assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']); - }); - it('should include user agent in header', () => { - const collectorExporter = new OTLPTraceExporter(); - const actualMetadata = - collectorExporter['_transport']['_parameters'].metadata(); - assert.deepStrictEqual(actualMetadata.get('User-Agent'), [ - `OTel-OTLP-Exporter-JavaScript/${VERSION}`, - ]); - }); - it('should not override hard-coded headers config with headers defined via env', () => { - const metadata = new grpc.Metadata(); - metadata.set('foo', 'bar'); - metadata.set('goo', 'lol'); - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=jar,bar=foo'; - envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = 'foo=boo'; - const collectorExporter = new OTLPTraceExporter({ metadata }); - const actualMetadata = - collectorExporter['_transport']['_parameters'].metadata(); - assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']); - assert.deepStrictEqual(actualMetadata.get('goo'), ['lol']); - assert.deepStrictEqual(actualMetadata.get('bar'), ['foo']); - }); -}); - testCollectorExporter({ useTLS: true }); testCollectorExporter({ useTLS: false }); testCollectorExporter({ metadata }); diff --git a/experimental/packages/exporter-trace-otlp-http/src/platform/browser/OTLPTraceExporter.ts b/experimental/packages/exporter-trace-otlp-http/src/platform/browser/OTLPTraceExporter.ts index c9bc8e92428..2e03ef845e0 100644 --- a/experimental/packages/exporter-trace-otlp-http/src/platform/browser/OTLPTraceExporter.ts +++ b/experimental/packages/exporter-trace-otlp-http/src/platform/browser/OTLPTraceExporter.ts @@ -25,7 +25,6 @@ import { } from '@opentelemetry/otlp-transformer'; const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/traces'; -const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; /** * Collector Trace Exporter for Web @@ -35,14 +34,11 @@ export class OTLPTraceExporter implements SpanExporter { constructor(config: OTLPExporterConfigBase = {}) { - super(config, JsonTraceSerializer, `application/json`); - } - - getDefaultUrl(config: OTLPExporterConfigBase): string { - if (typeof config.url === 'string') { - return config.url; - } - - return DEFAULT_COLLECTOR_URL; + super( + config, + JsonTraceSerializer, + { 'Content-Type': 'application/json' }, + DEFAULT_COLLECTOR_RESOURCE_PATH + ); } } diff --git a/experimental/packages/exporter-trace-otlp-http/src/platform/node/OTLPTraceExporter.ts b/experimental/packages/exporter-trace-otlp-http/src/platform/node/OTLPTraceExporter.ts index aa91b8c237a..8d41df8f0db 100644 --- a/experimental/packages/exporter-trace-otlp-http/src/platform/node/OTLPTraceExporter.ts +++ b/experimental/packages/exporter-trace-otlp-http/src/platform/node/OTLPTraceExporter.ts @@ -15,22 +15,12 @@ */ import { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'; -import { getEnv, baggageUtils } from '@opentelemetry/core'; -import { - OTLPExporterNodeBase, - parseHeaders, -} from '@opentelemetry/otlp-exporter-base'; -import { - OTLPExporterNodeConfigBase, - appendResourcePathToUrl, - appendRootPathToUrlIfNeeded, -} from '@opentelemetry/otlp-exporter-base'; +import { OTLPExporterNodeBase } from '@opentelemetry/otlp-exporter-base'; +import { OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base'; import { IExportTraceServiceResponse } from '@opentelemetry/otlp-transformer'; import { VERSION } from '../../version'; import { JsonTraceSerializer } from '@opentelemetry/otlp-transformer'; -const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/traces'; -const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; const USER_AGENT = { 'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`, }; @@ -43,35 +33,15 @@ export class OTLPTraceExporter implements SpanExporter { constructor(config: OTLPExporterNodeConfigBase = {}) { - super(config, JsonTraceSerializer, { - ...baggageUtils.parseKeyPairsIntoRecord( - getEnv().OTEL_EXPORTER_OTLP_TRACES_HEADERS - ), - ...parseHeaders(config?.headers), - ...USER_AGENT, - 'Content-Type': 'application/json', - }); - } - - getDefaultUrl(config: OTLPExporterNodeConfigBase): string { - if (typeof config.url === 'string') { - return config.url; - } - - const env = getEnv(); - if (env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT.length > 0) { - return appendRootPathToUrlIfNeeded( - env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT - ); - } - - if (env.OTEL_EXPORTER_OTLP_ENDPOINT.length > 0) { - return appendResourcePathToUrl( - env.OTEL_EXPORTER_OTLP_ENDPOINT, - DEFAULT_COLLECTOR_RESOURCE_PATH - ); - } - - return DEFAULT_COLLECTOR_URL; + super( + config, + JsonTraceSerializer, + { + ...USER_AGENT, + 'Content-Type': 'application/json', + }, + 'TRACES', + 'v1/traces' + ); } } diff --git a/experimental/packages/exporter-trace-otlp-http/test/browser/CollectorTraceExporter.test.ts b/experimental/packages/exporter-trace-otlp-http/test/browser/CollectorTraceExporter.test.ts index 2e234fb2fad..f1aed425e67 100644 --- a/experimental/packages/exporter-trace-otlp-http/test/browser/CollectorTraceExporter.test.ts +++ b/experimental/packages/exporter-trace-otlp-http/test/browser/CollectorTraceExporter.test.ts @@ -54,12 +54,8 @@ describe('OTLPTraceExporter - web', () => { }); describe('constructor', () => { - let onInitSpy: any; - beforeEach(() => { - onInitSpy = sinon.stub(OTLPTraceExporter.prototype, 'onInit'); collectorExporterConfig = { - hostname: 'foo', url: 'http://foo.bar.com', }; collectorTraceExporter = new OTLPTraceExporter(collectorExporterConfig); @@ -68,26 +64,11 @@ describe('OTLPTraceExporter - web', () => { it('should create an instance', () => { assert.ok(typeof collectorTraceExporter !== 'undefined'); }); - - it('should call onInit', () => { - assert.strictEqual(onInitSpy.callCount, 1); - }); - - describe('when config contains certain params', () => { - it('should set hostname', () => { - assert.strictEqual(collectorTraceExporter.hostname, 'foo'); - }); - - it('should set url', () => { - assert.strictEqual(collectorTraceExporter.url, 'http://foo.bar.com'); - }); - }); }); describe('export', () => { beforeEach(() => { collectorExporterConfig = { - hostname: 'foo', url: 'http://foo.bar.com', }; }); @@ -202,29 +183,36 @@ describe('OTLPTraceExporter - web', () => { collectorTraceExporter.export(spans, () => {}); queueMicrotask(async () => { - const request = server.requests[0]; - assert.strictEqual(request.method, 'POST'); - assert.strictEqual(request.url, 'http://foo.bar.com'); - - const body = request.requestBody as Blob; - const decoder = new TextDecoder(); - const json = JSON.parse( - decoder.decode(await body.arrayBuffer()) - ) as IExportTraceServiceRequest; - const span1 = json.resourceSpans?.[0].scopeSpans?.[0].spans?.[0]; + try { + const request = server.requests[0]; + assert.strictEqual(request.method, 'POST'); + assert.strictEqual(request.url, 'http://foo.bar.com'); + + const body = request.requestBody as Blob; + const decoder = new TextDecoder(); + const json = JSON.parse( + decoder.decode(await body.arrayBuffer()) + ) as IExportTraceServiceRequest; + const span1 = json.resourceSpans?.[0].scopeSpans?.[0].spans?.[0]; - assert.ok(typeof span1 !== 'undefined', "span doesn't exist"); - ensureSpanIsCorrect(span1); + assert.ok(typeof span1 !== 'undefined', "span doesn't exist"); + ensureSpanIsCorrect(span1); - const resource = json.resourceSpans?.[0].resource; - assert.ok(typeof resource !== 'undefined', "resource doesn't exist"); - ensureWebResourceIsCorrect(resource); + const resource = json.resourceSpans?.[0].resource; + assert.ok( + typeof resource !== 'undefined', + "resource doesn't exist" + ); + ensureWebResourceIsCorrect(resource); - assert.strictEqual(stubBeacon.callCount, 0); - ensureExportTraceServiceRequestIsSet(json); + assert.strictEqual(stubBeacon.callCount, 0); + ensureExportTraceServiceRequestIsSet(json); - clock.restore(); - done(); + clock.restore(); + done(); + } catch (e) { + done(e); + } }); }); @@ -496,27 +484,6 @@ describe('OTLPTraceExporter - web', () => { }); }); -describe('OTLPTraceExporter - browser (getDefaultUrl)', () => { - it('should default to v1/trace', done => { - const collectorExporter = new OTLPTraceExporter({}); - setTimeout(() => { - assert.strictEqual( - collectorExporter['url'], - 'http://localhost:4318/v1/traces' - ); - done(); - }); - }); - it('should keep the URL if included', done => { - const url = 'http://foo.bar.com'; - const collectorExporter = new OTLPTraceExporter({ url }); - setTimeout(() => { - assert.strictEqual(collectorExporter['url'], url); - done(); - }); - }); -}); - describe('export with retry - real http request destroyed', () => { let server: any; let collectorTraceExporter: OTLPTraceExporter; diff --git a/experimental/packages/exporter-trace-otlp-http/test/node/CollectorTraceExporter.test.ts b/experimental/packages/exporter-trace-otlp-http/test/node/CollectorTraceExporter.test.ts index 56f72c08d70..b36be295346 100644 --- a/experimental/packages/exporter-trace-otlp-http/test/node/CollectorTraceExporter.test.ts +++ b/experimental/packages/exporter-trace-otlp-http/test/node/CollectorTraceExporter.test.ts @@ -35,7 +35,6 @@ import { } from '../traceHelper'; import { MockedResponse } from './nodeHelpers'; import { IExportTraceServiceRequest } from '@opentelemetry/otlp-transformer'; -import { VERSION } from '../../src/version'; let fakeRequest: PassThrough; @@ -80,144 +79,6 @@ describe('OTLPTraceExporter - node with json over http', () => { }); }); - describe('when configuring via environment', () => { - const envSource = process.env; - it('should use url defined in env that ends with root path and append version and signal path', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}v1/traces` - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should use url defined in env without checking if path is already present', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/v1/traces'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces` - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should use url defined in env and append version and signal', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces` - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should override global exporter url with signal url defined in env', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://foo.traces/'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = ''; - }); - it('should override url defined in env with url defined in constructor', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://foo.bar'; - const constructorDefinedEndpoint = 'http://constructor/v1/traces'; - const collectorExporter = new OTLPTraceExporter({ - url: constructorDefinedEndpoint, - }); - assert.strictEqual(collectorExporter.url, constructorDefinedEndpoint); - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = ''; - }); - it('should add root path when signal url defined in env contains no path and no root path', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://foo.bar'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT}/` - ); - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = ''; - }); - it('should not add root path when signal url defined in env contains root path but no path', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://foo.bar/'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT}` - ); - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = ''; - }); - it('should not add root path when signal url defined in env contains path', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://foo.bar/v1/traces'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT}` - ); - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = ''; - }); - it('should not add root path when signal url defined in env contains path and ends in /', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = - 'http://foo.bar/v1/traces/'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT}` - ); - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = ''; - }); - it('should use headers defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar'; - const exporter = new OTLPTraceExporter(); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers']['foo'], - 'bar' - ); - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - it('should include user agent in header', () => { - const exporter = new OTLPTraceExporter(); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers'][ - 'User-Agent' - ], - `OTel-OTLP-Exporter-JavaScript/${VERSION}` - ); - }); - it('should override global headers config with signal headers defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; - envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = 'foo=boo'; - const exporter = new OTLPTraceExporter(); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers']['foo'], - 'boo' - ); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers']['bar'], - 'foo' - ); - envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = ''; - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - it('should override headers defined via env with headers defined in constructor', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; - const exporter = new OTLPTraceExporter({ - headers: { - foo: 'constructor', - }, - }); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers']['foo'], - 'constructor' - ); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers']['bar'], - 'foo' - ); - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - }); - describe('export', () => { beforeEach(() => { stubRequest = sinon.stub(http, 'request').returns(fakeRequest as any); @@ -225,7 +86,6 @@ describe('OTLPTraceExporter - node with json over http', () => { headers: { foo: 'bar', }, - hostname: 'foo', url: 'http://foo.bar.com', keepAlive: true, httpAgentOptions: { keepAliveMsecs: 2000 }, @@ -439,7 +299,6 @@ describe('OTLPTraceExporter - node with json over http', () => { headers: { foo: 'bar', }, - hostname: 'foo', url: 'http://foo.bar.com', keepAlive: true, compression: CompressionAlgorithm.GZIP, @@ -481,27 +340,6 @@ describe('OTLPTraceExporter - node with json over http', () => { }); }); - describe('OTLPTraceExporter - node (getDefaultUrl)', () => { - it('should default to localhost', done => { - const collectorExporter = new OTLPTraceExporter(); - setTimeout(() => { - assert.strictEqual( - collectorExporter['url'], - 'http://localhost:4318/v1/traces' - ); - done(); - }); - }); - - it('should keep the URL if included', done => { - const url = 'http://foo.bar.com'; - const collectorExporter = new OTLPTraceExporter({ url }); - setTimeout(() => { - assert.strictEqual(collectorExporter['url'], url); - done(); - }); - }); - }); describe('export - with timeout', () => { beforeEach(() => { fakeRequest = new Stream.PassThrough(); @@ -516,7 +354,6 @@ describe('OTLPTraceExporter - node with json over http', () => { headers: { foo: 'bar', }, - hostname: 'foo', url: 'http://foo.bar.com', keepAlive: true, httpAgentOptions: { keepAliveMsecs: 2000 }, diff --git a/experimental/packages/exporter-trace-otlp-proto/src/platform/browser/OTLPTraceExporter.ts b/experimental/packages/exporter-trace-otlp-proto/src/platform/browser/OTLPTraceExporter.ts index 82d4bc389df..85d208b741e 100644 --- a/experimental/packages/exporter-trace-otlp-proto/src/platform/browser/OTLPTraceExporter.ts +++ b/experimental/packages/exporter-trace-otlp-proto/src/platform/browser/OTLPTraceExporter.ts @@ -25,7 +25,6 @@ import { } from '@opentelemetry/otlp-transformer'; const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/traces'; -const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; /** * Collector Trace Exporter for Web @@ -35,14 +34,11 @@ export class OTLPTraceExporter implements SpanExporter { constructor(config: OTLPExporterConfigBase = {}) { - super(config, ProtobufTraceSerializer, 'application/x-protobuf'); - } - - getDefaultUrl(config: OTLPExporterConfigBase): string { - if (typeof config.url === 'string') { - return config.url; - } - - return DEFAULT_COLLECTOR_URL; + super( + config, + ProtobufTraceSerializer, + { 'Content-Type': 'application/x-protobuf' }, + DEFAULT_COLLECTOR_RESOURCE_PATH + ); } } diff --git a/experimental/packages/exporter-trace-otlp-proto/src/platform/node/OTLPTraceExporter.ts b/experimental/packages/exporter-trace-otlp-proto/src/platform/node/OTLPTraceExporter.ts index 79da4ddc28b..1e1b35a230a 100644 --- a/experimental/packages/exporter-trace-otlp-proto/src/platform/node/OTLPTraceExporter.ts +++ b/experimental/packages/exporter-trace-otlp-proto/src/platform/node/OTLPTraceExporter.ts @@ -15,13 +15,9 @@ */ import { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'; -import { getEnv, baggageUtils } from '@opentelemetry/core'; import { OTLPExporterNodeConfigBase, - appendResourcePathToUrl, - appendRootPathToUrlIfNeeded, OTLPExporterNodeBase, - parseHeaders, } from '@opentelemetry/otlp-exporter-base'; import { IExportTraceServiceResponse, @@ -29,8 +25,6 @@ import { } from '@opentelemetry/otlp-transformer'; import { VERSION } from '../../version'; -const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/traces'; -const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; const USER_AGENT = { 'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`, }; @@ -43,35 +37,15 @@ export class OTLPTraceExporter implements SpanExporter { constructor(config: OTLPExporterNodeConfigBase = {}) { - super(config, ProtobufTraceSerializer, { - ...baggageUtils.parseKeyPairsIntoRecord( - getEnv().OTEL_EXPORTER_OTLP_TRACES_HEADERS - ), - ...parseHeaders(config?.headers), - ...USER_AGENT, - 'Content-Type': 'application/x-protobuf', - }); - } - - getDefaultUrl(config: OTLPExporterNodeConfigBase) { - if (typeof config.url === 'string') { - return config.url; - } - - const env = getEnv(); - if (env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT.length > 0) { - return appendRootPathToUrlIfNeeded( - env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT - ); - } - - if (env.OTEL_EXPORTER_OTLP_ENDPOINT.length > 0) { - return appendResourcePathToUrl( - env.OTEL_EXPORTER_OTLP_ENDPOINT, - DEFAULT_COLLECTOR_RESOURCE_PATH - ); - } - - return DEFAULT_COLLECTOR_URL; + super( + config, + ProtobufTraceSerializer, + { + ...USER_AGENT, + 'Content-Type': 'application/x-protobuf', + }, + 'TRACES', + 'v1/traces' + ); } } diff --git a/experimental/packages/exporter-trace-otlp-proto/test/browser/CollectorTraceExporter.test.ts b/experimental/packages/exporter-trace-otlp-proto/test/browser/CollectorTraceExporter.test.ts index e8187e72960..264d0051f52 100644 --- a/experimental/packages/exporter-trace-otlp-proto/test/browser/CollectorTraceExporter.test.ts +++ b/experimental/packages/exporter-trace-otlp-proto/test/browser/CollectorTraceExporter.test.ts @@ -21,9 +21,7 @@ import { OTLPTraceExporter } from '../../src/platform/browser/index'; describe('OTLPTraceExporter - web', () => { let collectorTraceExporter: OTLPTraceExporter; describe('constructor', () => { - let onInitSpy: any; beforeEach(() => { - onInitSpy = sinon.stub(OTLPTraceExporter.prototype, 'onInit'); const collectorExporterConfig = { hostname: 'foo', url: 'http://foo.bar.com', @@ -36,15 +34,5 @@ describe('OTLPTraceExporter - web', () => { it('should create an instance', () => { assert.ok(typeof collectorTraceExporter !== 'undefined'); }); - it('should call onInit', () => { - assert.strictEqual(onInitSpy.callCount, 1); - }); - it('should set hostname', () => { - assert.strictEqual(collectorTraceExporter.hostname, 'foo'); - }); - - it('should set url', () => { - assert.strictEqual(collectorTraceExporter.url, 'http://foo.bar.com'); - }); }); }); diff --git a/experimental/packages/exporter-trace-otlp-proto/test/node/OTLPTraceExporter.test.ts b/experimental/packages/exporter-trace-otlp-proto/test/node/OTLPTraceExporter.test.ts index 6adf335c1a2..b034778e33c 100644 --- a/experimental/packages/exporter-trace-otlp-proto/test/node/OTLPTraceExporter.test.ts +++ b/experimental/packages/exporter-trace-otlp-proto/test/node/OTLPTraceExporter.test.ts @@ -81,142 +81,12 @@ describe('OTLPTraceExporter - node with proto over http', () => { }); }); - describe('when configuring via environment', () => { - const envSource = process.env; - it('should use url defined in env that ends with root path and append version and signal path', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}v1/traces` - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should use url defined in env without checking if path is already present', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/v1/traces'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces` - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should use url defined in env and append version and signal', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces` - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should override global exporter url with signal url defined in env', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://foo.traces/'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = ''; - }); - it('should override url defined in env with url defined in constructor', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; - const constructorDefinedEndpoint = 'http://constructor/v1/traces'; - const collectorExporter = new OTLPTraceExporter({ - url: constructorDefinedEndpoint, - }); - assert.strictEqual(collectorExporter.url, constructorDefinedEndpoint); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should add root path when signal url defined in env contains no path and no root path', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://foo.bar'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT}/` - ); - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = ''; - }); - it('should not add root path when signal url defined in env contains root path but no path', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://foo.bar/'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT}` - ); - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = ''; - }); - it('should not add root path when signal url defined in env contains path', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://foo.bar/v1/traces'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT}` - ); - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = ''; - }); - it('should not add root path when signal url defined in env contains path and ends in /', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = - 'http://foo.bar/v1/traces/'; - const collectorExporter = new OTLPTraceExporter(); - assert.strictEqual( - collectorExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT}` - ); - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = ''; - }); - it('should use headers defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar'; - const exporter = new OTLPTraceExporter(); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers']['foo'], - 'bar' - ); - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - it('should override global headers config with signal headers defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; - envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = 'foo=boo'; - const exporter = new OTLPTraceExporter(); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers']['foo'], - 'boo' - ); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers']['bar'], - 'foo' - ); - envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = ''; - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - it('should override headers defined via env with headers defined in constructor', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; - const exporter = new OTLPTraceExporter({ - headers: { - foo: 'constructor', - }, - }); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers']['foo'], - 'constructor' - ); - assert.strictEqual( - exporter['_transport']['_transport']['_parameters']['headers']['bar'], - 'foo' - ); - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - }); - describe('export', () => { beforeEach(() => { collectorExporterConfig = { headers: { foo: 'bar', }, - hostname: 'foo', url: 'http://foo.bar.com', keepAlive: true, httpAgentOptions: { keepAliveMsecs: 2000 }, @@ -366,7 +236,6 @@ describe('OTLPTraceExporter - node with proto over http', () => { headers: { foo: 'bar', }, - hostname: 'foo', url: 'http://foo.bar.com', keepAlive: true, compression: CompressionAlgorithm.GZIP, diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/src/OTLPMetricExporter.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/src/OTLPMetricExporter.ts index d7d3c1eea34..2602911aa1a 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/src/OTLPMetricExporter.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/src/OTLPMetricExporter.ts @@ -22,55 +22,23 @@ import { ResourceMetrics } from '@opentelemetry/sdk-metrics'; import { OTLPGRPCExporterConfigNode, OTLPGRPCExporterNodeBase, - validateAndNormalizeUrl, - DEFAULT_COLLECTOR_URL, } from '@opentelemetry/otlp-grpc-exporter-base'; -import { baggageUtils, getEnv } from '@opentelemetry/core'; import { IExportMetricsServiceResponse, ProtobufMetricsSerializer, } from '@opentelemetry/otlp-transformer'; -import { VERSION } from './version'; -import { parseHeaders } from '@opentelemetry/otlp-exporter-base'; - -const USER_AGENT = { - 'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`, -}; class OTLPMetricExporterProxy extends OTLPGRPCExporterNodeBase< ResourceMetrics, IExportMetricsServiceResponse > { constructor(config?: OTLPGRPCExporterConfigNode & OTLPMetricExporterOptions) { - const signalSpecificMetadata = { - ...USER_AGENT, - ...baggageUtils.parseKeyPairsIntoRecord( - getEnv().OTEL_EXPORTER_OTLP_METRICS_HEADERS - ), - ...parseHeaders(config?.headers), - }; super( config, - signalSpecificMetadata, + ProtobufMetricsSerializer, 'MetricsExportService', '/opentelemetry.proto.collector.metrics.v1.MetricsService/Export', - ProtobufMetricsSerializer - ); - } - - getDefaultUrl(config: OTLPGRPCExporterConfigNode): string { - return validateAndNormalizeUrl(this.getUrlFromConfig(config)); - } - - getUrlFromConfig(config: OTLPGRPCExporterConfigNode): string { - if (typeof config.url === 'string') { - return config.url; - } - - return ( - getEnv().OTEL_EXPORTER_OTLP_METRICS_ENDPOINT || - getEnv().OTEL_EXPORTER_OTLP_ENDPOINT || - DEFAULT_COLLECTOR_URL + 'METRICS' ); } } diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/OTLPMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/OTLPMetricExporter.test.ts index 9ffd1d0d95c..833fa2ba7d1 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/OTLPMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/OTLPMetricExporter.test.ts @@ -41,7 +41,6 @@ import { IExportMetricsServiceRequest, IResourceMetrics, } from '@opentelemetry/otlp-transformer'; -import { VERSION } from '../src/version'; import { AggregationTemporalityPreference } from '@opentelemetry/exporter-metrics-otlp-http'; const metricsServiceProtoPath = @@ -302,112 +301,6 @@ const testOTLPMetricExporter = (params: TestParams) => { }); }; -describe('OTLPMetricExporter - node (getDefaultUrl)', () => { - it('should default to localhost', done => { - const collectorExporter = new OTLPMetricExporter(); - setTimeout(() => { - assert.strictEqual(collectorExporter._otlpExporter.url, 'localhost:4317'); - done(); - }); - }); - it('should keep the URL if included', done => { - const url = 'http://foo.bar.com'; - const collectorExporter = new OTLPMetricExporter({ - url, - temporalityPreference: AggregationTemporalityPreference.CUMULATIVE, - }); - setTimeout(() => { - assert.strictEqual(collectorExporter._otlpExporter.url, 'foo.bar.com'); - done(); - }); - }); -}); - -describe('when configuring via environment', () => { - afterEach(function () { - // Ensure we don't pollute other tests if assertions fail - delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT; - delete envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT; - delete envSource.OTEL_EXPORTER_OTLP_HEADERS; - delete envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS; - sinon.restore(); - }); - - const envSource = process.env; - it('should use url defined in env', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual(collectorExporter._otlpExporter.url, 'foo.bar'); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should override global exporter url with signal url defined in env', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = 'http://foo.metrics'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual(collectorExporter._otlpExporter.url, 'foo.metrics'); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = ''; - }); - it('should use override url defined in env with url defined in constructor', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/v1/metrics'; - const constructorDefinedEndpoint = 'http://constructor/v1/metrics'; - const collectorExporter = new OTLPMetricExporter({ - url: constructorDefinedEndpoint, - }); - assert.strictEqual(collectorExporter._otlpExporter.url, 'constructor'); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should use headers defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar'; - const collectorExporter = new OTLPMetricExporter(); - const actualMetadata = - collectorExporter._otlpExporter['_transport']['_parameters'].metadata(); - assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']); - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - it('should include user agent in header', () => { - const collectorExporter = new OTLPMetricExporter(); - const actualMetadata = - collectorExporter._otlpExporter['_transport']['_parameters'].metadata(); - assert.deepStrictEqual(actualMetadata.get('User-Agent'), [ - `OTel-OTLP-Exporter-JavaScript/${VERSION}`, - ]); - }); - it('should not override hard-coded headers config with headers defined via env', () => { - const metadata = new grpc.Metadata(); - metadata.set('foo', 'bar'); - metadata.set('goo', 'lol'); - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=jar,bar=foo'; - envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS = 'foo=boo'; - const collectorExporter = new OTLPMetricExporter({ - metadata, - temporalityPreference: AggregationTemporalityPreference.CUMULATIVE, - }); - const actualMetadata = - collectorExporter._otlpExporter['_transport']['_parameters'].metadata(); - assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']); - assert.deepStrictEqual(actualMetadata.get('bar'), ['foo']); - assert.deepStrictEqual(actualMetadata.get('goo'), ['lol']); - envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS = ''; - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - - it('should override headers defined via env with headers defined in constructor', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; - const collectorExporter = new OTLPMetricExporter({ - headers: { - foo: 'constructor', - }, - }); - - const actualMetadata = - collectorExporter._otlpExporter['_transport']['_parameters'].metadata(); - assert.deepStrictEqual(actualMetadata.get('foo'), ['constructor']); - assert.deepStrictEqual(actualMetadata.get('bar'), ['foo']); - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); -}); - testOTLPMetricExporter({ useTLS: true }); testOTLPMetricExporter({ useTLS: false }); testOTLPMetricExporter({ metadata }); diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/browser/OTLPMetricExporter.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/browser/OTLPMetricExporter.ts index b84c194f914..828cd61b0c0 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/browser/OTLPMetricExporter.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/browser/OTLPMetricExporter.ts @@ -27,22 +27,18 @@ import { } from '@opentelemetry/otlp-transformer'; const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/metrics'; -const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; class OTLPExporterBrowserProxy extends OTLPExporterBrowserBase< ResourceMetrics, IExportMetricsServiceResponse > { constructor(config?: OTLPMetricExporterOptions & OTLPExporterConfigBase) { - super(config, JsonMetricsSerializer, 'application/json'); - } - - getDefaultUrl(config: OTLPExporterConfigBase): string { - if (typeof config.url === 'string') { - return config.url; - } - - return DEFAULT_COLLECTOR_URL; + super( + config, + JsonMetricsSerializer, + { 'Content-Type': 'application/json' }, + DEFAULT_COLLECTOR_RESOURCE_PATH + ); } } diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts index bf57b807dc3..368858190af 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts @@ -15,15 +15,11 @@ */ import { ResourceMetrics } from '@opentelemetry/sdk-metrics'; -import { getEnv, baggageUtils } from '@opentelemetry/core'; import { OTLPMetricExporterOptions } from '../../OTLPMetricExporterOptions'; import { OTLPMetricExporterBase } from '../../OTLPMetricExporterBase'; import { OTLPExporterNodeBase, OTLPExporterNodeConfigBase, - appendResourcePathToUrl, - appendRootPathToUrlIfNeeded, - parseHeaders, } from '@opentelemetry/otlp-exporter-base'; import { IExportMetricsServiceResponse, @@ -31,8 +27,6 @@ import { } from '@opentelemetry/otlp-transformer'; import { VERSION } from '../../version'; -const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/metrics'; -const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; const USER_AGENT = { 'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`, }; @@ -42,36 +36,16 @@ class OTLPExporterNodeProxy extends OTLPExporterNodeBase< IExportMetricsServiceResponse > { constructor(config?: OTLPExporterNodeConfigBase & OTLPMetricExporterOptions) { - super(config, JsonMetricsSerializer, { - ...baggageUtils.parseKeyPairsIntoRecord( - getEnv().OTEL_EXPORTER_OTLP_METRICS_HEADERS - ), - ...parseHeaders(config?.headers), - ...USER_AGENT, - 'Content-Type': 'application/json', - }); - } - - getDefaultUrl(config: OTLPExporterNodeConfigBase): string { - if (typeof config.url === 'string') { - return config.url; - } - - const env = getEnv(); - if (env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT.length > 0) { - return appendRootPathToUrlIfNeeded( - env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT - ); - } - - if (env.OTEL_EXPORTER_OTLP_ENDPOINT.length > 0) { - return appendResourcePathToUrl( - env.OTEL_EXPORTER_OTLP_ENDPOINT, - DEFAULT_COLLECTOR_RESOURCE_PATH - ); - } - - return DEFAULT_COLLECTOR_URL; + super( + config, + JsonMetricsSerializer, + { + ...USER_AGENT, + 'Content-Type': 'application/json', + }, + 'METRICS', + 'v1/metrics' + ); } } diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/common/CollectorMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/common/CollectorMetricExporter.test.ts index e4ae9f3926b..e6e646f06a2 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/common/CollectorMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/common/CollectorMetricExporter.test.ts @@ -36,8 +36,6 @@ class OTLPMetricExporter extends OTLPExporterBase< CollectorExporterConfig, ResourceMetrics > { - onInit() {} - onShutdown() {} send() {} @@ -62,12 +60,8 @@ describe('OTLPMetricExporter - common', () => { }); describe('constructor', () => { - let onInitSpy: any; - beforeEach(async () => { - onInitSpy = sinon.stub(OTLPMetricExporter.prototype, 'onInit'); collectorExporterConfig = { - hostname: 'foo', url: 'http://foo.bar.com', }; collectorExporter = new OTLPMetricExporter(collectorExporterConfig); @@ -87,20 +81,6 @@ describe('OTLPMetricExporter - common', () => { assert.ok(typeof collectorExporter !== 'undefined'); }); - it('should call onInit', () => { - assert.strictEqual(onInitSpy.callCount, 1); - }); - - describe('when config contains certain params', () => { - it('should set hostname', () => { - assert.strictEqual(collectorExporter.hostname, 'foo'); - }); - - it('should set url', () => { - assert.strictEqual(collectorExporter.url, 'http://foo.bar.com'); - }); - }); - describe('when config is missing certain params', () => { beforeEach(() => { collectorExporter = new OTLPMetricExporter(); @@ -180,7 +160,6 @@ describe('OTLPMetricExporter - common', () => { beforeEach(() => { onShutdownSpy = sinon.stub(OTLPMetricExporter.prototype, 'onShutdown'); collectorExporterConfig = { - hostname: 'foo', url: 'http://foo.bar.com', }; collectorExporter = new OTLPMetricExporter(collectorExporterConfig); diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts index 7b19b84f029..4b11c946cc1 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts @@ -21,9 +21,6 @@ import * as http from 'http'; import * as sinon from 'sinon'; import { AggregationTemporalityPreference, - CumulativeTemporalitySelector, - DeltaTemporalitySelector, - LowMemoryTemporalitySelector, OTLPMetricExporterOptions, } from '../../src'; @@ -55,7 +52,6 @@ import { OTLPExporterNodeConfigBase, } from '@opentelemetry/otlp-exporter-base'; import { IExportMetricsServiceRequest } from '@opentelemetry/otlp-transformer'; -import { VERSION } from '../../src/version'; let fakeRequest: PassThrough; @@ -248,219 +244,6 @@ describe('OTLPMetricExporter - node with json over http', () => { }); }); - describe('when configuring via environment', () => { - const envSource = process.env; - it('should use url defined in env that ends with root path and append version and signal path', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}v1/metrics` - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should use url defined in env without checking if path is already present', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/v1/metrics'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/metrics` - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should use url defined in env and append version and signal', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/metrics` - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should override global exporter url with signal url defined in env', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = 'http://foo.metrics/'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = ''; - }); - it('should add root path when signal url defined in env contains no path and no root path', () => { - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = 'http://foo.bar'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT}/` - ); - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = ''; - }); - it('should not add root path when signal url defined in env contains root path but no path', () => { - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = 'http://foo.bar/'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT}` - ); - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = ''; - }); - it('should not add root path when signal url defined in env contains path', () => { - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = - 'http://foo.bar/v1/metrics'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT}` - ); - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = ''; - }); - it('should not add root path when signal url defined in env contains path and ends in /', () => { - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = - 'http://foo.bar/v1/metrics/'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT}` - ); - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = ''; - }); - it('should use override url defined in env with url defined in constructor', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/v1/metrics'; - const constructorDefinedEndpoint = 'http://constructor/v1/metrics'; - const collectorExporter = new OTLPMetricExporter({ - url: constructorDefinedEndpoint, - }); - assert.strictEqual( - collectorExporter._otlpExporter.url, - constructorDefinedEndpoint - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should use headers defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar'; - const exporter = new OTLPMetricExporter(); - assert.strictEqual( - exporter._otlpExporter['_transport']['_transport']['_parameters'][ - 'headers' - ]['foo'], - 'bar' - ); - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - it('should include user agent in header', () => { - const exporter = new OTLPMetricExporter(); - assert.strictEqual( - exporter._otlpExporter['_transport']['_transport']['_parameters'][ - 'headers' - ]['User-Agent'], - `OTel-OTLP-Exporter-JavaScript/${VERSION}` - ); - }); - it('should override global headers config with signal headers defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; - envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS = 'foo=boo'; - const exporter = new OTLPMetricExporter(); - assert.strictEqual( - exporter._otlpExporter['_transport']['_transport']['_parameters'][ - 'headers' - ]['foo'], - 'boo' - ); - assert.strictEqual( - exporter._otlpExporter['_transport']['_transport']['_parameters'][ - 'headers' - ]['bar'], - 'foo' - ); - envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS = ''; - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - it('should override headers defined via env with headers defined in constructor', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; - const exporter = new OTLPMetricExporter({ - headers: { - foo: 'constructor', - }, - }); - assert.strictEqual( - exporter._otlpExporter['_transport']['_transport']['_parameters'][ - 'headers' - ]['foo'], - 'constructor' - ); - assert.strictEqual( - exporter._otlpExporter['_transport']['_transport']['_parameters'][ - 'headers' - ]['bar'], - 'foo' - ); - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - it('should use delta temporality defined via env', () => { - for (const envValue of ['delta', 'DELTA', 'DeLTa', 'delta ']) { - envSource.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE = envValue; - const exporter = new OTLPMetricExporter(); - assert.strictEqual( - exporter['_aggregationTemporalitySelector'], - DeltaTemporalitySelector - ); - } - }); - it('should use cumulative temporality defined via env', () => { - for (const envValue of [ - 'cumulative', - 'CUMULATIVE', - 'CuMULaTIvE', - 'cumulative ', - ]) { - envSource.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE = envValue; - const exporter = new OTLPMetricExporter(); - assert.strictEqual( - exporter['_aggregationTemporalitySelector'], - CumulativeTemporalitySelector - ); - } - }); - it('should use low memory temporality defined via env', () => { - for (const envValue of [ - 'lowmemory', - 'LOWMEMORY', - 'LoWMeMOrY', - 'lowmemory ', - ]) { - envSource.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE = envValue; - const exporter = new OTLPMetricExporter(); - assert.strictEqual( - exporter['_aggregationTemporalitySelector'], - LowMemoryTemporalitySelector - ); - } - }); - it('should configure cumulative temporality with invalid value in env', () => { - for (const envValue of ['invalid', ' ']) { - envSource.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE = envValue; - const exporter = new OTLPMetricExporter(); - assert.strictEqual( - exporter['_aggregationTemporalitySelector'], - CumulativeTemporalitySelector - ); - } - }); - it('should respect explicit config over environment variable', () => { - envSource.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE = - 'cumulative'; - const exporter = new OTLPMetricExporter({ - temporalityPreference: AggregationTemporalityPreference.DELTA, - }); - assert.strictEqual( - exporter['_aggregationTemporalitySelector'], - DeltaTemporalitySelector - ); - }); - }); - describe('export', () => { beforeEach(async () => { stubRequest = sinon.stub(http, 'request').returns(fakeRequest as any); @@ -468,7 +251,6 @@ describe('OTLPMetricExporter - node with json over http', () => { headers: { foo: 'bar', }, - hostname: 'foo', url: 'http://foo.bar.com', keepAlive: true, httpAgentOptions: { keepAliveMsecs: 2000 }, @@ -677,28 +459,4 @@ describe('OTLPMetricExporter - node with json over http', () => { }); }); }); - describe('OTLPMetricExporter - node (getDefaultUrl)', () => { - it('should default to localhost', done => { - const collectorExporter = new OTLPMetricExporter(); - setTimeout(() => { - assert.strictEqual( - collectorExporter._otlpExporter.url, - 'http://localhost:4318/v1/metrics' - ); - done(); - }); - }); - - it('should keep the URL if included', done => { - const url = 'http://foo.bar.com'; - const collectorExporter = new OTLPMetricExporter({ - url, - temporalityPreference: AggregationTemporalityPreference.CUMULATIVE, - }); - setTimeout(() => { - assert.strictEqual(collectorExporter._otlpExporter.url, url); - done(); - }); - }); - }); }); diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/src/OTLPMetricExporter.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/src/OTLPMetricExporter.ts index 4834c5a698f..9f594055a5b 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/src/OTLPMetricExporter.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/src/OTLPMetricExporter.ts @@ -15,14 +15,10 @@ */ import { OTLPMetricExporterOptions } from '@opentelemetry/exporter-metrics-otlp-http'; -import { getEnv, baggageUtils } from '@opentelemetry/core'; import { ResourceMetrics } from '@opentelemetry/sdk-metrics'; import { OTLPMetricExporterBase } from '@opentelemetry/exporter-metrics-otlp-http'; import { OTLPExporterNodeConfigBase, - appendResourcePathToUrl, - appendRootPathToUrlIfNeeded, - parseHeaders, OTLPExporterNodeBase, } from '@opentelemetry/otlp-exporter-base'; import { @@ -31,8 +27,6 @@ import { } from '@opentelemetry/otlp-transformer'; import { VERSION } from './version'; -const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/metrics'; -const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; const USER_AGENT = { 'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`, }; @@ -42,36 +36,16 @@ class OTLPMetricExporterNodeProxy extends OTLPExporterNodeBase< IExportMetricsServiceResponse > { constructor(config?: OTLPExporterNodeConfigBase & OTLPMetricExporterOptions) { - super(config, ProtobufMetricsSerializer, { - ...baggageUtils.parseKeyPairsIntoRecord( - getEnv().OTEL_EXPORTER_OTLP_METRICS_HEADERS - ), - ...parseHeaders(config?.headers), - ...USER_AGENT, - 'Content-Type': 'application/x-protobuf', - }); - } - - getDefaultUrl(config: OTLPExporterNodeConfigBase): string { - if (typeof config.url === 'string') { - return config.url; - } - - const env = getEnv(); - if (env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT.length > 0) { - return appendRootPathToUrlIfNeeded( - env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT - ); - } - - if (env.OTEL_EXPORTER_OTLP_ENDPOINT.length > 0) { - return appendResourcePathToUrl( - env.OTEL_EXPORTER_OTLP_ENDPOINT, - DEFAULT_COLLECTOR_RESOURCE_PATH - ); - } - - return DEFAULT_COLLECTOR_URL; + super( + config, + ProtobufMetricsSerializer, + { + ...USER_AGENT, + 'Content-Type': 'application/x-protobuf', + }, + 'METRICS', + 'v1/metrics' + ); } } diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/OTLPMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/OTLPMetricExporter.test.ts index 300fd8eb059..42408adab96 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/OTLPMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/OTLPMetricExporter.test.ts @@ -88,156 +88,12 @@ describe('OTLPMetricExporter - node with proto over http', () => { }); }); - describe('when configuring via environment', () => { - const envSource = process.env; - it('should use url defined in env that ends with root path and append version and signal path', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}v1/metrics` - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should use url defined in env without checking if path is already present', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/v1/metrics'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/metrics` - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should use url defined in env and append version and signal', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/metrics` - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should override global exporter url with signal url defined in env', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = 'http://foo.metrics/'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = ''; - }); - it('should add root path when signal url defined in env contains no path and no root path', () => { - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = 'http://foo.bar'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT}/` - ); - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = ''; - }); - it('should not add root path when signal url defined in env contains root path but no path', () => { - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = 'http://foo.bar/'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT}` - ); - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = ''; - }); - it('should not add root path when signal url defined in env contains path', () => { - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = - 'http://foo.bar/v1/metrics'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT}` - ); - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = ''; - }); - it('should not add root path when signal url defined in env contains path and ends in /', () => { - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = - 'http://foo.bar/v1/metrics/'; - const collectorExporter = new OTLPMetricExporter(); - assert.strictEqual( - collectorExporter._otlpExporter.url, - `${envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT}` - ); - envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = ''; - }); - it('should use override url defined in env with url defined in constructor', () => { - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/v1/metrics'; - const constructorDefinedEndpoint = 'http://constructor/v1/metrics'; - const collectorExporter = new OTLPMetricExporter({ - url: constructorDefinedEndpoint, - }); - assert.strictEqual( - collectorExporter._otlpExporter.url, - constructorDefinedEndpoint - ); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - }); - it('should use headers defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar'; - const exporter = new OTLPMetricExporter(); - assert.strictEqual( - exporter._otlpExporter['_transport']['_transport']['_parameters'][ - 'headers' - ]['foo'], - 'bar' - ); - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - it('should override global headers config with signal headers defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; - envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS = 'foo=boo'; - const exporter = new OTLPMetricExporter(); - assert.strictEqual( - exporter._otlpExporter['_transport']['_transport']['_parameters'][ - 'headers' - ]['foo'], - 'boo' - ); - assert.strictEqual( - exporter._otlpExporter['_transport']['_transport']['_parameters'][ - 'headers' - ]['bar'], - 'foo' - ); - envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS = ''; - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - it('should override headers defined via env with headers defined in constructor', () => { - envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; - const exporter = new OTLPMetricExporter({ - headers: { - foo: 'constructor', - }, - }); - assert.strictEqual( - exporter._otlpExporter['_transport']['_transport']['_parameters'][ - 'headers' - ]['foo'], - 'constructor' - ); - assert.strictEqual( - exporter._otlpExporter['_transport']['_transport']['_parameters'][ - 'headers' - ]['bar'], - 'foo' - ); - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; - }); - }); - describe('export', () => { beforeEach(async () => { collectorExporterConfig = { headers: { foo: 'bar', }, - hostname: 'foo', url: 'http://foo.bar.com', keepAlive: true, httpAgentOptions: { keepAliveMsecs: 2000 }, diff --git a/experimental/packages/otlp-exporter-base/src/OTLPExporterBase.ts b/experimental/packages/otlp-exporter-base/src/OTLPExporterBase.ts index 0e8c37f00d2..986447add25 100644 --- a/experimental/packages/otlp-exporter-base/src/OTLPExporterBase.ts +++ b/experimental/packages/otlp-exporter-base/src/OTLPExporterBase.ts @@ -25,7 +25,6 @@ import { OTLPExporterConfigBase, ExportServiceError, } from './types'; -import { configureExporterTimeout } from './util'; /** * Collector Exporter abstract base class @@ -34,12 +33,6 @@ export abstract class OTLPExporterBase< T extends OTLPExporterConfigBase, ExportItem, > { - public readonly url: string; - /** - * @deprecated scheduled for removal. This is only used in tests. - */ - public readonly hostname: string | undefined; - public readonly timeoutMillis: number; protected _concurrencyLimit: number; protected _sendingPromises: Promise[] = []; protected _shutdownOnce: BindOnceFuture; @@ -48,11 +41,6 @@ export abstract class OTLPExporterBase< * @param config */ constructor(config: T = {} as T) { - this.url = this.getDefaultUrl(config); - if (typeof config.hostname === 'string') { - this.hostname = config.hostname; - } - this.shutdown = this.shutdown.bind(this); this._shutdownOnce = new BindOnceFuture(this._shutdown, this); @@ -60,11 +48,6 @@ export abstract class OTLPExporterBase< typeof config.concurrencyLimit === 'number' ? config.concurrencyLimit : 30; - - this.timeoutMillis = configureExporterTimeout(config.timeoutMillis); - - // platform dependent - this.onInit(config); } /** @@ -138,11 +121,9 @@ export abstract class OTLPExporterBase< } abstract onShutdown(): void; - abstract onInit(config: T): void; abstract send( items: ExportItem[], onSuccess: () => void, onError: (error: OTLPExporterError) => void ): void; - abstract getDefaultUrl(config: T): string; } diff --git a/experimental/packages/otlp-exporter-base/src/configuration/otlp-http-configuration.ts b/experimental/packages/otlp-exporter-base/src/configuration/otlp-http-configuration.ts new file mode 100644 index 00000000000..d0605163284 --- /dev/null +++ b/experimental/packages/otlp-exporter-base/src/configuration/otlp-http-configuration.ts @@ -0,0 +1,89 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + getSharedConfigurationDefaults, + mergeOtlpSharedConfigurationWithDefaults, + OtlpSharedConfiguration, +} from './shared-configuration'; + +export interface OtlpHttpConfiguration extends OtlpSharedConfiguration { + url: string; + headers: Record; +} + +function determineHeaders( + userProvidedHeaders: Record | undefined | null, + fallbackHeaders: Record | undefined | null, + defaultHeaders: Record +): Record { + const requiredHeaders = { + ...defaultHeaders, + }; + const headers = {}; + + // add fallback ones first + if (fallbackHeaders != null) { + Object.assign(headers, fallbackHeaders); + } + + // override with user-provided ones + if (userProvidedHeaders != null) { + Object.assign(headers, userProvidedHeaders); + } + + // override required ones. + return Object.assign(headers, requiredHeaders); +} + +/** + * @param userProvidedConfiguration Configuration options provided by the user in code. + * @param fallbackConfiguration Fallback to use when the {@link userProvidedConfiguration} does not specify an option. + * @param defaultConfiguration The defaults as defined by the exporter specification + */ +export function mergeOtlpHttpConfigurationWithDefaults( + userProvidedConfiguration: Partial, + fallbackConfiguration: Partial, + defaultConfiguration: OtlpHttpConfiguration +): OtlpHttpConfiguration { + return { + ...mergeOtlpSharedConfigurationWithDefaults( + userProvidedConfiguration, + fallbackConfiguration, + defaultConfiguration + ), + headers: determineHeaders( + userProvidedConfiguration.headers, + fallbackConfiguration.headers, + defaultConfiguration.headers + ), + url: + userProvidedConfiguration.url ?? + fallbackConfiguration.url ?? + defaultConfiguration.url, + }; +} + +export function getHttpConfigurationDefaults( + requiredHeaders: Record, + signalResourcePath: string +): OtlpHttpConfiguration { + return { + ...getSharedConfigurationDefaults(), + headers: requiredHeaders, + url: 'http://localhost:4318/' + signalResourcePath, + }; +} diff --git a/experimental/packages/otlp-exporter-base/src/configuration/otlp-http-env-configuration.ts b/experimental/packages/otlp-exporter-base/src/configuration/otlp-http-env-configuration.ts new file mode 100644 index 00000000000..ba4bd6c5951 --- /dev/null +++ b/experimental/packages/otlp-exporter-base/src/configuration/otlp-http-env-configuration.ts @@ -0,0 +1,90 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { baggageUtils } from '@opentelemetry/core'; +import { diag } from '@opentelemetry/api'; +import { getSharedConfigurationFromEnvironment } from './shared-env-configuration'; +import { OtlpHttpConfiguration } from './otlp-http-configuration'; + +function getHeadersFromEnv(signalIdentifier: string) { + const envHeaders = + process.env[`OTEL_EXPORTER_OTLP_${signalIdentifier}_HEADERS`]; + const envNonSignalSpecificHeaders = process.env.OTEL_EXPORTER_OTLP_HEADERS; + if (envHeaders == null && envNonSignalSpecificHeaders == null) { + return undefined; + } + + // headers are combined instead, with the non-specific headers taking precedence over the more specific ones. + return Object.assign( + {}, + baggageUtils.parseKeyPairsIntoRecord(envNonSignalSpecificHeaders), + baggageUtils.parseKeyPairsIntoRecord(envHeaders) + ); +} + +function appendRootPathToUrlIfNeeded(url: string): string { + try { + const parsedUrl = new URL(url); + // This will automatically append '/' if there's no root path. + return parsedUrl.toString(); + } catch { + diag.warn( + `Configuration: Could not parse export URL: '${url}', falling back to undefined` + ); + return url; + } +} + +function appendResourcePathToUrl(url: string, path: string): string { + if (!url.endsWith('/')) { + url = url + '/'; + } + return url + path; +} + +function determineNonSpecificUrl(signalResourcePath: string) { + const envUrl = process.env.OTEL_EXPORTER_OTLP_ENDPOINT; + if (envUrl == null || envUrl === '') { + return undefined; + } + return appendResourcePathToUrl(envUrl, signalResourcePath); +} + +function determineUrl(signalIdentifier: string) { + const envUrl = process.env[`OTEL_EXPORTER_OTLP_${signalIdentifier}_ENDPOINT`]; + if (envUrl == null || envUrl === '') { + return undefined; + } + return appendRootPathToUrlIfNeeded(envUrl); +} + +/** + * Reads and returns configuration from the environment + * + * @param signalIdentifier all caps part in environment variables that identifies the signal (e.g.: METRICS, TRACES, LOGS) + * @param signalResourcePath signal resource path to append if necessary (e.g.: v1/metrics, v1/traces, v1/logs) + */ +export function getHttpConfigurationFromEnvironment( + signalIdentifier: string, + signalResourcePath: string +): Partial { + return { + ...getSharedConfigurationFromEnvironment(signalIdentifier), + url: + determineUrl(signalIdentifier) ?? + determineNonSpecificUrl(signalResourcePath), + headers: getHeadersFromEnv(signalIdentifier), + }; +} diff --git a/experimental/packages/otlp-exporter-base/src/configuration/shared-configuration.ts b/experimental/packages/otlp-exporter-base/src/configuration/shared-configuration.ts new file mode 100644 index 00000000000..c85fe998131 --- /dev/null +++ b/experimental/packages/otlp-exporter-base/src/configuration/shared-configuration.ts @@ -0,0 +1,110 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { diag } from '@opentelemetry/api'; + +/** + * Configuration shared across all OTLP exporters + */ +export interface OtlpSharedConfiguration { + // Implementation note: anything added here MUST be + // - platform-agnostic + // - signal-agnostic + // - transport-agnostic + timeoutMillis: number; + concurrencyLimit: number; + compression: 'gzip' | 'none'; +} + +export function parseTimeout(timeoutEnvVar: string): number | undefined { + const envTimeout = process.env[timeoutEnvVar]; + if (envTimeout != null && envTimeout !== '') { + const definedTimeout = Number(envTimeout); + if ( + !Number.isNaN(definedTimeout) && + Number.isFinite(definedTimeout) && + definedTimeout > 0 + ) { + return definedTimeout; + } + diag.warn( + `Configuration: ${timeoutEnvVar} is invalid, expected number greater than 0 (actual: ${envTimeout})` + ); + } + return undefined; +} + +export function validateTimeoutMillis(timeoutMillis: number) { + if ( + !Number.isNaN(timeoutMillis) && + Number.isFinite(timeoutMillis) && + timeoutMillis > 0 + ) { + return timeoutMillis; + } + throw new Error( + `Configuration: timeoutMillis is invalid, expected number greater than 0 (actual: ${timeoutMillis})` + ); +} + +export function parseCompression( + compressionEnvVar: string +): 'none' | 'gzip' | undefined { + const compression = process.env[compressionEnvVar]; + + if (compression == null || compression === 'none' || compression === 'gzip') { + return compression; + } + diag.warn( + `Configuration: ${compressionEnvVar} is invalid, expected 'none' or 'gzip' (actual: '${compression}')` + ); + return undefined; +} + +/** + * @param userProvidedConfiguration Configuration options provided by the user in code. + * @param fallbackConfiguration Fallback to use when the {@link userProvidedConfiguration} does not specify an option. + * @param defaultConfiguration The defaults as defined by the exporter specification + */ +export function mergeOtlpSharedConfigurationWithDefaults( + userProvidedConfiguration: Partial, + fallbackConfiguration: Partial, + defaultConfiguration: OtlpSharedConfiguration +): OtlpSharedConfiguration { + return { + timeoutMillis: validateTimeoutMillis( + userProvidedConfiguration.timeoutMillis ?? + fallbackConfiguration.timeoutMillis ?? + defaultConfiguration.timeoutMillis + ), + concurrencyLimit: + userProvidedConfiguration.concurrencyLimit ?? + fallbackConfiguration.concurrencyLimit ?? + defaultConfiguration.concurrencyLimit, + compression: + userProvidedConfiguration.compression ?? + fallbackConfiguration.compression ?? + defaultConfiguration.compression, + }; +} + +export function getSharedConfigurationDefaults(): OtlpSharedConfiguration { + return { + timeoutMillis: 10000, + concurrencyLimit: 30, + compression: 'none', + }; +} diff --git a/experimental/packages/otlp-exporter-base/src/configuration/shared-env-configuration.ts b/experimental/packages/otlp-exporter-base/src/configuration/shared-env-configuration.ts new file mode 100644 index 00000000000..07cbd17c5d3 --- /dev/null +++ b/experimental/packages/otlp-exporter-base/src/configuration/shared-env-configuration.ts @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + OtlpSharedConfiguration, + parseCompression, + parseTimeout, +} from './shared-configuration'; + +function determineTimeout(signalIdentifier: string) { + const specificTimeout = parseTimeout( + `OTEL_EXPORTER_OTLP_${signalIdentifier}_TIMEOUT` + ); + const nonSpecificTimeout = parseTimeout('OTEL_EXPORTER_OTLP_TIMEOUT'); + + return specificTimeout ?? nonSpecificTimeout; +} + +function determineCompression( + signalIdentifier: string +): 'none' | 'gzip' | undefined { + const specificCompression = parseCompression( + `OTEL_EXPORTER_OTLP_${signalIdentifier}_COMPRESSION` + ); + const nonSpecificCompression = parseCompression( + 'OTEL_EXPORTER_OTLP_COMPRESSION' + ); + + return specificCompression ?? nonSpecificCompression; +} + +export function getSharedConfigurationFromEnvironment( + signalIdentifier: string +): Partial { + return { + timeoutMillis: determineTimeout(signalIdentifier), + compression: determineCompression(signalIdentifier), + }; +} diff --git a/experimental/packages/otlp-exporter-base/src/index.ts b/experimental/packages/otlp-exporter-base/src/index.ts index 2150f6c2c5c..58d148610af 100644 --- a/experimental/packages/otlp-exporter-base/src/index.ts +++ b/experimental/packages/otlp-exporter-base/src/index.ts @@ -40,3 +40,11 @@ export { } from './export-response'; export { IExporterTransport } from './exporter-transport'; + +export { + OtlpSharedConfiguration, + mergeOtlpSharedConfigurationWithDefaults, + getSharedConfigurationDefaults, +} from './configuration/shared-configuration'; + +export { getSharedConfigurationFromEnvironment } from './configuration/shared-env-configuration'; diff --git a/experimental/packages/otlp-exporter-base/src/platform/browser/OTLPExporterBrowserBase.ts b/experimental/packages/otlp-exporter-base/src/platform/browser/OTLPExporterBrowserBase.ts index 3313c283d77..da5401698cc 100644 --- a/experimental/packages/otlp-exporter-base/src/platform/browser/OTLPExporterBrowserBase.ts +++ b/experimental/packages/otlp-exporter-base/src/platform/browser/OTLPExporterBrowserBase.ts @@ -16,14 +16,16 @@ import { OTLPExporterBase } from '../../OTLPExporterBase'; import { OTLPExporterConfigBase, OTLPExporterError } from '../../types'; -import { parseHeaders } from '../../util'; import { diag } from '@opentelemetry/api'; -import { getEnv, baggageUtils } from '@opentelemetry/core'; import { ISerializer } from '@opentelemetry/otlp-transformer'; import { IExporterTransport } from '../../exporter-transport'; import { createXhrTransport } from './xhr-transport'; import { createSendBeaconTransport } from './send-beacon-transport'; import { createRetryingTransport } from '../../retrying-transport'; +import { + getHttpConfigurationDefaults, + mergeOtlpHttpConfigurationWithDefaults, +} from '../../configuration/otlp-http-configuration'; /** * Collector Metric Exporter abstract base class @@ -34,46 +36,55 @@ export abstract class OTLPExporterBrowserBase< > extends OTLPExporterBase { private _serializer: ISerializer; private _transport: IExporterTransport; + private _timeoutMillis: number; /** * @param config * @param serializer - * @param contentType + * @param requiredHeaders + * @param signalResourcePath */ constructor( config: OTLPExporterConfigBase = {}, serializer: ISerializer, - contentType: string + requiredHeaders: Record, + signalResourcePath: string ) { super(config); this._serializer = serializer; const useXhr = !!config.headers || typeof navigator.sendBeacon !== 'function'; + + const actualConfig = mergeOtlpHttpConfigurationWithDefaults( + { + url: config.url, + timeoutMillis: config.timeoutMillis, + headers: config.headers, + concurrencyLimit: config.concurrencyLimit, + }, + {}, // no fallback for browser case + getHttpConfigurationDefaults(requiredHeaders, signalResourcePath) + ); + + this._timeoutMillis = actualConfig.timeoutMillis; + this._concurrencyLimit = actualConfig.concurrencyLimit; + if (useXhr) { this._transport = createRetryingTransport({ transport: createXhrTransport({ - headers: Object.assign( - {}, - parseHeaders(config.headers), - baggageUtils.parseKeyPairsIntoRecord( - getEnv().OTEL_EXPORTER_OTLP_HEADERS - ), - { 'Content-Type': contentType } - ), - url: this.url, + headers: actualConfig.headers, + url: actualConfig.url, }), }); } else { // sendBeacon has no way to signal retry, so we do not wrap it in a RetryingTransport this._transport = createSendBeaconTransport({ - url: this.url, - blobType: contentType, + url: actualConfig.url, + blobType: actualConfig.headers['Content-Type'], }); } } - onInit(): void {} - onShutdown(): void {} send( @@ -94,7 +105,7 @@ export abstract class OTLPExporterBrowserBase< } const promise = this._transport - .send(data, this.timeoutMillis) + .send(data, this._timeoutMillis) .then(response => { if (response.status === 'success') { onSuccess(); diff --git a/experimental/packages/otlp-exporter-base/src/platform/node/OTLPExporterNodeBase.ts b/experimental/packages/otlp-exporter-base/src/platform/node/OTLPExporterNodeBase.ts index 2c66d8bca9f..f5d858016c8 100644 --- a/experimental/packages/otlp-exporter-base/src/platform/node/OTLPExporterNodeBase.ts +++ b/experimental/packages/otlp-exporter-base/src/platform/node/OTLPExporterNodeBase.ts @@ -16,14 +16,18 @@ import { OTLPExporterBase } from '../../OTLPExporterBase'; import { OTLPExporterNodeConfigBase } from './types'; -import { configureCompression } from './util'; import { diag } from '@opentelemetry/api'; -import { getEnv, baggageUtils } from '@opentelemetry/core'; import { ISerializer } from '@opentelemetry/otlp-transformer'; import { IExporterTransport } from '../../exporter-transport'; import { createHttpExporterTransport } from './http-exporter-transport'; import { OTLPExporterError } from '../../types'; import { createRetryingTransport } from '../../retrying-transport'; +import { convertLegacyAgentOptions } from './convert-legacy-agent-options'; +import { + getHttpConfigurationDefaults, + mergeOtlpHttpConfigurationWithDefaults, +} from '../../configuration/otlp-http-configuration'; +import { getHttpConfigurationFromEnvironment } from '../../configuration/otlp-http-env-configuration'; /** * Collector Metric Exporter abstract base class @@ -34,54 +38,47 @@ export abstract class OTLPExporterNodeBase< > extends OTLPExporterBase { private _serializer: ISerializer; private _transport: IExporterTransport; + private _timeoutMillis: number; constructor( config: OTLPExporterNodeConfigBase = {}, serializer: ISerializer, - signalSpecificHeaders: Record + requiredHeaders: Record, + signalIdentifier: string, + signalResourcePath: string ) { super(config); + const actualConfig = mergeOtlpHttpConfigurationWithDefaults( + { + url: config.url, + headers: config.headers, + concurrencyLimit: config.concurrencyLimit, + timeoutMillis: config.timeoutMillis, + compression: config.compression, + }, + getHttpConfigurationFromEnvironment(signalIdentifier, signalResourcePath), + getHttpConfigurationDefaults(requiredHeaders, signalResourcePath) + ); + + this._timeoutMillis = actualConfig.timeoutMillis; + this._concurrencyLimit = actualConfig.concurrencyLimit; + // eslint-disable-next-line @typescript-eslint/no-explicit-any if ((config as any).metadata) { diag.warn('Metadata cannot be set when using http'); } this._serializer = serializer; - // populate keepAlive for use with new settings - if (config?.keepAlive != null) { - if (config.httpAgentOptions != null) { - if (config.httpAgentOptions.keepAlive == null) { - // specific setting is not set, populate with non-specific setting. - config.httpAgentOptions.keepAlive = config.keepAlive; - } - // do nothing, use specific setting otherwise - } else { - // populate specific option if AgentOptions does not exist. - config.httpAgentOptions = { - keepAlive: config.keepAlive, - }; - } - } - const nonSignalSpecificHeaders = baggageUtils.parseKeyPairsIntoRecord( - getEnv().OTEL_EXPORTER_OTLP_HEADERS - ); - this._transport = createRetryingTransport({ transport: createHttpExporterTransport({ - agentOptions: config.httpAgentOptions ?? { keepAlive: true }, - compression: configureCompression(config.compression), - headers: Object.assign( - {}, - nonSignalSpecificHeaders, - signalSpecificHeaders - ), - url: this.url, + agentOptions: convertLegacyAgentOptions(config), + compression: actualConfig.compression, + headers: actualConfig.headers, + url: actualConfig.url, }), }); } - onInit(_config: OTLPExporterNodeConfigBase): void {} - send( objects: ExportItem[], onSuccess: () => void, @@ -100,7 +97,7 @@ export abstract class OTLPExporterNodeBase< } const promise = this._transport - .send(data, this.timeoutMillis) + .send(data, this._timeoutMillis) .then(response => { if (response.status === 'success') { onSuccess(); diff --git a/experimental/packages/otlp-exporter-base/src/platform/node/convert-legacy-agent-options.ts b/experimental/packages/otlp-exporter-base/src/platform/node/convert-legacy-agent-options.ts new file mode 100644 index 00000000000..a73c5b7ead7 --- /dev/null +++ b/experimental/packages/otlp-exporter-base/src/platform/node/convert-legacy-agent-options.ts @@ -0,0 +1,46 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { OTLPExporterNodeConfigBase } from './types'; +import type * as http from 'http'; +import type * as https from 'https'; + +/** + * Replicates old config behavior where there's two ways to set keepAlive. + * This function sets keepAlive in AgentOptions if it is defined. In the future, we will remove + * this duplicate to only allow setting keepAlive in AgentOptions. + * @param config + */ +export function convertLegacyAgentOptions( + config: OTLPExporterNodeConfigBase +): http.AgentOptions | https.AgentOptions { + // populate keepAlive for use with new settings + if (config?.keepAlive != null) { + if (config.httpAgentOptions != null) { + if (config.httpAgentOptions.keepAlive == null) { + // specific setting is not set, populate with non-specific setting. + config.httpAgentOptions.keepAlive = config.keepAlive; + } + // do nothing, use specific setting otherwise + } else { + // populate specific option if AgentOptions does not exist. + config.httpAgentOptions = { + keepAlive: config.keepAlive, + }; + } + } + + return config.httpAgentOptions ?? { keepAlive: true }; +} diff --git a/experimental/packages/otlp-exporter-base/src/platform/node/util.ts b/experimental/packages/otlp-exporter-base/src/platform/node/util.ts deleted file mode 100644 index 68125fb2993..00000000000 --- a/experimental/packages/otlp-exporter-base/src/platform/node/util.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { CompressionAlgorithm } from './types'; -import { getEnv } from '@opentelemetry/core'; - -export function configureCompression( - compression: CompressionAlgorithm | undefined -): CompressionAlgorithm { - if (compression) { - return compression; - } else { - const definedCompression = - getEnv().OTEL_EXPORTER_OTLP_TRACES_COMPRESSION || - getEnv().OTEL_EXPORTER_OTLP_COMPRESSION; - return definedCompression === CompressionAlgorithm.GZIP - ? CompressionAlgorithm.GZIP - : CompressionAlgorithm.NONE; - } -} diff --git a/experimental/packages/otlp-exporter-base/src/types.ts b/experimental/packages/otlp-exporter-base/src/types.ts index a8ee1db7432..7964f0048c1 100644 --- a/experimental/packages/otlp-exporter-base/src/types.ts +++ b/experimental/packages/otlp-exporter-base/src/types.ts @@ -45,8 +45,7 @@ export interface ExportServiceError { * Collector Exporter base config */ export interface OTLPExporterConfigBase { - headers?: Partial>; - hostname?: string; + headers?: Record; url?: string; concurrencyLimit?: number; /** Maximum time the OTLP exporter will wait for each batch export. diff --git a/experimental/packages/otlp-exporter-base/test/common/CollectorExporter.test.ts b/experimental/packages/otlp-exporter-base/test/common/CollectorExporter.test.ts index c6be6965e66..89874f353d1 100644 --- a/experimental/packages/otlp-exporter-base/test/common/CollectorExporter.test.ts +++ b/experimental/packages/otlp-exporter-base/test/common/CollectorExporter.test.ts @@ -27,7 +27,6 @@ class OTLPTraceExporter extends OTLPExporterBase< CollectorExporterConfig, ComplexTestObject > { - onInit() {} onShutdown() {} send( items: any[], @@ -55,12 +54,8 @@ describe('OTLPTraceExporter - common', () => { }); describe('constructor', () => { - let onInitSpy: any; - beforeEach(() => { - onInitSpy = sinon.stub(OTLPTraceExporter.prototype, 'onInit'); collectorExporterConfig = { - hostname: 'foo', url: 'http://foo.bar.com', }; collectorExporter = new OTLPTraceExporter(collectorExporterConfig); @@ -69,20 +64,6 @@ describe('OTLPTraceExporter - common', () => { it('should create an instance', () => { assert.ok(typeof collectorExporter !== 'undefined'); }); - - it('should call onInit', () => { - assert.strictEqual(onInitSpy.callCount, 1); - }); - - describe('when config contains certain params', () => { - it('should set hostname', () => { - assert.strictEqual(collectorExporter.hostname, 'foo'); - }); - - it('should set url', () => { - assert.strictEqual(collectorExporter.url, 'http://foo.bar.com'); - }); - }); }); describe('export', () => { @@ -190,7 +171,6 @@ describe('OTLPTraceExporter - common', () => { beforeEach(() => { onShutdownSpy = sinon.stub(OTLPTraceExporter.prototype, 'onShutdown'); collectorExporterConfig = { - hostname: 'foo', url: 'http://foo.bar.com', }; collectorExporter = new OTLPTraceExporter(collectorExporterConfig); diff --git a/experimental/packages/otlp-exporter-base/test/node/util.test.ts b/experimental/packages/otlp-exporter-base/test/node/util.test.ts index 19abbee9520..04d5685c77d 100644 --- a/experimental/packages/otlp-exporter-base/test/node/util.test.ts +++ b/experimental/packages/otlp-exporter-base/test/node/util.test.ts @@ -16,22 +16,13 @@ import * as assert from 'assert'; import { configureExporterTimeout, invalidTimeout } from '../../src/util'; -import { CompressionAlgorithm } from '../../src/platform/node/types'; -import { configureCompression } from '../../src/platform/node/util'; import { diag } from '@opentelemetry/api'; import * as sinon from 'sinon'; import { OTLPExporterNodeBase } from '../../src/platform/node/OTLPExporterNodeBase'; -import { OTLPExporterNodeConfigBase } from '../../src/platform/node/types'; import { ISerializer } from '@opentelemetry/otlp-transformer'; -// Barebones exporter for use by sendWithHttp -type ExporterConfig = OTLPExporterNodeConfigBase; -class Exporter extends OTLPExporterNodeBase { - getDefaultUrl(config: ExporterConfig): string { - return config.url || ''; - } -} +class Exporter extends OTLPExporterNodeBase {} const noopSerializer: ISerializer = { serializeRequest(request: object): Uint8Array | undefined { @@ -44,7 +35,7 @@ const noopSerializer: ISerializer = { describe('force flush', () => { it('forceFlush should flush spans and return', async () => { - const exporter = new Exporter({}, noopSerializer, {}); + const exporter = new Exporter({}, noopSerializer, {}, 'TEST', 'v1/test'); await exporter.forceFlush(); }); }); @@ -131,36 +122,3 @@ describe('invalidTimeout', () => { assert.strictEqual(defaultTimeout, 10000); }); }); - -describe('configureCompression', () => { - const envSource = process.env; - it('should return none for compression', () => { - const compression = CompressionAlgorithm.NONE; - assert.strictEqual( - configureCompression(compression), - CompressionAlgorithm.NONE - ); - }); - it('should return gzip compression defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_COMPRESSION = 'gzip'; - assert.strictEqual( - configureCompression(undefined), - CompressionAlgorithm.GZIP - ); - delete envSource.OTEL_EXPORTER_OTLP_TRACES_COMPRESSION; - }); - it('should return none for compression defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_COMPRESSION = 'none'; - assert.strictEqual( - configureCompression(undefined), - CompressionAlgorithm.NONE - ); - delete envSource.OTEL_EXPORTER_OTLP_TRACES_COMPRESSION; - }); - it('should return none for compression when no compression is set', () => { - assert.strictEqual( - configureCompression(undefined), - CompressionAlgorithm.NONE - ); - }); -}); diff --git a/experimental/packages/otlp-grpc-exporter-base/src/OTLPGRPCExporterNodeBase.ts b/experimental/packages/otlp-grpc-exporter-base/src/OTLPGRPCExporterNodeBase.ts index 6ad33ec77cc..e53976b3f4b 100644 --- a/experimental/packages/otlp-grpc-exporter-base/src/OTLPGRPCExporterNodeBase.ts +++ b/experimental/packages/otlp-grpc-exporter-base/src/OTLPGRPCExporterNodeBase.ts @@ -15,10 +15,8 @@ */ import { diag } from '@opentelemetry/api'; -import { GRPCQueueItem, OTLPGRPCExporterConfigNode } from './types'; -import { baggageUtils, getEnv } from '@opentelemetry/core'; +import { OTLPGRPCExporterConfigNode } from './types'; import { - CompressionAlgorithm, OTLPExporterBase, OTLPExporterError, } from '@opentelemetry/otlp-exporter-base'; @@ -26,9 +24,13 @@ import { createEmptyMetadata, GrpcExporterTransport, } from './grpc-exporter-transport'; -import { configureCompression, configureCredentials } from './util'; import { ISerializer } from '@opentelemetry/otlp-transformer'; import { IExporterTransport } from '@opentelemetry/otlp-exporter-base'; +import { + getOtlpGrpcDefaultConfiguration, + mergeOtlpGrpcConfigurationWithDefaults, +} from './otlp-grpc-configuration'; +import { getOtlpGrpcConfigurationFromEnv } from './otlp-grpc-env-configuration'; /** * OTLP Exporter abstract base class @@ -37,73 +39,55 @@ export abstract class OTLPGRPCExporterNodeBase< ExportItem, ServiceResponse, > extends OTLPExporterBase { - grpcQueue: GRPCQueueItem[] = []; - compression: CompressionAlgorithm; private _transport: IExporterTransport; private _serializer: ISerializer; + private _timeoutMillis: number; constructor( config: OTLPGRPCExporterConfigNode = {}, - signalSpecificMetadata: Record, + serializer: ISerializer, grpcName: string, grpcPath: string, - serializer: ISerializer + signalIdentifier: string ) { super(config); + // keep credentials locally in case user updates the reference on the config object + const userProvidedCredentials = config.credentials; + const actualConfig = mergeOtlpGrpcConfigurationWithDefaults( + { + url: config.url, + metadata: () => { + // metadata resolution strategy is merge, so we can return empty here, and it will not override the rest of the settings. + return config.metadata ?? createEmptyMetadata(); + }, + compression: config.compression, + timeoutMillis: config.timeoutMillis, + concurrencyLimit: config.concurrencyLimit, + credentials: + userProvidedCredentials != null + ? () => userProvidedCredentials + : undefined, + }, + getOtlpGrpcConfigurationFromEnv(signalIdentifier), + getOtlpGrpcDefaultConfiguration() + ); this._serializer = serializer; + this._timeoutMillis = actualConfig.timeoutMillis; + this._concurrencyLimit = actualConfig.concurrencyLimit; if (config.headers) { diag.warn('Headers cannot be set when using grpc'); } - const nonSignalSpecificMetadata = baggageUtils.parseKeyPairsIntoRecord( - getEnv().OTEL_EXPORTER_OTLP_HEADERS - ); - const rawMetadata = Object.assign( - {}, - nonSignalSpecificMetadata, - signalSpecificMetadata - ); - - let credentialProvider = () => { - return configureCredentials(undefined, this.getUrlFromConfig(config)); - }; - - if (config.credentials != null) { - const credentials = config.credentials; - credentialProvider = () => { - return credentials; - }; - } - // Ensure we don't modify the original. - const configMetadata = config.metadata?.clone(); - const metadataProvider = () => { - const metadata = configMetadata ?? createEmptyMetadata(); - for (const [key, value] of Object.entries(rawMetadata)) { - // only override with env var data if the key has no values. - // not using Metadata.merge() as it will keep both values. - if (metadata.get(key).length < 1) { - metadata.set(key, value); - } - } - - return metadata; - }; - - this.compression = configureCompression(config.compression); this._transport = new GrpcExporterTransport({ - address: this.getDefaultUrl(config), - compression: this.compression, - credentials: credentialProvider, + address: actualConfig.url, + compression: actualConfig.compression, + credentials: actualConfig.credentials, grpcName: grpcName, grpcPath: grpcPath, - metadata: metadataProvider, + metadata: actualConfig.metadata, }); } - onInit() { - // Intentionally left empty; nothing to do. - } - override onShutdown() { this._transport.shutdown(); } @@ -126,7 +110,7 @@ export abstract class OTLPGRPCExporterNodeBase< } const promise = this._transport - .send(data, this.timeoutMillis) + .send(data, this._timeoutMillis) .then(response => { if (response.status === 'success') { onSuccess(); @@ -146,6 +130,4 @@ export abstract class OTLPGRPCExporterNodeBase< }; promise.then(popPromise, popPromise); } - - abstract getUrlFromConfig(config: OTLPGRPCExporterConfigNode): string; } diff --git a/experimental/packages/otlp-grpc-exporter-base/src/index.ts b/experimental/packages/otlp-grpc-exporter-base/src/index.ts index 566b12f42f9..3a445ed91bf 100644 --- a/experimental/packages/otlp-grpc-exporter-base/src/index.ts +++ b/experimental/packages/otlp-grpc-exporter-base/src/index.ts @@ -16,4 +16,3 @@ export { OTLPGRPCExporterNodeBase } from './OTLPGRPCExporterNodeBase'; export { OTLPGRPCExporterConfigNode } from './types'; -export { DEFAULT_COLLECTOR_URL, validateAndNormalizeUrl } from './util'; diff --git a/experimental/packages/otlp-grpc-exporter-base/src/otlp-grpc-configuration.ts b/experimental/packages/otlp-grpc-exporter-base/src/otlp-grpc-configuration.ts new file mode 100644 index 00000000000..ea75d39166c --- /dev/null +++ b/experimental/packages/otlp-grpc-exporter-base/src/otlp-grpc-configuration.ts @@ -0,0 +1,136 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + getSharedConfigurationDefaults, + mergeOtlpSharedConfigurationWithDefaults, + OtlpSharedConfiguration, +} from '@opentelemetry/otlp-exporter-base'; +import { + createEmptyMetadata, + createInsecureCredentials, + createSslCredentials, +} from './grpc-exporter-transport'; +import { VERSION } from './version'; +import { URL } from 'url'; +import { diag } from '@opentelemetry/api'; + +// NOTE: do not change this to be an actual import, doing so will break `@opentelemetry/instrumentation-grpc` +import type { ChannelCredentials, Metadata } from '@grpc/grpc-js'; + +export interface OtlpGrpcConfiguration extends OtlpSharedConfiguration { + // TODO: rename to address? + url: string; + metadata: () => Metadata; + credentials: () => ChannelCredentials; +} + +export interface UnresolvedOtlpGrpcConfiguration + extends OtlpSharedConfiguration { + url: string; + metadata: () => Metadata; + // Credentials are based on the final resolved URL, so they need to be resolved last + credentials: (url: string) => () => ChannelCredentials; +} + +export function validateAndNormalizeUrl(url: string): string { + const hasProtocol = url.match(/^([\w]{1,8}):\/\//); + if (!hasProtocol) { + url = `https://${url}`; + } + const target = new URL(url); + if (target.protocol === 'unix:') { + return url; + } + if (target.pathname && target.pathname !== '/') { + diag.warn( + 'URL path should not be set when using grpc, the path part of the URL will be ignored.' + ); + } + if (target.protocol !== '' && !target.protocol?.match(/^(http)s?:$/)) { + diag.warn('URL protocol should be http(s)://. Using http://.'); + } + return target.host; +} + +function overrideMetadataEntriesIfNotPresent( + metadata: Metadata, + additionalMetadata: Metadata +): void { + for (const [key, value] of Object.entries(additionalMetadata.getMap())) { + // only override with env var data if the key has no values. + // not using Metadata.merge() as it will keep both values. + if (metadata.get(key).length < 1) { + metadata.set(key, value); + } + } +} + +export function mergeOtlpGrpcConfigurationWithDefaults( + userProvidedConfiguration: Partial, + fallbackConfiguration: Partial, + defaultConfiguration: UnresolvedOtlpGrpcConfiguration +): OtlpGrpcConfiguration { + const rawUrl = + userProvidedConfiguration.url ?? + fallbackConfiguration.url ?? + defaultConfiguration.url; + + return { + ...mergeOtlpSharedConfigurationWithDefaults( + userProvidedConfiguration, + fallbackConfiguration, + defaultConfiguration + ), + metadata: () => { + const metadata = defaultConfiguration.metadata(); + overrideMetadataEntriesIfNotPresent( + metadata, + // clone to ensure we don't modify what the user gave us in case they hold on to the returned reference + userProvidedConfiguration.metadata?.().clone() ?? createEmptyMetadata() + ); + overrideMetadataEntriesIfNotPresent( + metadata, + fallbackConfiguration.metadata?.() ?? createEmptyMetadata() + ); + return metadata; + }, + url: validateAndNormalizeUrl(rawUrl), + credentials: + userProvidedConfiguration.credentials ?? + fallbackConfiguration.credentials?.(rawUrl) ?? + defaultConfiguration.credentials(rawUrl), + }; +} + +export function getOtlpGrpcDefaultConfiguration(): UnresolvedOtlpGrpcConfiguration { + return { + ...getSharedConfigurationDefaults(), + metadata: () => { + const metadata = createEmptyMetadata(); + metadata.set('User-Agent', `OTel-OTLP-Exporter-JavaScript/${VERSION}`); + return metadata; + }, + url: 'http://localhost:4317', + credentials: (url: string) => { + if (url.startsWith('http://')) { + return () => createInsecureCredentials(); + } else { + return () => createSslCredentials(); + } + }, + }; +} diff --git a/experimental/packages/otlp-grpc-exporter-base/src/otlp-grpc-env-configuration.ts b/experimental/packages/otlp-grpc-exporter-base/src/otlp-grpc-env-configuration.ts new file mode 100644 index 00000000000..11c81d901c5 --- /dev/null +++ b/experimental/packages/otlp-grpc-exporter-base/src/otlp-grpc-env-configuration.ts @@ -0,0 +1,170 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { UnresolvedOtlpGrpcConfiguration } from './otlp-grpc-configuration'; +import type { ChannelCredentials, Metadata } from '@grpc/grpc-js'; +import { baggageUtils } from '@opentelemetry/core'; +import { + createEmptyMetadata, + createInsecureCredentials, + createSslCredentials, +} from './grpc-exporter-transport'; +import { getSharedConfigurationFromEnvironment } from '@opentelemetry/otlp-exporter-base'; +import * as fs from 'fs'; +import * as path from 'path'; +import { diag } from '@opentelemetry/api'; + +function getMetadataFromEnv(signalIdentifier: string): Metadata { + const signalSpecificHeaders = baggageUtils.parseKeyPairsIntoRecord( + process.env[`OTEL_EXPORTER_OTLP_${signalIdentifier}_HEADERS`] + ); + const nonSignalSpecificHeaders = baggageUtils.parseKeyPairsIntoRecord( + process.env[`OTEL_EXPORTER_OTLP_HEADERS`] + ); + + const mergeHeaders = Object.assign( + {}, + nonSignalSpecificHeaders, + signalSpecificHeaders + ); + + const metadata = createEmptyMetadata(); + + // for this to work, metadata MUST be empty - otherwise `Metadata#set()` will merge items. + for (const [key, value] of Object.entries(mergeHeaders)) { + metadata.set(key, value); + } + + return metadata; +} + +function getUrlFromEnv(signalIdentifier: string) { + return ( + process.env[`OTEL_EXPORTER_OTLP_${signalIdentifier}_ENDPOINT`] ?? + process.env[`OTEL_EXPORTER_OTLP_ENDPOINT`] + ); +} + +function getInsecureSettingFromEnv(signalIdentifier: string): boolean { + const signalSpecificInsecureValue = process.env[ + `OTEL_EXPORTER_OTLP_${signalIdentifier}_INSECURE` + ] + ?.toLowerCase() + .trim(); + const nonSignalSpecificInsecureValue = process.env[ + `OTEL_EXPORTER_OTLP_INSECURE` + ] + ?.toLowerCase() + .trim(); + + if (signalSpecificInsecureValue != null) { + return signalSpecificInsecureValue === 'true'; + } + if (nonSignalSpecificInsecureValue != null) { + return nonSignalSpecificInsecureValue === 'true'; + } + + return false; +} + +function retrieveCertChain(signalIdentifier: string): Buffer | undefined { + const clientChain = + process.env[ + `OTEL_EXPORTER_OTLP_${signalIdentifier}_CLIENT_CERTIFICATE` + ]?.trim() ?? process.env['OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE']?.trim(); + + if (clientChain) { + try { + return fs.readFileSync(path.resolve(process.cwd(), clientChain)); + } catch { + diag.warn('Failed to read client certificate chain file'); + return undefined; + } + } else { + return undefined; + } +} + +function retrievePrivateKey(signalIdentifier: string): Buffer | undefined { + const clientKey = + process.env[`OTEL_EXPORTER_OTLP_${signalIdentifier}_CLIENT_KEY`]?.trim() || + process.env[`OTEL_EXPORTER_OTLP_CLIENT_KEY`]?.trim(); + + if (clientKey) { + try { + return fs.readFileSync(path.resolve(process.cwd(), clientKey)); + } catch { + diag.warn('Failed to read client certificate private key file'); + return undefined; + } + } else { + return undefined; + } +} + +function retrieveRootCert(signalIdentifier: string): Buffer | undefined { + const rootCertificate = + process.env[`OTEL_EXPORTER_OTLP_${signalIdentifier}_CERTIFICATE`]?.trim() || + process.env[`OTEL_EXPORTER_OTLP_CERTIFICATE`]?.trim(); + + if (rootCertificate) { + try { + return fs.readFileSync(path.resolve(process.cwd(), rootCertificate)); + } catch { + diag.warn('Failed to read root certificate file'); + return undefined; + } + } else { + return undefined; + } +} + +function getCredentialsFromEnv(signalIdentifier: string): ChannelCredentials { + if (getInsecureSettingFromEnv(signalIdentifier)) { + return createInsecureCredentials(); + } + + return createSslCredentials( + retrieveRootCert(signalIdentifier), + retrievePrivateKey(signalIdentifier), + retrieveCertChain(signalIdentifier) + ); +} + +export function getOtlpGrpcConfigurationFromEnv( + signalIdentifier: string +): Partial { + // special case url = default? = insecure credentials? possibly needs to be done on final merge. + const rawUrl = getUrlFromEnv(signalIdentifier); + return { + ...getSharedConfigurationFromEnvironment(signalIdentifier), + metadata: () => getMetadataFromEnv(signalIdentifier), + url: rawUrl, + credentials: (url: string) => { + // on http always assume insecure + if (url.startsWith('http://')) { + return () => { + return createInsecureCredentials(); + }; + } + + // defer to env settings in this case + // TODO: what to do with unix:// ? + return () => { + return getCredentialsFromEnv(signalIdentifier); + }; + }, + }; +} diff --git a/experimental/packages/otlp-grpc-exporter-base/src/types.ts b/experimental/packages/otlp-grpc-exporter-base/src/types.ts index 43caad1371a..61f5613de30 100644 --- a/experimental/packages/otlp-grpc-exporter-base/src/types.ts +++ b/experimental/packages/otlp-grpc-exporter-base/src/types.ts @@ -19,19 +19,8 @@ import type { ChannelCredentials, Metadata } from '@grpc/grpc-js'; import { CompressionAlgorithm, OTLPExporterConfigBase, - OTLPExporterError, } from '@opentelemetry/otlp-exporter-base'; -/** - * Queue item to be used to save temporary spans/metrics/logs in case the GRPC service - * hasn't been fully initialized yet - */ -export interface GRPCQueueItem { - objects: ExportedItem[]; - onSuccess: () => void; - onError: (error: OTLPExporterError) => void; -} - /** * OTLP Exporter Config for Node */ diff --git a/experimental/packages/otlp-grpc-exporter-base/src/util.ts b/experimental/packages/otlp-grpc-exporter-base/src/util.ts deleted file mode 100644 index 4386b341694..00000000000 --- a/experimental/packages/otlp-grpc-exporter-base/src/util.ts +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { diag } from '@opentelemetry/api'; -import { getEnv } from '@opentelemetry/core'; -import * as path from 'path'; -import { URL } from 'url'; -import * as fs from 'fs'; -import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base'; -import { - createInsecureCredentials, - createSslCredentials, -} from './grpc-exporter-transport'; - -// NOTE: do not change these type imports to actual imports. Doing so WILL break `@opentelemetry/instrumentation-http`, -// as they'd be imported before the http/https modules can be wrapped. -import type { ChannelCredentials } from '@grpc/grpc-js'; - -export const DEFAULT_COLLECTOR_URL = 'http://localhost:4317'; - -export function validateAndNormalizeUrl(url: string): string { - const hasProtocol = url.match(/^([\w]{1,8}):\/\//); - if (!hasProtocol) { - url = `https://${url}`; - } - const target = new URL(url); - if (target.protocol === 'unix:') { - return url; - } - if (target.pathname && target.pathname !== '/') { - diag.warn( - 'URL path should not be set when using grpc, the path part of the URL will be ignored.' - ); - } - if (target.protocol !== '' && !target.protocol?.match(/^(http)s?:$/)) { - diag.warn('URL protocol should be http(s)://. Using http://.'); - } - return target.host; -} - -export function configureCredentials( - credentials: ChannelCredentials | undefined, - endpoint: string -): ChannelCredentials { - let insecure: boolean; - - if (credentials) { - return credentials; - } else if (endpoint.startsWith('https://')) { - insecure = false; - } else if ( - endpoint.startsWith('http://') || - endpoint === DEFAULT_COLLECTOR_URL - ) { - insecure = true; - } else { - insecure = getSecurityFromEnv(); - } - - if (insecure) { - return createInsecureCredentials(); - } else { - return getCredentialsFromEnvironment(); - } -} - -function getSecurityFromEnv(): boolean { - const definedInsecure = - getEnv().OTEL_EXPORTER_OTLP_TRACES_INSECURE || - getEnv().OTEL_EXPORTER_OTLP_INSECURE; - - if (definedInsecure) { - return definedInsecure.toLowerCase() === 'true'; - } else { - return false; - } -} - -/** - * Exported for testing - */ -export function getCredentialsFromEnvironment(): ChannelCredentials { - const rootCert = retrieveRootCert(); - const privateKey = retrievePrivateKey(); - const certChain = retrieveCertChain(); - - return createSslCredentials(rootCert, privateKey, certChain); -} - -function retrieveRootCert(): Buffer | undefined { - const rootCertificate = - getEnv().OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE || - getEnv().OTEL_EXPORTER_OTLP_CERTIFICATE; - - if (rootCertificate) { - try { - return fs.readFileSync(path.resolve(process.cwd(), rootCertificate)); - } catch { - diag.warn('Failed to read root certificate file'); - return undefined; - } - } else { - return undefined; - } -} - -function retrievePrivateKey(): Buffer | undefined { - const clientKey = - getEnv().OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY || - getEnv().OTEL_EXPORTER_OTLP_CLIENT_KEY; - - if (clientKey) { - try { - return fs.readFileSync(path.resolve(process.cwd(), clientKey)); - } catch { - diag.warn('Failed to read client certificate private key file'); - return undefined; - } - } else { - return undefined; - } -} - -function retrieveCertChain(): Buffer | undefined { - const clientChain = - getEnv().OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE || - getEnv().OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE; - - if (clientChain) { - try { - return fs.readFileSync(path.resolve(process.cwd(), clientChain)); - } catch { - diag.warn('Failed to read client certificate chain file'); - return undefined; - } - } else { - return undefined; - } -} - -export function configureCompression( - compression: CompressionAlgorithm | undefined -): CompressionAlgorithm { - if (compression != null) { - return compression; - } - - const envCompression = - getEnv().OTEL_EXPORTER_OTLP_TRACES_COMPRESSION || - getEnv().OTEL_EXPORTER_OTLP_COMPRESSION; - - if (envCompression === 'gzip') { - return CompressionAlgorithm.GZIP; - } else if (envCompression === 'none') { - return CompressionAlgorithm.NONE; - } - - diag.warn( - 'Unknown compression "' + envCompression + '", falling back to "none"' - ); - return CompressionAlgorithm.NONE; -} diff --git a/experimental/packages/otlp-grpc-exporter-base/test/OTLPGRPCExporterNodeBase.test.ts b/experimental/packages/otlp-grpc-exporter-base/test/OTLPGRPCExporterNodeBase.test.ts index 99677dd05a7..e04d9653e4a 100644 --- a/experimental/packages/otlp-grpc-exporter-base/test/OTLPGRPCExporterNodeBase.test.ts +++ b/experimental/packages/otlp-grpc-exporter-base/test/OTLPGRPCExporterNodeBase.test.ts @@ -55,9 +55,6 @@ describe('OTLPGRPCExporterNodeBase', () => { shutdown: sinon.stub(), }; const mockTransport = transportStubs; - const signalSpecificMetadata: Record = { - key: 'signal-specific-metadata', - }; const serializerStubs = { serializeRequest: sinon.stub().resolves(Buffer.from([1, 2, 3])), @@ -70,10 +67,10 @@ describe('OTLPGRPCExporterNodeBase', () => { exporter = new MockCollectorExporter( { concurrencyLimit }, - signalSpecificMetadata, + serializer, 'grpcName', 'grpcPath', - serializer + 'SIGNAL' ); exporter['_transport'] = mockTransport; diff --git a/experimental/packages/otlp-grpc-exporter-base/test/otlp-grpc-configuration.test.ts b/experimental/packages/otlp-grpc-exporter-base/test/otlp-grpc-configuration.test.ts new file mode 100644 index 00000000000..85fded0f7cc --- /dev/null +++ b/experimental/packages/otlp-grpc-exporter-base/test/otlp-grpc-configuration.test.ts @@ -0,0 +1,83 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { diag } from '@opentelemetry/api'; +import * as sinon from 'sinon'; +import * as assert from 'assert'; +import { validateAndNormalizeUrl } from '../src/otlp-grpc-configuration'; + +describe('validateAndNormalizeUrl()', () => { + const tests = [ + { + name: 'bare hostname should return same value', + input: 'api.datacat.io', + expected: 'api.datacat.io', + }, + { + name: 'host:port should return same value', + input: 'api.datacat.io:1234', + expected: 'api.datacat.io:1234', + }, + { + name: 'https://host:port should trim off protocol', + input: 'https://api.datacat.io:1234', + expected: 'api.datacat.io:1234', + }, + { + name: 'should accept unix socket', + input: 'unix:///tmp/grpc.sock', + expected: 'unix:///tmp/grpc.sock', + }, + { + name: 'bad protocol should warn but return host:port', + input: 'badproto://api.datacat.io:1234', + expected: 'api.datacat.io:1234', + warn: 'URL protocol should be http(s)://. Using http://.', + }, + { + name: 'path on end of url should warn but return host:port', + input: 'http://api.datacat.io:1234/a/b/c', + expected: 'api.datacat.io:1234', + warn: 'URL path should not be set when using grpc, the path part of the URL will be ignored.', + }, + { + name: ':// in path should not be used for protocol even if protocol not specified', + input: 'api.datacat.io/a/b://c', + expected: 'api.datacat.io', + warn: 'URL path should not be set when using grpc, the path part of the URL will be ignored.', + }, + { + name: ':// in path is valid when a protocol is specified', + input: 'http://api.datacat.io/a/b://c', + expected: 'api.datacat.io', + warn: 'URL path should not be set when using grpc, the path part of the URL will be ignored.', + }, + ]; + tests.forEach(test => { + it(test.name, () => { + const diagWarn = sinon.stub(diag, 'warn'); + try { + assert.strictEqual(validateAndNormalizeUrl(test.input), test.expected); + if (test.warn) { + sinon.assert.calledWith(diagWarn, test.warn); + } else { + sinon.assert.notCalled(diagWarn); + } + } finally { + diagWarn.restore(); + } + }); + }); +}); diff --git a/experimental/packages/otlp-grpc-exporter-base/test/util.test.ts b/experimental/packages/otlp-grpc-exporter-base/test/util.test.ts deleted file mode 100644 index 1da067b559c..00000000000 --- a/experimental/packages/otlp-grpc-exporter-base/test/util.test.ts +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as sinon from 'sinon'; -import * as assert from 'assert'; - -import { diag } from '@opentelemetry/api'; -import * as grpc from '@grpc/grpc-js'; -import { - validateAndNormalizeUrl, - configureCompression, - configureCredentials, - getCredentialsFromEnvironment, - DEFAULT_COLLECTOR_URL, -} from '../src/util'; -import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base'; - -// Tests added to detect breakage released in #2130 -describe('validateAndNormalizeUrl()', () => { - const tests = [ - { - name: 'bare hostname should return same value', - input: 'api.datacat.io', - expected: 'api.datacat.io', - }, - { - name: 'host:port should return same value', - input: 'api.datacat.io:1234', - expected: 'api.datacat.io:1234', - }, - { - name: 'https://host:port should trim off protocol', - input: 'https://api.datacat.io:1234', - expected: 'api.datacat.io:1234', - }, - { - name: 'should accept unix socket', - input: 'unix:///tmp/grpc.sock', - expected: 'unix:///tmp/grpc.sock', - }, - { - name: 'bad protocol should warn but return host:port', - input: 'badproto://api.datacat.io:1234', - expected: 'api.datacat.io:1234', - warn: 'URL protocol should be http(s)://. Using http://.', - }, - { - name: 'path on end of url should warn but return host:port', - input: 'http://api.datacat.io:1234/a/b/c', - expected: 'api.datacat.io:1234', - warn: 'URL path should not be set when using grpc, the path part of the URL will be ignored.', - }, - { - name: ':// in path should not be used for protocol even if protocol not specified', - input: 'api.datacat.io/a/b://c', - expected: 'api.datacat.io', - warn: 'URL path should not be set when using grpc, the path part of the URL will be ignored.', - }, - { - name: ':// in path is valid when a protocol is specified', - input: 'http://api.datacat.io/a/b://c', - expected: 'api.datacat.io', - warn: 'URL path should not be set when using grpc, the path part of the URL will be ignored.', - }, - ]; - tests.forEach(test => { - it(test.name, () => { - const diagWarn = sinon.stub(diag, 'warn'); - try { - assert.strictEqual(validateAndNormalizeUrl(test.input), test.expected); - if (test.warn) { - sinon.assert.calledWith(diagWarn, test.warn); - } else { - sinon.assert.notCalled(diagWarn); - } - } finally { - diagWarn.restore(); - } - }); - }); -}); - -describe('utils - configureCredentials', () => { - const envSource = process.env; - it('should return insecure channel when using all defaults', () => { - const credentials = configureCredentials(undefined, DEFAULT_COLLECTOR_URL); - assert.ok(credentials._isSecure() === false); - }); - it('should return user defined channel credentials', () => { - const userDefinedCredentials = grpc.credentials.createSsl(); - const credentials = configureCredentials( - userDefinedCredentials, - 'http://foo.bar' - ); - - assert.ok(userDefinedCredentials === credentials); - assert.ok(credentials._isSecure() === true); - }); - it('should return secure channel when endpoint contains https scheme - no matter insecure env settings,', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_INSECURE = 'true'; - const credentials = configureCredentials(undefined, 'https://foo.bar'); - assert.ok(credentials._isSecure() === true); - delete envSource.OTEL_EXPORTER_OTLP_TRACES_INSECURE; - }); - - it('should return insecure channel when endpoint contains http scheme and no insecure env settings', () => { - const credentials = configureCredentials(undefined, 'http://foo.bar'); - assert.ok(credentials._isSecure() === false); - }); - it('should return secure channel when endpoint does not contain scheme and no insecure env settings', () => { - const credentials = configureCredentials(undefined, 'foo.bar'); - assert.ok(credentials._isSecure() === true); - }); - it('should return insecure channel when endpoint contains http scheme and insecure env set to false', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_INSECURE = 'false'; - const credentials = configureCredentials(undefined, 'http://foo.bar'); - assert.ok(credentials._isSecure() === false); - delete envSource.OTEL_EXPORTER_OTLP_TRACES_INSECURE; - }); - it('should return insecure channel when endpoint contains http scheme and insecure env set to true', () => { - envSource.OTEL_EXPORTER_OTLP_INSECURE = 'true'; - const credentials = configureCredentials(undefined, 'http://localhost'); - assert.ok(credentials._isSecure() === false); - delete envSource.OTEL_EXPORTER_OTLP_INSECURE; - }); - it('should return secure channel when endpoint does not contain scheme and insecure env set to false', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_INSECURE = 'false'; - const credentials = configureCredentials(undefined, 'foo.bar'); - assert.ok(credentials._isSecure() === true); - delete envSource.OTEL_EXPORTER_OTLP_TRACES_INSECURE; - }); - it('should return insecure channel when endpoint does not contain scheme and insecure env set to true', () => { - envSource.OTEL_EXPORTER_OTLP_INSECURE = 'true'; - const credentials = configureCredentials(undefined, 'foo.bar'); - assert.ok(credentials._isSecure() === false); - delete envSource.OTEL_EXPORTER_OTLP_INSECURE; - }); -}); - -describe('useSecureConnection', () => { - const envSource = process.env; - it('should return secure connection using all credentials', () => { - envSource.OTEL_EXPORTER_OTLP_CERTIFICATE = './test/certs/ca.crt'; - envSource.OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY = './test/certs/client.key'; - envSource.OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE = - './test/certs/client.crt'; - - const credentials = getCredentialsFromEnvironment(); - assert.ok(credentials._isSecure() === true); - - delete envSource.OTEL_EXPORTER_OTLP_CERTIFICATE; - delete envSource.OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY; - delete envSource.OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE; - }); - it('should return secure connection using only root certificate', () => { - envSource.OTEL_EXPORTER_OTLP_CERTIFICATE = './test/certs/ca.crt'; - const credentials = getCredentialsFromEnvironment(); - assert.ok(credentials._isSecure() === true); - delete envSource.OTEL_EXPORTER_OTLP_CERTIFICATE; - }); - it('should warn user when file cannot be read and use default root certificate', () => { - envSource.OTEL_EXPORTER_OTLP_CERTIFICATE = './wrongpath/test/certs/ca.crt'; - const diagWarn = sinon.stub(diag, 'warn'); - const credentials = getCredentialsFromEnvironment(); - const args = diagWarn.args[0]; - - assert.strictEqual(args[0], 'Failed to read root certificate file'); - sinon.assert.calledOnce(diagWarn); - assert.ok(credentials._isSecure() === true); - - delete envSource.OTEL_EXPORTER_OTLP_CERTIFICATE; - diagWarn.restore(); - }); -}); - -describe('configureCompression', () => { - const envSource = process.env; - it('should return none for compression', () => { - const compression = CompressionAlgorithm.NONE; - assert.strictEqual( - configureCompression(compression), - CompressionAlgorithm.NONE - ); - }); - it('should return gzip compression defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_COMPRESSION = 'gzip'; - assert.strictEqual( - configureCompression(undefined), - CompressionAlgorithm.GZIP - ); - delete envSource.OTEL_EXPORTER_OTLP_TRACES_COMPRESSION; - }); - it('should return none for compression defined via env', () => { - envSource.OTEL_EXPORTER_OTLP_TRACES_COMPRESSION = 'none'; - assert.strictEqual( - configureCompression(undefined), - CompressionAlgorithm.NONE - ); - delete envSource.OTEL_EXPORTER_OTLP_TRACES_COMPRESSION; - }); - it('should return none for compression when no compression is set', () => { - assert.strictEqual( - configureCompression(undefined), - CompressionAlgorithm.NONE - ); - }); -});