From 1758fa6e84333c9c43cc468c5e99dd3646f7e3c7 Mon Sep 17 00:00:00 2001 From: Valentin Marchaud Date: Wed, 12 May 2021 17:58:55 +0200 Subject: [PATCH] feat(tracing): allow to configure exporter by environment #1676 (#2100) Co-authored-by: Bartlomiej Obecny Co-authored-by: Daniel Dyla --- .../src/utils/environment.ts | 2 + .../src/BasicTracerProvider.ts | 48 ++++++++++++++- .../src/{ => export}/NoopSpanProcessor.ts | 6 +- packages/opentelemetry-tracing/src/index.ts | 1 + .../test/BasicTracerProvider.test.ts | 59 +++++++++++++++++-- 5 files changed, 106 insertions(+), 10 deletions(-) rename packages/opentelemetry-tracing/src/{ => export}/NoopSpanProcessor.ts (87%) diff --git a/packages/opentelemetry-core/src/utils/environment.ts b/packages/opentelemetry-core/src/utils/environment.ts index 61021a01ea..8e621a0d86 100644 --- a/packages/opentelemetry-core/src/utils/environment.ts +++ b/packages/opentelemetry-core/src/utils/environment.ts @@ -76,6 +76,7 @@ export type ENVIRONMENT = { OTEL_EXPORTER_ZIPKIN_ENDPOINT?: string; OTEL_LOG_LEVEL?: DiagLogLevel; OTEL_RESOURCE_ATTRIBUTES?: string; + OTEL_TRACES_EXPORTER?: string; OTEL_TRACES_SAMPLER_ARG?: string; OTEL_TRACES_SAMPLER?: string; } & ENVIRONMENT_NUMBERS & @@ -117,6 +118,7 @@ export const DEFAULT_ENVIRONMENT: Required = { OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: 128, OTEL_SPAN_EVENT_COUNT_LIMIT: 128, OTEL_SPAN_LINK_COUNT_LIMIT: 128, + OTEL_TRACES_EXPORTER: 'none', OTEL_TRACES_SAMPLER: TracesSamplerValues.ParentBasedAlwaysOn, OTEL_TRACES_SAMPLER_ARG: '', }; diff --git a/packages/opentelemetry-tracing/src/BasicTracerProvider.ts b/packages/opentelemetry-tracing/src/BasicTracerProvider.ts index 0a7aea24da..01d6cb8b21 100644 --- a/packages/opentelemetry-tracing/src/BasicTracerProvider.ts +++ b/packages/opentelemetry-tracing/src/BasicTracerProvider.ts @@ -32,11 +32,14 @@ import { Resource } from '@opentelemetry/resources'; import { SpanProcessor, Tracer } from '.'; import { DEFAULT_CONFIG } from './config'; import { MultiSpanProcessor } from './MultiSpanProcessor'; -import { NoopSpanProcessor } from './NoopSpanProcessor'; +import { NoopSpanProcessor } from './export/NoopSpanProcessor'; import { SDKRegistrationConfig, TracerConfig } from './types'; const merge = require('lodash.merge'); +import { SpanExporter } from './export/SpanExporter'; +import { BatchSpanProcessor } from './export/BatchSpanProcessor'; export type PROPAGATOR_FACTORY = () => TextMapPropagator; +export type EXPORTER_FACTORY = () => SpanExporter; /** * This class represents a basic tracer provider which platform libraries can extend @@ -50,11 +53,16 @@ export class BasicTracerProvider implements TracerProvider { ['baggage', () => new HttpBaggagePropagator()], ]); + protected static readonly _registeredExporters = new Map< + string, + EXPORTER_FACTORY + >(); + private readonly _config: TracerConfig; private readonly _registeredSpanProcessors: SpanProcessor[] = []; private readonly _tracers: Map = new Map(); - activeSpanProcessor: SpanProcessor = new NoopSpanProcessor(); + activeSpanProcessor: SpanProcessor; readonly resource: Resource; constructor(config: TracerConfig = {}) { @@ -64,6 +72,14 @@ export class BasicTracerProvider implements TracerProvider { this._config = Object.assign({}, mergedConfig, { resource: this.resource, }); + + const defaultExporter = this._buildExporterFromEnv(); + if (defaultExporter !== undefined) { + const batchProcessor = new BatchSpanProcessor(defaultExporter); + this.activeSpanProcessor = batchProcessor; + } else { + this.activeSpanProcessor = new NoopSpanProcessor(); + } } getTracer(name: string, version?: string): Tracer { @@ -80,6 +96,18 @@ export class BasicTracerProvider implements TracerProvider { * @param spanProcessor the new SpanProcessor to be added. */ addSpanProcessor(spanProcessor: SpanProcessor): void { + if (this._registeredSpanProcessors.length === 0) { + // since we might have enabled by default a batchProcessor, we disable it + // before adding the new one + this.activeSpanProcessor + .shutdown() + .catch(err => + diag.error( + 'Error while trying to shutdown current span processor', + err + ) + ); + } this._registeredSpanProcessors.push(spanProcessor); this.activeSpanProcessor = new MultiSpanProcessor( this._registeredSpanProcessors @@ -120,6 +148,10 @@ export class BasicTracerProvider implements TracerProvider { return BasicTracerProvider._registeredPropagators.get(name)?.(); } + protected _getSpanExporter(name: string): SpanExporter | undefined { + return BasicTracerProvider._registeredExporters.get(name)?.(); + } + protected _buildPropagatorFromEnv(): TextMapPropagator | undefined { // per spec, propagators from env must be deduplicated const uniquePropagatorNames = Array.from( @@ -156,4 +188,16 @@ export class BasicTracerProvider implements TracerProvider { }); } } + + protected _buildExporterFromEnv(): SpanExporter | undefined { + const exporterName = getEnv().OTEL_TRACES_EXPORTER; + if (exporterName === 'none') return; + const exporter = this._getSpanExporter(exporterName); + if (!exporter) { + diag.error( + `Exporter "${exporterName}" requested through environment variable is unavailable.` + ); + } + return exporter; + } } diff --git a/packages/opentelemetry-tracing/src/NoopSpanProcessor.ts b/packages/opentelemetry-tracing/src/export/NoopSpanProcessor.ts similarity index 87% rename from packages/opentelemetry-tracing/src/NoopSpanProcessor.ts rename to packages/opentelemetry-tracing/src/export/NoopSpanProcessor.ts index 6623803e85..21e608e28a 100644 --- a/packages/opentelemetry-tracing/src/NoopSpanProcessor.ts +++ b/packages/opentelemetry-tracing/src/export/NoopSpanProcessor.ts @@ -15,9 +15,9 @@ */ import { Context } from '@opentelemetry/api'; -import { ReadableSpan } from './export/ReadableSpan'; -import { Span } from './Span'; -import { SpanProcessor } from './SpanProcessor'; +import { ReadableSpan } from './ReadableSpan'; +import { Span } from '../Span'; +import { SpanProcessor } from '../SpanProcessor'; /** No-op implementation of SpanProcessor */ export class NoopSpanProcessor implements SpanProcessor { diff --git a/packages/opentelemetry-tracing/src/index.ts b/packages/opentelemetry-tracing/src/index.ts index 188b5105ce..298657290f 100644 --- a/packages/opentelemetry-tracing/src/index.ts +++ b/packages/opentelemetry-tracing/src/index.ts @@ -22,6 +22,7 @@ export * from './export/InMemorySpanExporter'; export * from './export/ReadableSpan'; export * from './export/SimpleSpanProcessor'; export * from './export/SpanExporter'; +export * from './export/NoopSpanProcessor'; export * from './Span'; export * from './SpanProcessor'; export * from './TimedEvent'; diff --git a/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts b/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts index 04c30a38a2..8f2f20269b 100644 --- a/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts +++ b/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts @@ -38,10 +38,20 @@ import { import { Resource } from '@opentelemetry/resources'; import * as assert from 'assert'; import * as sinon from 'sinon'; -import { BasicTracerProvider, Span } from '../src'; +import { + BasicTracerProvider, + NoopSpanProcessor, + Span, + InMemorySpanExporter, + SpanExporter, + BatchSpanProcessor, +} from '../src'; describe('BasicTracerProvider', () => { let removeEvent: Function | undefined; + const envSource = (typeof window !== 'undefined' + ? window + : process.env) as any; beforeEach(() => { context.disable(); @@ -120,13 +130,14 @@ describe('BasicTracerProvider', () => { const tracer = new BasicTracerProvider(); assert.ok(tracer instanceof BasicTracerProvider); }); + + it('should use noop span processor by default', () => { + const tracer = new BasicTracerProvider(); + assert.ok(tracer.activeSpanProcessor instanceof NoopSpanProcessor); + }); }); describe('.register()', () => { - const envSource = (typeof window !== 'undefined' - ? window - : process.env) as any; - describe('propagator', () => { class DummyPropagator implements TextMapPropagator { inject( @@ -213,6 +224,44 @@ describe('BasicTracerProvider', () => { warnStub.restore(); }); }); + + describe('exporter', () => { + class CustomTracerProvider extends BasicTracerProvider { + protected _getSpanExporter(name: string): SpanExporter | undefined { + return name === 'memory' + ? new InMemorySpanExporter() + : BasicTracerProvider._registeredExporters.get(name)?.(); + } + } + + afterEach(() => { + delete envSource.OTEL_TRACES_EXPORTER; + }); + + it('logs error if there is no exporter registered with a given name', () => { + const errorStub = sinon.spy(diag, 'error'); + + envSource.OTEL_TRACES_EXPORTER = 'missing-exporter'; + const provider = new BasicTracerProvider({}); + provider.register(); + assert.ok( + errorStub.getCall(0).args[0] === + 'Exporter "missing-exporter" requested through environment variable is unavailable.' + ); + errorStub.restore(); + }); + + it('registers trace exporter from environment variable', () => { + envSource.OTEL_TRACES_EXPORTER = 'memory'; + const provider = new CustomTracerProvider({}); + provider.register(); + const processor = provider.getActiveSpanProcessor(); + assert(processor instanceof BatchSpanProcessor); + // @ts-expect-error access configured to verify its the correct one + const exporter = processor._exporter; + assert(exporter instanceof InMemorySpanExporter); + }); + }); }); describe('.startSpan()', () => {