diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index 1c77eafbada..04482240f8c 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -8,6 +8,8 @@ All notable changes to experimental packages in this project will be documented ### :rocket: (Enhancement) +* feat(sdk-node): configure trace exporter with environment variables [#3143](https://github.com/open-telemetry/opentelemetry-js/pull/3143) @svetlanabrennan + ### :bug: (Bug Fix) ### :books: (Refine Doc) diff --git a/experimental/packages/opentelemetry-sdk-node/README.md b/experimental/packages/opentelemetry-sdk-node/README.md index 97027ff7fb0..0d4f3c0f7a8 100644 --- a/experimental/packages/opentelemetry-sdk-node/README.md +++ b/experimental/packages/opentelemetry-sdk-node/README.md @@ -129,7 +129,7 @@ Configure a custom sampler. By default, all traces will be sampled. ### traceExporter -Configure a trace exporter. If an exporter OR span processor is not configured, the tracing SDK will not be initialized and registered. If an exporter is configured, it will be used with a [BatchSpanProcessor](../../../packages/opentelemetry-sdk-trace-base/src/platform/node/export/BatchSpanProcessor.ts). +Configure a trace exporter. If an exporter is configured, it will be used with a [BatchSpanProcessor](../../../packages/opentelemetry-sdk-trace-base/src/platform/node/export/BatchSpanProcessor.ts). If an exporter OR span processor is not configured programatically, this package will auto setup the default `otlp` exporter with `http/protobuf` protocol with a `BatchSpanProcessor`. ### spanLimits @@ -139,6 +139,30 @@ Configure tracing parameters. These are the same trace parameters used to [confi Configure the [service name](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/README.md#service). +## Configure Trace Exporter from Environment + +This is an alternative to programmatically configuring an exporter or span processor. This package will auto setup the default `otlp` exporter with `http/protobuf` protocol if `traceExporter` or `spanProcessor` hasn't been passed into the `NodeSDK` constructor. + +### Exporters + +| Environment variable | Description | +|----------------------|-------------| +| OTEL_TRACES_EXPORTER | List of exporters to be used for tracing, separated by commas. Options include `otlp`, `jaeger`, `zipkin`, and `none`. Default is `otlp`. `none` means no autoconfigured exporter. + +### OTLP Exporter + +| Environment variable | Description | +|----------------------|-------------| +| OTEL_EXPORTER_OTLP_PROTOCOL | The transport protocol to use on OTLP trace, metric, and log requests. Options include `grpc`, `http/protobuf`, and `http/json`. Default is `http/protobuf`. | +| OTEL_EXPORTER_OTLP_TRACES_PROTOCOL | The transport protocol to use on OTLP trace requests. Options include `grpc`, `http/protobuf`, and `http/json`. Default is `http/protobuf`. | +| OTEL_EXPORTER_OTLP_METRICS_PROTOCOL | The transport protocol to use on OTLP metric requests. Options include `grpc`, `http/protobuf`, and `http/json`. Default is `http/protobuf`. | + +Additionally, you can specify other applicable environment variables that apply to each exporter such as the following: + +- [OTLP exporter environment configuration](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options) +- [Zipkin exporter environment configuration](https://github.com/open-telemetry/opentelemetry-specification/blob/6ce62202e5407518e19c56c445c13682ef51a51d/specification/sdk-environment-variables.md#zipkin-exporter) +- [Jaeger exporter environment configuration](https://github.com/open-telemetry/opentelemetry-specification/blob/6ce62202e5407518e19c56c445c13682ef51a51d/specification/sdk-environment-variables.md#jaeger-exporter) + ## Useful links - For more information on OpenTelemetry, visit: diff --git a/experimental/packages/opentelemetry-sdk-node/package.json b/experimental/packages/opentelemetry-sdk-node/package.json index 8bc140b6858..d4f0f48a381 100644 --- a/experimental/packages/opentelemetry-sdk-node/package.json +++ b/experimental/packages/opentelemetry-sdk-node/package.json @@ -44,6 +44,11 @@ "access": "public" }, "dependencies": { + "@opentelemetry/exporter-jaeger": "1.7.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.33.0", + "@opentelemetry/exporter-trace-otlp-http": "0.33.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.33.0", + "@opentelemetry/exporter-zipkin": "1.7.0", "@opentelemetry/api-metrics": "0.33.0", "@opentelemetry/core": "1.7.0", "@opentelemetry/instrumentation": "0.33.0", diff --git a/experimental/packages/opentelemetry-sdk-node/src/TracerProviderWithEnvExporter.ts b/experimental/packages/opentelemetry-sdk-node/src/TracerProviderWithEnvExporter.ts new file mode 100644 index 00000000000..f4abb593abe --- /dev/null +++ b/experimental/packages/opentelemetry-sdk-node/src/TracerProviderWithEnvExporter.ts @@ -0,0 +1,124 @@ +/* + * 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, getEnvWithoutDefaults } from '@opentelemetry/core'; +import { ConsoleSpanExporter, SpanExporter, BatchSpanProcessor, SimpleSpanProcessor, SDKRegistrationConfig, SpanProcessor } from '@opentelemetry/sdk-trace-base'; +import { NodeTracerConfig, NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; +import { OTLPTraceExporter as OTLPProtoTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'; +import { OTLPTraceExporter as OTLPHttpTraceExporter} from '@opentelemetry/exporter-trace-otlp-http'; +import { OTLPTraceExporter as OTLPGrpcTraceExporter} from '@opentelemetry/exporter-trace-otlp-grpc'; +import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'; +import { JaegerExporter } from '@opentelemetry/exporter-jaeger'; + +export class TracerProviderWithEnvExporters extends NodeTracerProvider { + private _configuredExporters: SpanExporter[] = []; + private _spanProcessors: SpanProcessor[] | undefined; + private _hasSpanProcessors: boolean = false; + + static configureOtlp(): SpanExporter { + const protocol = this.getOtlpProtocol(); + + switch (protocol) { + case 'grpc': + return new OTLPGrpcTraceExporter; + case 'http/json': + return new OTLPHttpTraceExporter; + case 'http/protobuf': + return new OTLPProtoTraceExporter; + default: + diag.warn(`Unsupported OTLP traces protocol: ${protocol}. Using http/protobuf.`); + return new OTLPProtoTraceExporter; + } + } + + static getOtlpProtocol(): string { + const parsedEnvValues = getEnvWithoutDefaults(); + + return parsedEnvValues.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL ?? + parsedEnvValues.OTEL_EXPORTER_OTLP_PROTOCOL ?? + getEnv().OTEL_EXPORTER_OTLP_TRACES_PROTOCOL ?? + getEnv().OTEL_EXPORTER_OTLP_PROTOCOL; + } + + protected static override _registeredExporters = new Map< + string, + () => SpanExporter + >([ + ['otlp', () => this.configureOtlp()], + ['zipkin', () => new ZipkinExporter], + ['jaeger', () => new JaegerExporter], + ['console', () => new ConsoleSpanExporter] + ]); + + public constructor(config: NodeTracerConfig = {}) { + super(config); + let traceExportersList = this.filterBlanksAndNulls(Array.from(new Set(getEnv().OTEL_TRACES_EXPORTER.split(',')))); + + if (traceExportersList.length === 0 || traceExportersList[0] === 'none') { + diag.warn('OTEL_TRACES_EXPORTER contains "none" or is empty. SDK will not be initialized.'); + } else { + if (traceExportersList.length > 1 && traceExportersList.includes('none')) { + diag.warn('OTEL_TRACES_EXPORTER contains "none" along with other exporters. Using default otlp exporter.'); + traceExportersList = ['otlp']; + } + + traceExportersList.forEach(exporterName => { + const exporter = this._getSpanExporter(exporterName); + if (exporter) { + this._configuredExporters.push(exporter); + } else { + diag.warn(`Unrecognized OTEL_TRACES_EXPORTER value: ${exporterName}.`); + } + }); + + if (this._configuredExporters.length > 0) { + this._spanProcessors = this.configureSpanProcessors(this._configuredExporters); + this._spanProcessors.forEach(processor => { + this.addSpanProcessor(processor); + }); + } else { + diag.warn('Unable to set up trace exporter(s) due to invalid exporter and/or protocol values.'); + } + } + } + + override addSpanProcessor(spanProcessor: SpanProcessor) { + super.addSpanProcessor(spanProcessor); + this._hasSpanProcessors = true; + } + + override register(config?: SDKRegistrationConfig) { + if (this._hasSpanProcessors) { + super.register(config); + } + } + + private configureSpanProcessors(exporters: SpanExporter[]): (BatchSpanProcessor | SimpleSpanProcessor)[] { + return exporters.map(exporter => { + if (exporter instanceof ConsoleSpanExporter) { + return new SimpleSpanProcessor(exporter); + } else { + return new BatchSpanProcessor(exporter); + } + }); + } + + private filterBlanksAndNulls(list: string[]): string[] { + return list.map(item => item.trim()) + .filter(s => s !== 'null' && s !== ''); + } +} diff --git a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts index a48028760bb..bb20ed94402 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/sdk.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/sdk.ts @@ -31,11 +31,12 @@ import { import { MeterProvider, MetricReader, View } from '@opentelemetry/sdk-metrics'; import { BatchSpanProcessor, - SpanProcessor + SpanProcessor, } from '@opentelemetry/sdk-trace-base'; import { NodeTracerConfig, NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { NodeSDKConfiguration } from './types'; +import { TracerProviderWithEnvExporters } from './TracerProviderWithEnvExporter'; /** This class represents everything needed to register a fully configured OpenTelemetry Node.js SDK */ @@ -64,7 +65,7 @@ export class NodeSDK { private _autoDetectResources: boolean; - private _tracerProvider?: NodeTracerProvider; + private _tracerProvider?: NodeTracerProvider | TracerProviderWithEnvExporters; private _meterProvider?: MeterProvider; private _serviceName?: string; @@ -193,21 +194,25 @@ export class NodeSDK { { [SemanticResourceAttributes.SERVICE_NAME]: this._serviceName } )); - if (this._tracerProviderConfig) { - const tracerProvider = new NodeTracerProvider({ - ...this._tracerProviderConfig.tracerConfig, - resource: this._resource, - }); + const Provider = + this._tracerProviderConfig ? NodeTracerProvider : TracerProviderWithEnvExporters; + + const tracerProvider = new Provider ({ + ...this._tracerProviderConfig?.tracerConfig, + resource: this._resource, + }); - this._tracerProvider = tracerProvider; + this._tracerProvider = tracerProvider; + if (this._tracerProviderConfig) { tracerProvider.addSpanProcessor(this._tracerProviderConfig.spanProcessor); - tracerProvider.register({ - contextManager: this._tracerProviderConfig.contextManager, - propagator: this._tracerProviderConfig.textMapPropagator, - }); } + tracerProvider.register({ + contextManager: this._tracerProviderConfig?.contextManager, + propagator: this._tracerProviderConfig?.textMapPropagator, + }); + if (this._meterProviderConfig) { const meterProvider = new MeterProvider({ resource: this._resource, diff --git a/experimental/packages/opentelemetry-sdk-node/test/TracerProviderWithEnvExporter.test.ts b/experimental/packages/opentelemetry-sdk-node/test/TracerProviderWithEnvExporter.test.ts new file mode 100644 index 00000000000..2ba4d0394a7 --- /dev/null +++ b/experimental/packages/opentelemetry-sdk-node/test/TracerProviderWithEnvExporter.test.ts @@ -0,0 +1,283 @@ +/* + * 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 { + ConsoleSpanExporter, + SimpleSpanProcessor, + BatchSpanProcessor, +} from '@opentelemetry/sdk-trace-base'; +import * as assert from 'assert'; +import * as Sinon from 'sinon'; +import { env } from 'process'; +import { OTLPTraceExporter as OTLPProtoTraceExporter, OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'; +import { OTLPTraceExporter as OTLPHttpTraceExporter} from '@opentelemetry/exporter-trace-otlp-http'; +import { OTLPTraceExporter as OTLPGrpcTraceExporter} from '@opentelemetry/exporter-trace-otlp-grpc'; +import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'; +import { JaegerExporter } from '@opentelemetry/exporter-jaeger'; +import { TracerProviderWithEnvExporters } from '../src/TracerProviderWithEnvExporter'; + +describe('set up trace exporter with env exporters', () => { + let spyGetOtlpProtocol: Sinon.SinonSpy; + let stubLoggerError: Sinon.SinonStub; + + beforeEach(() => { + spyGetOtlpProtocol = Sinon.spy(TracerProviderWithEnvExporters, 'getOtlpProtocol'); + stubLoggerError = Sinon.stub(diag, 'warn'); + }); + afterEach(() => { + spyGetOtlpProtocol.restore(); + stubLoggerError.restore(); + }); + describe('setup otlp exporter from env', () => { + it('set up default exporter when user does not define otel trace exporter', async () => { + const sdk = new TracerProviderWithEnvExporters(); + const listOfProcessors = sdk['_spanProcessors']!; + const listOfExporters = sdk['_configuredExporters']; + + assert(spyGetOtlpProtocol.returned('http/protobuf')); + assert(listOfExporters.length === 1); + assert(listOfExporters[0] instanceof OTLPProtoTraceExporter); + assert(listOfProcessors.length === 1); + assert(listOfProcessors[0] instanceof BatchSpanProcessor); + }); + it('use otlp exporter and grpc exporter protocol env value', async () => { + env.OTEL_TRACES_EXPORTER = 'otlp'; + env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = 'grpc'; + + const sdk = new TracerProviderWithEnvExporters(); + const listOfProcessors = sdk['_spanProcessors']!; + const listOfExporters = sdk['_configuredExporters']; + + assert(spyGetOtlpProtocol.returned('grpc')); + assert(listOfExporters.length === 1); + assert(listOfExporters[0] instanceof OTLPGrpcTraceExporter); + assert(listOfProcessors.length === 1); + assert(listOfProcessors[0] instanceof BatchSpanProcessor); + + delete env.OTEL_TRACES_EXPORTER; + delete env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL; + }); + it('sdk will ignore protocol defined with no-signal env and use signal specific protocol instead', async () => { + env.OTEL_TRACES_EXPORTER = 'otlp'; + env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = 'http/protobuf'; + env.OTEL_EXPORTER_OTLP_PROTOCOL = 'grpc'; + + const sdk = new TracerProviderWithEnvExporters(); + const listOfProcessors = sdk['_spanProcessors']!; + const listOfExporters = sdk['_configuredExporters']; + + assert(spyGetOtlpProtocol.returned('http/protobuf')); + assert(listOfExporters.length === 1); + assert(listOfExporters[0] instanceof OTLPTraceExporter); + assert(listOfProcessors.length === 1); + assert(listOfProcessors[0] instanceof BatchSpanProcessor); + + delete env.OTEL_TRACES_EXPORTER; + delete env.OTEL_EXPORTER_OTLP_PROTOCOL; + delete env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL; + }); + it('do not use any exporters when empty value is provided for exporter', async () => { + env.OTEL_TRACES_EXPORTER = ''; + const sdk = new TracerProviderWithEnvExporters(); + const listOfProcessors = sdk['_spanProcessors']; + const listOfExporters = sdk['_configuredExporters']; + + assert(spyGetOtlpProtocol.notCalled); + assert(listOfExporters.length === 0); + assert(listOfProcessors === undefined); + env.OTEL_TRACES_EXPORTER = ''; + }); + it('do not use any exporters when none value is only provided', async () => { + env.OTEL_TRACES_EXPORTER = 'none'; + const sdk = new TracerProviderWithEnvExporters(); + const listOfProcessors = sdk['_spanProcessors']; + const listOfExporters = sdk['_configuredExporters']; + + assert(spyGetOtlpProtocol.notCalled); + assert(listOfExporters.length === 0); + assert(listOfProcessors === undefined); + delete env.OTEL_TRACES_EXPORTER; + }); + it('log warning that sdk will not be initalized when exporter is set to none', async () => { + env.OTEL_TRACES_EXPORTER = 'none'; + new TracerProviderWithEnvExporters(); + + assert.strictEqual(stubLoggerError.args[0][0], 'OTEL_TRACES_EXPORTER contains "none" or is empty. SDK will not be initialized.'); + delete env.OTEL_TRACES_EXPORTER; + }); + it('use default exporter when none value is provided with other exports', async () => { + env.OTEL_TRACES_EXPORTER = 'otlp,zipkin,none'; + const sdk = new TracerProviderWithEnvExporters(); + const listOfProcessors = sdk['_spanProcessors']!; + const listOfExporters = sdk['_configuredExporters']; + + assert(listOfExporters[0] instanceof OTLPProtoTraceExporter); + assert(listOfExporters.length === 1); + assert(listOfExporters[0] instanceof OTLPHttpTraceExporter === false); + assert(listOfExporters[0] instanceof ZipkinExporter === false); + assert(listOfProcessors.length === 1); + assert(listOfProcessors[0] instanceof BatchSpanProcessor); + delete env.OTEL_TRACES_EXPORTER; + }); + it('log warning that default exporter will be used since exporter list contains none with other exports ', async () => { + env.OTEL_TRACES_EXPORTER = 'otlp,zipkin,none'; + new TracerProviderWithEnvExporters(); + + assert.strictEqual( + stubLoggerError.args[0][0], 'OTEL_TRACES_EXPORTER contains "none" along with other exporters. Using default otlp exporter.' + ); + delete env.OTEL_TRACES_EXPORTER; + }); + it('should warn that exporter is unrecognized and not able to be set up', async () => { + env.OTEL_TRACES_EXPORTER = 'invalid'; + new TracerProviderWithEnvExporters(); + + assert.strictEqual( + stubLoggerError.args[0][0], 'Unrecognized OTEL_TRACES_EXPORTER value: invalid.' + ); + + assert.strictEqual( + stubLoggerError.args[1][0], 'Unable to set up trace exporter(s) due to invalid exporter and/or protocol values.' + ); + + delete env.OTEL_TRACES_EXPORTER; + }); + it('should log warning when provided protocol name is not valid', async () => { + env.OTEL_EXPORTER_OTLP_PROTOCOL = 'invalid'; + new TracerProviderWithEnvExporters(); + + assert.strictEqual( + stubLoggerError.args[0][0], 'Unsupported OTLP traces protocol: invalid. Using http/protobuf.' + ); + delete env.OTEL_EXPORTER_OTLP_PROTOCOL; + }); + }); + describe('setup zipkin exporter from env', () => { + it('use the zipkin exporter', async () => { + env.OTEL_TRACES_EXPORTER = 'zipkin'; + const sdk = new TracerProviderWithEnvExporters(); + const listOfProcessors = sdk['_spanProcessors']!; + const listOfExporters = sdk['_configuredExporters']; + + assert(listOfExporters.length === 1); + assert(listOfExporters[0] instanceof ZipkinExporter); + assert(listOfProcessors.length === 1); + assert(listOfProcessors[0] instanceof BatchSpanProcessor); + delete env.OTEL_TRACES_EXPORTER; + }); + it('setup zipkin exporter and otlp exporter', async () => { + env.OTEL_TRACES_EXPORTER = 'zipkin, otlp'; + env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = 'grpc'; + + const sdk = new TracerProviderWithEnvExporters(); + const listOfProcessors = sdk['_spanProcessors']!; + const listOfExporters = sdk['_configuredExporters']; + + assert(spyGetOtlpProtocol.returned('grpc')); + assert(listOfExporters.length === 2); + assert(listOfExporters[0] instanceof ZipkinExporter); + assert(listOfExporters[1] instanceof OTLPGrpcTraceExporter); + assert(listOfProcessors[0] instanceof BatchSpanProcessor); + assert(listOfProcessors[1] instanceof BatchSpanProcessor); + + delete env.OTEL_TRACES_EXPORTER; + delete env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL; + }); + }); + describe('setup jaeger exporter from env', () => { + it('use the jaeger exporter', async () => { + env.OTEL_TRACES_EXPORTER = 'jaeger'; + const sdk = new TracerProviderWithEnvExporters(); + const listOfProcessors = sdk['_spanProcessors']!; + const listOfExporters = sdk['_configuredExporters']; + + assert(listOfExporters.length === 1); + assert(listOfExporters[0] instanceof JaegerExporter); + assert(listOfProcessors.length === 1); + assert(listOfProcessors[0] instanceof BatchSpanProcessor); + delete env.OTEL_TRACES_EXPORTER; + }); + it('setup jaeger exporter and otlp exporter', async () => { + env.OTEL_TRACES_EXPORTER = 'jaeger, otlp'; + env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = 'http/json'; + const sdk = new TracerProviderWithEnvExporters(); + const listOfProcessors = sdk['_spanProcessors']!; + const listOfExporters = sdk['_configuredExporters']; + + assert(spyGetOtlpProtocol.returned('http/json')); + assert(listOfExporters.length === 2); + assert(listOfExporters[0] instanceof JaegerExporter); + assert(listOfExporters[1] instanceof OTLPHttpTraceExporter); + assert(listOfProcessors.length === 2); + assert(listOfProcessors[0] instanceof BatchSpanProcessor); + assert(listOfProcessors[1] instanceof BatchSpanProcessor); + + delete env.OTEL_TRACES_EXPORTER; + delete env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL; + }); + }); + describe('setup console exporter from env', () => { + it('use the console exporter', async () => { + env.OTEL_TRACES_EXPORTER = 'console'; + const sdk = new TracerProviderWithEnvExporters(); + const listOfProcessors = sdk['_spanProcessors']!; + const listOfExporters = sdk['_configuredExporters']; + + assert(listOfExporters.length === 1); + assert(listOfExporters[0] instanceof ConsoleSpanExporter); + assert(listOfProcessors.length === 1); + assert(listOfProcessors[0] instanceof SimpleSpanProcessor); + delete env.OTEL_TRACES_EXPORTER; + }); + it('ignores the protocol', async () => { + env.OTEL_TRACES_EXPORTER = 'console'; + env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = 'grpc'; + const sdk = new TracerProviderWithEnvExporters(); + const listOfProcessors = sdk['_spanProcessors']!; + const listOfExporters = sdk['_configuredExporters']; + + assert(spyGetOtlpProtocol.notCalled); + assert(listOfExporters.length === 1); + assert(listOfExporters[0] instanceof ConsoleSpanExporter); + assert(listOfProcessors.length === 1); + assert(listOfProcessors[0] instanceof SimpleSpanProcessor); + + delete env.OTEL_TRACES_EXPORTER; + delete env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL; + }); + it('setup console exporter and otlp exporter', async () => { + env.OTEL_TRACES_EXPORTER = 'console, otlp'; + env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = 'grpc'; + const sdk = new TracerProviderWithEnvExporters(); + const listOfProcessors = sdk['_spanProcessors']!; + const listOfExporters = sdk['_configuredExporters']; + + assert(spyGetOtlpProtocol.returned('grpc')); + assert(listOfExporters.length === 2); + assert(listOfExporters[0] instanceof ConsoleSpanExporter); + assert(listOfExporters[1] instanceof OTLPGrpcTraceExporter); + assert(listOfProcessors.length === 2); + assert(listOfProcessors[0] instanceof SimpleSpanProcessor); + assert(listOfProcessors[1] instanceof BatchSpanProcessor); + + delete env.OTEL_TRACES_EXPORTER; + delete env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL; + }); + }); +}); diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index c1b0b90d567..12a38337441 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -44,17 +44,21 @@ import { import { ConsoleSpanExporter, SimpleSpanProcessor, + BatchSpanProcessor, + NoopSpanProcessor, } from '@opentelemetry/sdk-trace-base'; import * as assert from 'assert'; import * as semver from 'semver'; import * as Sinon from 'sinon'; import { NodeSDK } from '../src'; +import { env } from 'process'; +import { TracerProviderWithEnvExporters } from '../src/TracerProviderWithEnvExporter'; import { envDetector, processDetector, Resource } from '@opentelemetry/resources'; - +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; const DefaultContextManager = semver.gte(process.version, '14.8.0') ? AsyncLocalStorageContextManager @@ -78,6 +82,9 @@ describe('Node SDK', () => { describe('Basic Registration', () => { it('should not register any unconfigured SDK components', async () => { + // need to set OTEL_TRACES_EXPORTER to none since default value is otlp + // which sets up an exporter and affects the context manager + env.OTEL_TRACES_EXPORTER = 'none'; const sdk = new NodeSDK({ autoDetectResources: false, }); @@ -87,8 +94,8 @@ describe('Node SDK', () => { assert.strictEqual(context['_getContextManager'](), ctxManager, 'context manager should not change'); assert.strictEqual(propagation['_getGlobalPropagator'](), propagator, 'propagator should not change'); assert.strictEqual((trace.getTracerProvider() as ProxyTracerProvider).getDelegate(), delegate, 'tracer provider should not have changed'); - assert.ok(!(metrics.getMeterProvider() instanceof MeterProvider)); + delete env.OTEL_TRACES_EXPORTER; }); it('should register a tracer provider if an exporter is provided', async () => { @@ -135,6 +142,9 @@ describe('Node SDK', () => { }); it('should register a meter provider if a reader is provided', async () => { + // need to set OTEL_TRACES_EXPORTER to none since default value is otlp + // which sets up an exporter and affects the context manager + env.OTEL_TRACES_EXPORTER = 'none'; const exporter = new ConsoleMetricExporter(); const metricReader = new PeriodicExportingMetricReader({ exporter: exporter, @@ -156,6 +166,7 @@ describe('Node SDK', () => { assert.ok(metrics.getMeterProvider() instanceof MeterProvider); await sdk.shutdown(); + delete env.OTEL_TRACES_EXPORTER; }); }); @@ -173,6 +184,9 @@ describe('Node SDK', () => { } it('should register meter views when provided', async () => { + // need to set OTEL_TRACES_EXPORTER to none since default value is otlp + // which sets up an exporter and affects the context manager + env.OTEL_TRACES_EXPORTER = 'none'; const exporter = new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE); const metricReader = new PeriodicExportingMetricReader({ exporter: exporter, @@ -219,6 +233,7 @@ describe('Node SDK', () => { assert.ok(firstMetricRecord.descriptor.name === 'test-view', 'should have renamed counter metric'); await sdk.shutdown(); + delete env.OTEL_TRACES_EXPORTER; }); it('should throw error when calling configureMeterProvider when views are already configured', () => { @@ -471,7 +486,6 @@ describe('Node SDK', () => { delete process.env.OTEL_SERVICE_NAME; }); - it('should configure service name via OTEL_RESOURCE_ATTRIBUTES env var', async () => { process.env.OTEL_RESOURCE_ATTRIBUTES = 'service.name=resource-env-set-name'; const sdk = new NodeSDK(); @@ -501,3 +515,178 @@ describe('Node SDK', () => { }); }); }); + +describe('setup exporter from env', () => { + let spyGetOtlpProtocol: Sinon.SinonSpy; + let stubLoggerError: Sinon.SinonStub; + + beforeEach(() => { + spyGetOtlpProtocol = Sinon.spy(TracerProviderWithEnvExporters, 'getOtlpProtocol'); + stubLoggerError = Sinon.stub(diag, 'warn'); + }); + afterEach(() => { + spyGetOtlpProtocol.restore(); + stubLoggerError.restore(); + }); + it('use default exporter TracerProviderWithEnvExporters when user does not provide span processor or trace exporter to sdk config', async () => { + const sdk = new NodeSDK(); + await sdk.start(); + const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; + + assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters); + assert(listOfProcessors.length === 1); + assert(listOfProcessors[0] instanceof BatchSpanProcessor); + }); + it('ignore env exporter when user provides exporter to sdk config', async () => { + const traceExporter = new ConsoleSpanExporter(); + const sdk = new NodeSDK({ + traceExporter + }); + await sdk.start(); + const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; + + assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters === false); + assert(listOfProcessors.length === 1); + assert(listOfProcessors[0] instanceof SimpleSpanProcessor === false); + assert(listOfProcessors[0] instanceof BatchSpanProcessor); + }); + it('ignores default env exporter when user provides span processor to sdk config', async () => { + const traceExporter = new ConsoleSpanExporter(); + const spanProcessor = new SimpleSpanProcessor(traceExporter); + const sdk = new NodeSDK({ + spanProcessor + }); + await sdk.start(); + const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; + + assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters === false); + assert(listOfProcessors.length === 1); + assert(listOfProcessors[0] instanceof SimpleSpanProcessor); + assert(listOfProcessors[0] instanceof BatchSpanProcessor === false); + }); + it('ignores env exporter when user provides tracer exporter to sdk config and sets exporter via env', async () => { + env.OTEL_TRACES_EXPORTER = 'console'; + const traceExporter = new OTLPTraceExporter(); + const sdk = new NodeSDK({ + traceExporter + }); + await sdk.start(); + const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; + + assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters === false); + assert(listOfProcessors.length === 1); + assert(listOfProcessors[0] instanceof SimpleSpanProcessor === false); + assert(listOfProcessors[0] instanceof BatchSpanProcessor); + delete env.OTEL_TRACES_EXPORTER; + }); + it('use otlp exporter and defined exporter protocol env value', async () => { + env.OTEL_TRACES_EXPORTER = 'otlp'; + env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = 'grpc'; + const sdk = new NodeSDK(); + await sdk.start(); + + const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; + assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters); + assert(listOfProcessors.length === 1); + assert(listOfProcessors[0] instanceof BatchSpanProcessor); + delete env.OTEL_TRACES_EXPORTER; + delete env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL; + }); + it('use noop span processor when user sets env exporter to none', async () => { + env.OTEL_TRACES_EXPORTER = 'none'; + const sdk = new NodeSDK(); + await sdk.start(); + + const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; + const activeProcessor = sdk['_tracerProvider']?.getActiveSpanProcessor(); + + assert(listOfProcessors.length === 0); + assert(activeProcessor instanceof NoopSpanProcessor); + delete env.OTEL_TRACES_EXPORTER; + }); + it('log warning that sdk will not be initalized when exporter is set to none', async () => { + env.OTEL_TRACES_EXPORTER = 'none'; + const sdk = new NodeSDK(); + await sdk.start(); + + assert.strictEqual(stubLoggerError.args[0][0], 'OTEL_TRACES_EXPORTER contains "none" or is empty. SDK will not be initialized.'); + delete env.OTEL_TRACES_EXPORTER; + }); + it('do not use any exporters when empty value is provided for exporter', async () => { + env.OTEL_TRACES_EXPORTER = ''; + const sdk = new NodeSDK(); + await sdk.start(); + + const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; + const activeProcessor = sdk['_tracerProvider']?.getActiveSpanProcessor(); + + assert(listOfProcessors.length === 0); + assert(activeProcessor instanceof NoopSpanProcessor); + env.OTEL_TRACES_EXPORTER = ''; + }); + + it('use only default exporter when none value is provided with other exporters', async () => { + env.OTEL_TRACES_EXPORTER = 'otlp,zipkin,none'; + const sdk = new NodeSDK(); + await sdk.start(); + + const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; + assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters); + assert(listOfProcessors.length === 1); + assert(listOfProcessors[0] instanceof BatchSpanProcessor); + + delete env.OTEL_TRACES_EXPORTER; + }); + it('log warning that only default exporter will be used since exporter list contains none with other exports ', async () => { + env.OTEL_TRACES_EXPORTER = 'otlp,zipkin,none'; + const sdk = new NodeSDK(); + await sdk.start(); + + assert.strictEqual( + stubLoggerError.args[0][0], 'OTEL_TRACES_EXPORTER contains "none" along with other exporters. Using default otlp exporter.' + ); + delete env.OTEL_TRACES_EXPORTER; + }); + it('should warn that provided exporter value is unrecognized and not able to be set up', async () => { + env.OTEL_TRACES_EXPORTER = 'invalid'; + const sdk = new NodeSDK(); + await sdk.start(); + + assert.strictEqual( + stubLoggerError.args[0][0], 'Unrecognized OTEL_TRACES_EXPORTER value: invalid.' + ); + + assert.strictEqual( + stubLoggerError.args[1][0], 'Unable to set up trace exporter(s) due to invalid exporter and/or protocol values.' + ); + + delete env.OTEL_TRACES_EXPORTER; + }); + it('setup zipkin, jaeger and otlp exporters', async () => { + env.OTEL_TRACES_EXPORTER = 'zipkin, otlp, jaeger'; + env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = 'grpc'; + const sdk = new NodeSDK(); + await sdk.start(); + + const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; + assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters); + assert(listOfProcessors.length === 3); + assert(listOfProcessors[0] instanceof BatchSpanProcessor); + assert(listOfProcessors[1] instanceof BatchSpanProcessor); + assert(listOfProcessors[2] instanceof BatchSpanProcessor); + + delete env.OTEL_TRACES_EXPORTER; + delete env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL; + }); + it('use the console exporter', async () => { + env.OTEL_TRACES_EXPORTER = 'console, otlp'; + const sdk = new NodeSDK(); + await sdk.start(); + + const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; + assert(listOfProcessors.length === 2); + assert(listOfProcessors[0] instanceof SimpleSpanProcessor); + assert(listOfProcessors[1] instanceof BatchSpanProcessor); + delete env.OTEL_TRACES_EXPORTER; + }); +}); diff --git a/experimental/packages/opentelemetry-sdk-node/tsconfig.json b/experimental/packages/opentelemetry-sdk-node/tsconfig.json index 3d1db307391..55448ab7888 100644 --- a/experimental/packages/opentelemetry-sdk-node/tsconfig.json +++ b/experimental/packages/opentelemetry-sdk-node/tsconfig.json @@ -18,6 +18,12 @@ { "path": "../../../packages/opentelemetry-core" }, + { + "path": "../../../packages/opentelemetry-exporter-jaeger" + }, + { + "path": "../../../packages/opentelemetry-exporter-zipkin" + }, { "path": "../../../packages/opentelemetry-resources" }, @@ -30,6 +36,15 @@ { "path": "../../../packages/opentelemetry-semantic-conventions" }, + { + "path": "../exporter-trace-otlp-grpc" + }, + { + "path": "../exporter-trace-otlp-http" + }, + { + "path": "../exporter-trace-otlp-proto" + }, { "path": "../opentelemetry-api-metrics" }, diff --git a/packages/opentelemetry-core/src/utils/environment.ts b/packages/opentelemetry-core/src/utils/environment.ts index 05ba058f0fa..8d50e2cb9bc 100644 --- a/packages/opentelemetry-core/src/utils/environment.ts +++ b/packages/opentelemetry-core/src/utils/environment.ts @@ -102,7 +102,10 @@ export type ENVIRONMENT = { OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY?: string, OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE?: string, OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE?: string, - OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE?: string + OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE?: string, + OTEL_EXPORTER_OTLP_PROTOCOL?: string, + OTEL_EXPORTER_OTLP_TRACES_PROTOCOL?: string, + OTEL_EXPORTER_OTLP_METRICS_PROTOCOL?: string, } & ENVIRONMENT_NUMBERS & ENVIRONMENT_LISTS; @@ -154,7 +157,7 @@ export const DEFAULT_ENVIRONMENT: Required = { OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: DEFAULT_ATTRIBUTE_COUNT_LIMIT, OTEL_SPAN_EVENT_COUNT_LIMIT: 128, OTEL_SPAN_LINK_COUNT_LIMIT: 128, - OTEL_TRACES_EXPORTER: 'none', + OTEL_TRACES_EXPORTER: 'otlp', OTEL_TRACES_SAMPLER: TracesSamplerValues.ParentBasedAlwaysOn, OTEL_TRACES_SAMPLER_ARG: '', OTEL_EXPORTER_OTLP_INSECURE: '', @@ -171,7 +174,10 @@ export const DEFAULT_ENVIRONMENT: Required = { OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY: '', OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE: '', OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE: '', - OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE: '' + OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE: '', + OTEL_EXPORTER_OTLP_PROTOCOL: 'http/protobuf', + OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: 'http/protobuf', + OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: 'http/protobuf', }; /**