From 75e963bdef873d7318d0ecc4f1dd57c2df9ec497 Mon Sep 17 00:00:00 2001 From: vmarchaud Date: Sun, 2 May 2021 16:38:41 +0200 Subject: [PATCH] improv(tracing): allow to load exporter from environment #1676 --- .../src/utils/environment.ts | 2 + .../src/BasicTracerProvider.ts | 50 +++++++++++++++++-- .../src/{ => export}/NoopSpanProcessor.ts | 6 +-- packages/opentelemetry-tracing/src/index.ts | 1 + .../test/BasicTracerProvider.test.ts | 30 +++++++++-- 5 files changed, 78 insertions(+), 11 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 9554fe64a57..4bdfd3aeb40 100644 --- a/packages/opentelemetry-core/src/utils/environment.ts +++ b/packages/opentelemetry-core/src/utils/environment.ts @@ -70,6 +70,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 & @@ -105,6 +106,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 0a7aea24dad..0a8f23ab624 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 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.warn( + `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 6623803e857..21e608e28a7 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 720241a3107..5d7d512d344 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 './types'; diff --git a/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts b/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts index 04c30a38a24..07e6219ecf8 100644 --- a/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts +++ b/packages/opentelemetry-tracing/test/BasicTracerProvider.test.ts @@ -38,10 +38,13 @@ 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 } from '../src'; describe('BasicTracerProvider', () => { let removeEvent: Function | undefined; + const envSource = (typeof window !== 'undefined' + ? window + : process.env) as any; beforeEach(() => { context.disable(); @@ -120,13 +123,30 @@ 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); + }); + + it('warns if there is no exporter registered with a given name', () => { + const warnStub = sinon.spy(diag, 'warn'); + + envSource.OTEL_TRACES_EXPORTER = 'missing-exporter'; + const provider = new BasicTracerProvider({}); + provider.register(); + + assert.ok( + warnStub.calledOnceWithExactly( + 'Exporter "missing-exporter" requested through environment variable is unavailable.' + ) + ); + warnStub.restore(); + envSource.OTEL_TRACES_EXPORTER = 'none'; + }); }); describe('.register()', () => { - const envSource = (typeof window !== 'undefined' - ? window - : process.env) as any; - describe('propagator', () => { class DummyPropagator implements TextMapPropagator { inject(