/* * 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 { context, diag, propagation, TextMapPropagator, trace, TracerProvider, } from '@opentelemetry/api'; import { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator, getEnv, merge, } from '@opentelemetry/core'; import { IResource, Resource } from '@opentelemetry/resources'; import { SpanProcessor, Tracer } from '.'; import { loadDefaultConfig } from './config'; import { MultiSpanProcessor } from './MultiSpanProcessor'; import { NoopSpanProcessor } from './export/NoopSpanProcessor'; import { SDKRegistrationConfig, TracerConfig } from './types'; import { SpanExporter } from './export/SpanExporter'; import { BatchSpanProcessor } from './platform'; import { reconfigureLimits } from './utility'; export type PROPAGATOR_FACTORY = () => TextMapPropagator; export type EXPORTER_FACTORY = () => SpanExporter; export enum ForceFlushState { 'resolved', 'timeout', 'error', 'unresolved', } /** * This class represents a basic tracer provider which platform libraries can extend */ export class BasicTracerProvider implements TracerProvider { protected static readonly _registeredPropagators = new Map< string, PROPAGATOR_FACTORY >([ ['tracecontext', () => new W3CTraceContextPropagator()], ['baggage', () => new W3CBaggagePropagator()], ]); protected static readonly _registeredExporters = new Map< string, EXPORTER_FACTORY >(); private readonly _config: TracerConfig; private readonly _registeredSpanProcessors: SpanProcessor[] = []; private readonly _tracers: Map<string, Tracer> = new Map(); activeSpanProcessor: SpanProcessor; readonly resource: IResource; constructor(config: TracerConfig = {}) { const mergedConfig = merge( {}, loadDefaultConfig(), reconfigureLimits(config) ); this.resource = mergedConfig.resource ?? Resource.empty(); this.resource = Resource.default().merge(this.resource); 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, options?: { schemaUrl?: string } ): Tracer { const key = `${name}@${version || ''}:${options?.schemaUrl || ''}`; if (!this._tracers.has(key)) { this._tracers.set( key, new Tracer( { name, version, schemaUrl: options?.schemaUrl }, this._config, this ) ); } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return this._tracers.get(key)!; } /** * Adds a new {@link SpanProcessor} to this tracer. * @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 ); } getActiveSpanProcessor(): SpanProcessor { return this.activeSpanProcessor; } /** * Register this TracerProvider for use with the OpenTelemetry API. * Undefined values may be replaced with defaults, and * null values will be skipped. * * @param config Configuration object for SDK registration */ register(config: SDKRegistrationConfig = {}): void { trace.setGlobalTracerProvider(this); if (config.propagator === undefined) { config.propagator = this._buildPropagatorFromEnv(); } if (config.contextManager) { context.setGlobalContextManager(config.contextManager); } if (config.propagator) { propagation.setGlobalPropagator(config.propagator); } } forceFlush(): Promise<void> { const timeout = this._config.forceFlushTimeoutMillis; const promises = this._registeredSpanProcessors.map( (spanProcessor: SpanProcessor) => { return new Promise(resolve => { let state: ForceFlushState; const timeoutInterval = setTimeout(() => { resolve( new Error( `Span processor did not completed within timeout period of ${timeout} ms` ) ); state = ForceFlushState.timeout; }, timeout); spanProcessor .forceFlush() .then(() => { clearTimeout(timeoutInterval); if (state !== ForceFlushState.timeout) { state = ForceFlushState.resolved; resolve(state); } }) .catch(error => { clearTimeout(timeoutInterval); state = ForceFlushState.error; resolve(error); }); }); } ); return new Promise<void>((resolve, reject) => { Promise.all(promises) .then(results => { const errors = results.filter( result => result !== ForceFlushState.resolved ); if (errors.length > 0) { reject(errors); } else { resolve(); } }) .catch(error => reject([error])); }); } shutdown(): Promise<void> { return this.activeSpanProcessor.shutdown(); } /** * TS cannot yet infer the type of this.constructor: * https://github.com/Microsoft/TypeScript/issues/3841#issuecomment-337560146 * There is no need to override either of the getters in your child class. * The type of the registered component maps should be the same across all * classes in the inheritance tree. */ protected _getPropagator(name: string): TextMapPropagator | undefined { return ( this.constructor as typeof BasicTracerProvider )._registeredPropagators.get(name)?.(); } protected _getSpanExporter(name: string): SpanExporter | undefined { return ( this.constructor as typeof BasicTracerProvider )._registeredExporters.get(name)?.(); } protected _buildPropagatorFromEnv(): TextMapPropagator | undefined { // per spec, propagators from env must be deduplicated const uniquePropagatorNames = Array.from( new Set(getEnv().OTEL_PROPAGATORS) ); const propagators = uniquePropagatorNames.map(name => { const propagator = this._getPropagator(name); if (!propagator) { diag.warn( `Propagator "${name}" requested through environment variable is unavailable.` ); } return propagator; }); const validPropagators = propagators.reduce<TextMapPropagator[]>( (list, item) => { if (item) { list.push(item); } return list; }, [] ); if (validPropagators.length === 0) { return; } else if (uniquePropagatorNames.length === 1) { return validPropagators[0]; } else { return new CompositePropagator({ propagators: validPropagators, }); } } protected _buildExporterFromEnv(): SpanExporter | undefined { const exporterName = getEnv().OTEL_TRACES_EXPORTER; if (exporterName === 'none' || exporterName === '') return; const exporter = this._getSpanExporter(exporterName); if (!exporter) { diag.error( `Exporter "${exporterName}" requested through environment variable is unavailable.` ); } return exporter; } }