From 3c60157e075415ce1b8e0914c73aafc085d37410 Mon Sep 17 00:00:00 2001 From: Daniel Dyla Date: Mon, 2 Mar 2020 13:47:35 -0500 Subject: [PATCH] feat: use context-based tracing (#816) * feat: use context-based tracing * chore: use withSpan where possible * chore: propagate context in tests * chore: allow spanoptions to override parent from context --- packages/opentelemetry-api/src/api/context.ts | 6 +- .../src/trace/SpanOptions.ts | 11 +- .../opentelemetry-api/src/trace/tracer.ts | 21 +- .../src/NodeTracerProvider.ts | 9 +- .../test/NodeTracerProvider.test.ts | 48 ++- packages/opentelemetry-plugin-dns/src/dns.ts | 1 - .../src/documentLoad.ts | 79 +++-- .../test/documentLoad.test.ts | 27 +- .../opentelemetry-plugin-express/package.json | 1 + .../src/express.ts | 1 - .../test/express.test.ts | 21 +- .../opentelemetry-plugin-grpc/package.json | 2 + .../opentelemetry-plugin-grpc/src/grpc.ts | 102 +++--- .../test/grpc.test.ts | 26 +- .../opentelemetry-plugin-http/package.json | 1 + .../opentelemetry-plugin-http/src/http.ts | 301 ++++++++---------- .../test/functionals/http-enable.test.ts | 36 ++- .../test/functionals/http-package.test.ts | 30 +- .../test/functionals/utils.test.ts | 7 +- .../test/integrations/http-enable.test.ts | 2 - .../test/utils/DummyPropagation.ts | 8 +- .../opentelemetry-plugin-https/package.json | 2 + .../test/functionals/https-disable.test.ts | 3 - .../test/functionals/https-enable.test.ts | 34 +- .../test/functionals/https-package.test.ts | 2 - .../test/integrations/https-enable.test.ts | 3 - .../test/utils/DummyPropagation.ts | 8 +- .../opentelemetry-plugin-ioredis/package.json | 1 + .../opentelemetry-plugin-ioredis/src/utils.ts | 5 - .../test/ioredis.test.ts | 23 +- .../opentelemetry-plugin-mongodb/package.json | 1 + .../src/mongodb.ts | 2 - .../test/mongodb.test.ts | 19 +- .../opentelemetry-plugin-mysql/package.json | 1 + .../opentelemetry-plugin-mysql/src/mysql.ts | 1 - .../test/mysql.test.ts | 11 +- .../opentelemetry-plugin-pg-pool/package.json | 1 + .../src/pg-pool.ts | 1 - .../test/pg-pool.test.ts | 30 +- .../opentelemetry-plugin-pg/package.json | 1 + .../opentelemetry-plugin-pg/src/utils.ts | 1 - .../opentelemetry-plugin-pg/test/pg.test.ts | 58 ++-- .../opentelemetry-plugin-redis/package.json | 1 + .../opentelemetry-plugin-redis/src/utils.ts | 1 - .../test/redis.test.ts | 23 +- .../src/userInteraction.ts | 1 - .../test/userInteraction.nozone.test.ts | 13 +- .../test/userInteraction.test.ts | 17 +- .../src/xhr.ts | 80 +++-- .../test/xhr.test.ts | 33 +- .../src/shim.ts | 80 ++--- .../test/Shim.test.ts | 8 +- packages/opentelemetry-tracing/src/Tracer.ts | 79 ++--- packages/opentelemetry-tracing/src/types.ts | 18 +- .../test/BasicTracerRegistry.test.ts | 132 +++++--- .../test/export/InMemorySpanExporter.test.ts | 13 +- .../src/WebTracerProvider.ts | 8 +- .../test/WebTracerProvider.test.ts | 35 +- 58 files changed, 774 insertions(+), 716 deletions(-) diff --git a/packages/opentelemetry-api/src/api/context.ts b/packages/opentelemetry-api/src/api/context.ts index 6d4b89c4ea..e25b5bd5a8 100644 --- a/packages/opentelemetry-api/src/api/context.ts +++ b/packages/opentelemetry-api/src/api/context.ts @@ -57,12 +57,12 @@ export class ContextAPI { /** * Execute a function with an active context * + * @param context context to be active during function execution * @param fn function to execute in a context - * @param context context to be active during function execution. Defaults to the currently active context */ public with ReturnType>( - fn: T, - context: Context = this.active() + context: Context, + fn: T ): ReturnType { return this._scopeManager.with(context, fn); } diff --git a/packages/opentelemetry-api/src/trace/SpanOptions.ts b/packages/opentelemetry-api/src/trace/SpanOptions.ts index a546876a5f..56d2d38428 100644 --- a/packages/opentelemetry-api/src/trace/SpanOptions.ts +++ b/packages/opentelemetry-api/src/trace/SpanOptions.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { Span } from './span'; import { Attributes } from './attributes'; +import { Link } from './link'; import { SpanKind } from './span_kind'; +import { Span } from './span'; import { SpanContext } from './span_context'; -import { Link } from './link'; /** * Options needed for span creation @@ -43,8 +43,13 @@ export interface SpanOptions { links?: Link[]; /** + * This option is NOT RECOMMENDED for normal use and should ONLY be used + * if your application manages context manually without the global context + * manager, or you are trying to override the parent extracted from context. + * * A parent `SpanContext` (or `Span`, for convenience) that the newly-started - * span will be the child of. + * span will be the child of. This overrides the parent span extracted from + * the currently active context. */ parent?: Span | SpanContext | null; diff --git a/packages/opentelemetry-api/src/trace/tracer.ts b/packages/opentelemetry-api/src/trace/tracer.ts index a959e11c4a..b869036e3c 100644 --- a/packages/opentelemetry-api/src/trace/tracer.ts +++ b/packages/opentelemetry-api/src/trace/tracer.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { HttpTextFormat } from '../context/propagation/HttpTextFormat'; +import { Context } from '@opentelemetry/scope-base'; import { Span } from './span'; import { SpanOptions } from './SpanOptions'; @@ -39,9 +39,10 @@ export interface Tracer { * Starts a new {@link Span}. * @param name The name of the span * @param [options] SpanOptions used for span creation + * @param [context] Context to use to extract parent * @returns Span The newly created span */ - startSpan(name: string, options?: SpanOptions): Span; + startSpan(name: string, options?: SpanOptions, context?: Context): Span; /** * Executes the function given by fn within the context provided by Span @@ -60,19 +61,7 @@ export interface Tracer { * Bind a span as the target's scope or propagate the current one. * * @param target Any object to which a scope need to be set - * @param [span] Optionally specify the span which you want to assign + * @param [context] Optionally specify the context which you want to bind */ - bind(target: T, span?: Span): T; - - /** - * Returns the {@link HttpTextFormat} interface which can inject/extract - * Spans. - * - * If no tracer implementation is provided, this defaults to the W3C Trace - * Context HTTP text format {@link HttpTextFormat}. For more details see - * W3C Trace Context. - * - * @returns the {@link HttpTextFormat} for this implementation. - */ - getHttpTextFormat(): HttpTextFormat; + bind(target: T, context?: Span): T; } diff --git a/packages/opentelemetry-node/src/NodeTracerProvider.ts b/packages/opentelemetry-node/src/NodeTracerProvider.ts index b326a978d7..acdf887dc3 100644 --- a/packages/opentelemetry-node/src/NodeTracerProvider.ts +++ b/packages/opentelemetry-node/src/NodeTracerProvider.ts @@ -15,9 +15,8 @@ */ import { BasicTracerProvider } from '@opentelemetry/tracing'; -import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; +import { DEFAULT_INSTRUMENTATION_PLUGINS, NodeTracerConfig } from './config'; import { PluginLoader } from './instrumentation/PluginLoader'; -import { NodeTracerConfig, DEFAULT_INSTRUMENTATION_PLUGINS } from './config'; /** * This class represents a node tracer with `async_hooks` module. @@ -29,11 +28,7 @@ export class NodeTracerProvider extends BasicTracerProvider { * Constructs a new Tracer instance. */ constructor(config: NodeTracerConfig = {}) { - if (config.scopeManager === undefined) { - config.scopeManager = new AsyncHooksScopeManager(); - config.scopeManager.enable(); - } - super(Object.assign({ scopeManager: config.scopeManager }, config)); + super(config); this._pluginLoader = new PluginLoader(this, this.logger); this._pluginLoader.load(config.plugins || DEFAULT_INSTRUMENTATION_PLUGINS); diff --git a/packages/opentelemetry-node/test/NodeTracerProvider.test.ts b/packages/opentelemetry-node/test/NodeTracerProvider.test.ts index 21fc5919c1..a81dcc99f2 100644 --- a/packages/opentelemetry-node/test/NodeTracerProvider.test.ts +++ b/packages/opentelemetry-node/test/NodeTracerProvider.test.ts @@ -14,18 +14,20 @@ * limitations under the License. */ -import * as assert from 'assert'; +import { context, TraceFlags } from '@opentelemetry/api'; import { ALWAYS_SAMPLER, - HttpTraceContext, NEVER_SAMPLER, NoopLogger, NoRecordingSpan, + setActiveSpan, } from '@opentelemetry/core'; -import { NodeTracerProvider } from '../src/NodeTracerProvider'; -import { TraceFlags } from '@opentelemetry/api'; +import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; import { Span } from '@opentelemetry/tracing'; +import * as assert from 'assert'; import * as path from 'path'; +import { ScopeManager } from '../../opentelemetry-scope-base/build/src'; +import { NodeTracerProvider } from '../src/NodeTracerProvider'; const sleep = (time: number) => new Promise(resolve => { @@ -40,14 +42,21 @@ const INSTALLED_PLUGINS_PATH = path.join( describe('NodeTracerProvider', () => { let provider: NodeTracerProvider; + let scopeManager: ScopeManager; before(() => { module.paths.push(INSTALLED_PLUGINS_PATH); }); + beforeEach(() => { + scopeManager = new AsyncHooksScopeManager(); + context.initGlobalContextManager(scopeManager.enable()); + }); + afterEach(() => { // clear require cache Object.keys(require.cache).forEach(key => delete require.cache[key]); provider.stop(); + scopeManager.disable(); }); describe('constructor', () => { @@ -56,13 +65,6 @@ describe('NodeTracerProvider', () => { assert.ok(provider instanceof NodeTracerProvider); }); - it('should construct an instance with http text format', () => { - provider = new NodeTracerProvider({ - httpTextFormat: new HttpTraceContext(), - }); - assert.ok(provider instanceof NodeTracerProvider); - }); - it('should construct an instance with logger', () => { provider = new NodeTracerProvider({ logger: new NoopLogger(), @@ -194,9 +196,8 @@ describe('NodeTracerProvider', () => { span ); - const span1 = provider - .getTracer('default') - .startSpan('my-span1', { parent: span }); + const span1 = provider.getTracer('default').startSpan('my-span1'); + provider.getTracer('default').withSpan(span1, () => { assert.deepStrictEqual( provider.getTracer('default').getCurrentSpan(), @@ -217,10 +218,10 @@ describe('NodeTracerProvider', () => { ); }); - it('should find correct scope with promises', done => { - provider = new NodeTracerProvider({}); + it('should find correct scope with promises', async () => { + provider = new NodeTracerProvider(); const span = provider.getTracer('default').startSpan('my-span'); - provider.getTracer('default').withSpan(span, async () => { + await provider.getTracer('default').withSpan(span, async () => { for (let i = 0; i < 3; i++) { await sleep(5).then(() => { assert.deepStrictEqual( @@ -229,7 +230,6 @@ describe('NodeTracerProvider', () => { ); }); } - return done(); }); assert.deepStrictEqual( provider.getTracer('default').getCurrentSpan(), @@ -249,18 +249,8 @@ describe('NodeTracerProvider', () => { ); return done(); }; - const patchedFn = provider.getTracer('default').bind(fn, span); + const patchedFn = context.bind(fn, setActiveSpan(context.active(), span)); return patchedFn(); }); }); - - describe('.getHttpTextFormat()', () => { - it('should get default HTTP text formatter', () => { - provider = new NodeTracerProvider({}); - assert.ok( - provider.getTracer('default').getHttpTextFormat() instanceof - HttpTraceContext - ); - }); - }); }); diff --git a/packages/opentelemetry-plugin-dns/src/dns.ts b/packages/opentelemetry-plugin-dns/src/dns.ts index d305079dc7..b52a685b00 100644 --- a/packages/opentelemetry-plugin-dns/src/dns.ts +++ b/packages/opentelemetry-plugin-dns/src/dns.ts @@ -117,7 +117,6 @@ export class DnsPlugin extends BasePlugin { plugin._logger.debug('wrap lookup callback function and starts span'); const name = utils.getOperationName('lookup'); const span = plugin._startDnsSpan(name, { - parent: plugin._tracer.getCurrentSpan(), attributes: { [AttributeNames.PEER_HOSTNAME]: hostname, }, diff --git a/packages/opentelemetry-plugin-document-load/src/documentLoad.ts b/packages/opentelemetry-plugin-document-load/src/documentLoad.ts index cb661df6e5..666d11c061 100644 --- a/packages/opentelemetry-plugin-document-load/src/documentLoad.ts +++ b/packages/opentelemetry-plugin-document-load/src/documentLoad.ts @@ -14,13 +14,18 @@ * limitations under the License. */ +import { + context, + PluginConfig, + propagation, + Span, + SpanOptions, +} from '@opentelemetry/api'; import { BasePlugin, otperformance, - parseTraceParent, TRACE_PARENT_HEADER, } from '@opentelemetry/core'; -import { PluginConfig, Span, SpanOptions } from '@opentelemetry/api'; import { addSpanNetworkEvent, hasKey, @@ -71,9 +76,7 @@ export class DocumentLoad extends BasePlugin { ) as PerformanceResourceTiming[]; if (resources) { resources.forEach(resource => { - this._initResourceSpan(resource, { - parent: rootSpan, - }); + this._initResourceSpan(resource); }); } } @@ -102,40 +105,46 @@ export class DocumentLoad extends BasePlugin { ); const entries = this._getEntries(); - - const rootSpan = this._startSpan( - AttributeNames.DOCUMENT_LOAD, - PTN.FETCH_START, - entries, - { parent: parseTraceParent((metaElement && metaElement.content) || '') } - ); - if (!rootSpan) { - return; - } - const fetchSpan = this._startSpan( - AttributeNames.DOCUMENT_FETCH, - PTN.FETCH_START, - entries, - { - parent: rootSpan, + const traceparent = (metaElement && metaElement.content) || ''; + context.with(propagation.extract({ traceparent }), () => { + const rootSpan = this._startSpan( + AttributeNames.DOCUMENT_LOAD, + PTN.FETCH_START, + entries + ); + if (!rootSpan) { + return; } - ); - if (fetchSpan) { - this._addSpanNetworkEvents(fetchSpan, entries); - this._endSpan(fetchSpan, PTN.RESPONSE_END, entries); - } + this._tracer.withSpan(rootSpan, () => { + const fetchSpan = this._startSpan( + AttributeNames.DOCUMENT_FETCH, + PTN.FETCH_START, + entries + ); + if (fetchSpan) { + this._tracer.withSpan(fetchSpan, () => { + this._addSpanNetworkEvents(fetchSpan, entries); + this._endSpan(fetchSpan, PTN.RESPONSE_END, entries); + }); + } + }); - this._addResourcesSpans(rootSpan); + this._addResourcesSpans(rootSpan); - addSpanNetworkEvent(rootSpan, PTN.UNLOAD_EVENT_START, entries); - addSpanNetworkEvent(rootSpan, PTN.UNLOAD_EVENT_END, entries); - addSpanNetworkEvent(rootSpan, PTN.DOM_INTERACTIVE, entries); - addSpanNetworkEvent(rootSpan, PTN.DOM_CONTENT_LOADED_EVENT_START, entries); - addSpanNetworkEvent(rootSpan, PTN.DOM_CONTENT_LOADED_EVENT_END, entries); - addSpanNetworkEvent(rootSpan, PTN.DOM_COMPLETE, entries); - addSpanNetworkEvent(rootSpan, PTN.LOAD_EVENT_START, entries); + addSpanNetworkEvent(rootSpan, PTN.UNLOAD_EVENT_START, entries); + addSpanNetworkEvent(rootSpan, PTN.UNLOAD_EVENT_END, entries); + addSpanNetworkEvent(rootSpan, PTN.DOM_INTERACTIVE, entries); + addSpanNetworkEvent( + rootSpan, + PTN.DOM_CONTENT_LOADED_EVENT_START, + entries + ); + addSpanNetworkEvent(rootSpan, PTN.DOM_CONTENT_LOADED_EVENT_END, entries); + addSpanNetworkEvent(rootSpan, PTN.DOM_COMPLETE, entries); + addSpanNetworkEvent(rootSpan, PTN.LOAD_EVENT_START, entries); - this._endSpan(rootSpan, PTN.LOAD_EVENT_END, entries); + this._endSpan(rootSpan, PTN.LOAD_EVENT_END, entries); + }); } /** diff --git a/packages/opentelemetry-plugin-document-load/test/documentLoad.test.ts b/packages/opentelemetry-plugin-document-load/test/documentLoad.test.ts index 541044e05a..4b58f7ab01 100644 --- a/packages/opentelemetry-plugin-document-load/test/documentLoad.test.ts +++ b/packages/opentelemetry-plugin-document-load/test/documentLoad.test.ts @@ -18,19 +18,32 @@ * Can't use Sinon Fake Time here as then cannot stub the performance getEntriesByType with desired metrics */ -import { ConsoleLogger, TRACE_PARENT_HEADER } from '@opentelemetry/core'; +import { + context, + Logger, + PluginConfig, + propagation, + TimedEvent, +} from '@opentelemetry/api'; +import { + ConsoleLogger, + HttpTraceContext, + TRACE_PARENT_HEADER, +} from '@opentelemetry/core'; import { BasicTracerProvider, ReadableSpan, SimpleSpanProcessor, SpanExporter, } from '@opentelemetry/tracing'; -import { Logger, PluginConfig, TimedEvent } from '@opentelemetry/api'; +import { + PerformanceTimingNames as PTN, + StackScopeManager, +} from '@opentelemetry/web'; import * as assert from 'assert'; import * as sinon from 'sinon'; import { ExportResult } from '../../opentelemetry-base/build/src'; import { DocumentLoad } from '../src'; -import { PerformanceTimingNames as PTN } from '@opentelemetry/web'; export class DummyExporter implements SpanExporter { export( @@ -196,8 +209,11 @@ describe('DocumentLoad Plugin', () => { let config: PluginConfig; let spanProcessor: SimpleSpanProcessor; let dummyExporter: DummyExporter; + let scopeManager: StackScopeManager; beforeEach(() => { + scopeManager = new StackScopeManager().enable(); + context.initGlobalContextManager(scopeManager); Object.defineProperty(window.document, 'readyState', { writable: true, value: 'complete', @@ -213,12 +229,17 @@ describe('DocumentLoad Plugin', () => { }); afterEach(() => { + scopeManager.disable(); Object.defineProperty(window.document, 'readyState', { writable: true, value: 'complete', }); }); + before(() => { + propagation.initGlobalPropagator(new HttpTraceContext()); + }); + describe('constructor', () => { it('should construct an instance', () => { plugin = new DocumentLoad(); diff --git a/packages/opentelemetry-plugin-express/package.json b/packages/opentelemetry-plugin-express/package.json index c0ed0ae612..400ff9ef1f 100644 --- a/packages/opentelemetry-plugin-express/package.json +++ b/packages/opentelemetry-plugin-express/package.json @@ -42,6 +42,7 @@ }, "devDependencies": { "@opentelemetry/node": "^0.4.0", + "@opentelemetry/scope-async-hooks": "^0.4.0", "@opentelemetry/tracing": "^0.4.0", "@types/express": "^4.17.2", "@types/mocha": "^5.2.7", diff --git a/packages/opentelemetry-plugin-express/src/express.ts b/packages/opentelemetry-plugin-express/src/express.ts index 6186b03696..7800cf67dc 100644 --- a/packages/opentelemetry-plugin-express/src/express.ts +++ b/packages/opentelemetry-plugin-express/src/express.ts @@ -188,7 +188,6 @@ export class ExpressPlugin extends BasePlugin { return original.apply(this, arguments); } const span = plugin._tracer.startSpan(metadata.name, { - parent: plugin._tracer.getCurrentSpan(), attributes: Object.assign(attributes, metadata.attributes), }); // verify we have a callback diff --git a/packages/opentelemetry-plugin-express/test/express.test.ts b/packages/opentelemetry-plugin-express/test/express.test.ts index 231663f1b9..f1766f6d65 100644 --- a/packages/opentelemetry-plugin-express/test/express.test.ts +++ b/packages/opentelemetry-plugin-express/test/express.test.ts @@ -14,21 +14,23 @@ * limitations under the License. */ +import { context } from '@opentelemetry/api'; +import { NoopLogger } from '@opentelemetry/core'; import { NodeTracerProvider } from '@opentelemetry/node'; +import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; +import { + InMemorySpanExporter, + SimpleSpanProcessor, +} from '@opentelemetry/tracing'; import * as assert from 'assert'; import * as express from 'express'; import * as http from 'http'; import { AddressInfo } from 'net'; import { plugin } from '../src'; -import { NoopLogger } from '@opentelemetry/core'; -import { - InMemorySpanExporter, - SimpleSpanProcessor, -} from '@opentelemetry/tracing'; import { AttributeNames, - ExpressPluginConfig, ExpressLayerType, + ExpressPluginConfig, } from '../src/types'; const httpRequest = { @@ -57,13 +59,20 @@ describe('Express Plugin', () => { const spanProcessor = new SimpleSpanProcessor(memoryExporter); provider.addSpanProcessor(spanProcessor); const tracer = provider.getTracer('default'); + let scopeManager: AsyncHooksScopeManager; before(() => { plugin.enable(express, provider, logger); }); + beforeEach(() => { + scopeManager = new AsyncHooksScopeManager(); + context.initGlobalContextManager(scopeManager.enable()); + }); + afterEach(() => { memoryExporter.reset(); + scopeManager.disable(); }); describe('Instrumenting normal get operations', () => { diff --git a/packages/opentelemetry-plugin-grpc/package.json b/packages/opentelemetry-plugin-grpc/package.json index cb03707dbe..2cd07f8d1f 100644 --- a/packages/opentelemetry-plugin-grpc/package.json +++ b/packages/opentelemetry-plugin-grpc/package.json @@ -42,6 +42,8 @@ }, "devDependencies": { "@opentelemetry/node": "^0.4.0", + "@opentelemetry/scope-async-hooks": "^0.4.0", + "@opentelemetry/scope-base": "^0.4.0", "@opentelemetry/tracing": "^0.4.0", "@types/mocha": "^5.2.7", "@types/node": "^12.6.9", diff --git a/packages/opentelemetry-plugin-grpc/src/grpc.ts b/packages/opentelemetry-plugin-grpc/src/grpc.ts index 9123a2ce2f..3e08a5a168 100644 --- a/packages/opentelemetry-plugin-grpc/src/grpc.ts +++ b/packages/opentelemetry-plugin-grpc/src/grpc.ts @@ -14,20 +14,16 @@ * limitations under the License. */ -import { - BasePlugin, - getExtractedSpanContext, - setExtractedSpanContext, -} from '@opentelemetry/core'; import { CanonicalCode, + context, + propagation, Span, - SpanContext, SpanKind, SpanOptions, Status, - Context, } from '@opentelemetry/api'; +import { BasePlugin } from '@opentelemetry/core'; import * as events from 'events'; import * as grpcTypes from 'grpc'; import * as path from 'path'; @@ -127,22 +123,9 @@ export class GrpcPlugin extends BasePlugin { } } - private _getSpanContext( - metadata: grpcTypes.Metadata - ): SpanContext | undefined { - return getExtractedSpanContext( - this._tracer.getHttpTextFormat().extract(Context.TODO, metadata.getMap()) - ); - } - - private _setSpanContext( - metadata: grpcTypes.Metadata, - spanContext: SpanContext - ): void { + private _setSpanContext(metadata: grpcTypes.Metadata): void { const carrier = {}; - this._tracer - .getHttpTextFormat() - .inject(setExtractedSpanContext(Context.TODO, spanContext), carrier); + propagation.inject(carrier); for (const [k, v] of Object.entries(carrier)) { metadata.set(k, v as string); } @@ -180,7 +163,6 @@ export class GrpcPlugin extends BasePlugin { const spanName = `grpc.${name.replace('/', '')}`; const spanOptions: SpanOptions = { kind: SpanKind.SERVER, - parent: plugin._getSpanContext(call.metadata), }; plugin._logger.debug( @@ -188,37 +170,39 @@ export class GrpcPlugin extends BasePlugin { JSON.stringify(spanOptions) ); - const span = plugin._tracer - .startSpan(spanName, spanOptions) - .setAttributes({ - [AttributeNames.GRPC_KIND]: spanOptions.kind, - [AttributeNames.COMPONENT]: GrpcPlugin.component, + context.with(propagation.extract(call.metadata.getMap()), () => { + const span = plugin._tracer + .startSpan(spanName, spanOptions) + .setAttributes({ + [AttributeNames.GRPC_KIND]: spanOptions.kind, + [AttributeNames.COMPONENT]: GrpcPlugin.component, + }); + + plugin._tracer.withSpan(span, () => { + switch (type) { + case 'unary': + case 'client_stream': + return plugin._clientStreamAndUnaryHandler( + plugin, + span, + call, + callback, + originalFunc, + self + ); + case 'server_stream': + case 'bidi': + return plugin._serverStreamAndBidiHandler( + plugin, + span, + call, + originalFunc, + self + ); + default: + break; + } }); - - plugin._tracer.withSpan(span, () => { - switch (type) { - case 'unary': - case 'client_stream': - return plugin._clientStreamAndUnaryHandler( - plugin, - span, - call, - callback, - originalFunc, - self - ); - case 'server_stream': - case 'bidi': - return plugin._serverStreamAndBidiHandler( - plugin, - span, - call, - originalFunc, - self - ); - default: - break; - } }); }; } @@ -371,15 +355,11 @@ export class GrpcPlugin extends BasePlugin { const span = plugin._tracer .startSpan(name, { kind: SpanKind.CLIENT, - parent: plugin._tracer.getCurrentSpan(), }) .setAttribute(AttributeNames.COMPONENT, GrpcPlugin.component); - return plugin._makeGrpcClientRemoteCall( - original, - args, - this, - plugin - )(span); + return plugin._tracer.withSpan(span, () => + plugin._makeGrpcClientRemoteCall(original, args, this, plugin)(span) + ); }; }; } @@ -457,7 +437,7 @@ export class GrpcPlugin extends BasePlugin { [AttributeNames.GRPC_KIND]: SpanKind.CLIENT, }); - this._setSpanContext(metadata, span.context()); + this._setSpanContext(metadata); const call = original.apply(self, args); // if server stream or bidi diff --git a/packages/opentelemetry-plugin-grpc/test/grpc.test.ts b/packages/opentelemetry-plugin-grpc/test/grpc.test.ts index 0cd3b67669..25bc3f6a01 100644 --- a/packages/opentelemetry-plugin-grpc/test/grpc.test.ts +++ b/packages/opentelemetry-plugin-grpc/test/grpc.test.ts @@ -14,9 +14,16 @@ * limitations under the License. */ -import { NoopTracerProvider, SpanKind } from '@opentelemetry/api'; -import { NoopLogger } from '@opentelemetry/core'; +import { + context, + NoopTracerProvider, + SpanKind, + propagation, +} from '@opentelemetry/api'; +import { NoopLogger, HttpTraceContext } from '@opentelemetry/core'; import { NodeTracerProvider } from '@opentelemetry/node'; +import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; +import { ScopeManager } from '@opentelemetry/scope-base'; import { InMemorySpanExporter, SimpleSpanProcessor, @@ -293,6 +300,21 @@ function createClient(grpc: GrpcModule, proto: any) { } describe('GrpcPlugin', () => { + let scopeManger: ScopeManager; + + before(() => { + propagation.initGlobalPropagator(new HttpTraceContext()); + }); + + beforeEach(() => { + scopeManger = new AsyncHooksScopeManager().enable(); + context.initGlobalContextManager(scopeManger); + }); + + afterEach(() => { + scopeManger.disable(); + }); + it('should return a plugin', () => { assert.ok(plugin instanceof GrpcPlugin); }); diff --git a/packages/opentelemetry-plugin-http/package.json b/packages/opentelemetry-plugin-http/package.json index 9c550c69f0..384f16d0ce 100644 --- a/packages/opentelemetry-plugin-http/package.json +++ b/packages/opentelemetry-plugin-http/package.json @@ -42,6 +42,7 @@ }, "devDependencies": { "@opentelemetry/node": "^0.4.0", + "@opentelemetry/scope-async-hooks": "^0.4.0", "@opentelemetry/scope-base": "^0.4.0", "@opentelemetry/tracing": "^0.4.0", "@types/got": "^9.6.7", diff --git a/packages/opentelemetry-plugin-http/src/http.ts b/packages/opentelemetry-plugin-http/src/http.ts index 1107311305..53bbebe5bb 100644 --- a/packages/opentelemetry-plugin-http/src/http.ts +++ b/packages/opentelemetry-plugin-http/src/http.ts @@ -16,18 +16,14 @@ import { CanonicalCode, - Context, + context, + propagation, Span, SpanKind, SpanOptions, Status, } from '@opentelemetry/api'; -import { - BasePlugin, - getExtractedSpanContext, - isValid, - setActiveSpan, -} from '@opentelemetry/core'; +import { BasePlugin } from '@opentelemetry/core'; import { ClientRequest, IncomingMessage, @@ -175,87 +171,83 @@ export class HttpPlugin extends BasePlugin { } /** - * Injects span's context to header for distributed tracing and finishes the - * span when the response is finished. + * Attach event listeners to a client request to end span and add span attributes. + * * @param request The original request object. * @param options The arguments to the original function. * @param span representing the current operation */ - private _getMakeRequestTraceFunction( + private _traceClientRequest( request: ClientRequest, options: ParsedRequestOptions, span: Span - ): Func { - return (): ClientRequest => { - this._logger.debug('makeRequestTrace by injecting context into header'); - - const hostname = - options.hostname || - options.host?.replace(/^(.*)(\:[0-9]{1,5})/, '$1') || - 'localhost'; - const attributes = utils.getOutgoingRequestAttributes(options, { - component: this.component, - hostname, - }); - span.setAttributes(attributes); - - request.on( - 'response', - (response: IncomingMessage & { aborted?: boolean }) => { - const attributes = utils.getOutgoingRequestAttributesOnResponse( - response, - { hostname } - ); - span.setAttributes(attributes); - - this._tracer.bind(response); - this._logger.debug('outgoingRequest on response()'); - response.on('end', () => { - this._logger.debug('outgoingRequest on end()'); - let status: Status; - - if (response.aborted && !response.complete) { - status = { code: CanonicalCode.ABORTED }; - } else { - status = utils.parseResponseStatus(response.statusCode!); - } + ): ClientRequest { + const hostname = + options.hostname || + options.host?.replace(/^(.*)(\:[0-9]{1,5})/, '$1') || + 'localhost'; + const attributes = utils.getOutgoingRequestAttributes(options, { + component: this.component, + hostname, + }); + span.setAttributes(attributes); + + request.on( + 'response', + (response: IncomingMessage & { aborted?: boolean }) => { + const attributes = utils.getOutgoingRequestAttributesOnResponse( + response, + { hostname } + ); + span.setAttributes(attributes); + + this._tracer.bind(response); + this._logger.debug('outgoingRequest on response()'); + response.on('end', () => { + this._logger.debug('outgoingRequest on end()'); + let status: Status; + + if (response.aborted && !response.complete) { + status = { code: CanonicalCode.ABORTED }; + } else { + status = utils.parseResponseStatus(response.statusCode!); + } - span.setStatus(status); + span.setStatus(status); - if (this._config.applyCustomAttributesOnSpan) { - this._safeExecute( - span, - () => - this._config.applyCustomAttributesOnSpan!( - span, - request, - response - ), - false - ); - } + if (this._config.applyCustomAttributesOnSpan) { + this._safeExecute( + span, + () => + this._config.applyCustomAttributesOnSpan!( + span, + request, + response + ), + false + ); + } - this._closeHttpSpan(span); - }); - response.on('error', (error: Err) => { - utils.setSpanWithError(span, error, response); - this._closeHttpSpan(span); - }); - } - ); - request.on('close', () => { - if (!request.aborted) { this._closeHttpSpan(span); - } - }); - request.on('error', (error: Err) => { - utils.setSpanWithError(span, error, request); + }); + response.on('error', (error: Err) => { + utils.setSpanWithError(span, error, response); + this._closeHttpSpan(span); + }); + } + ); + request.on('close', () => { + if (!request.aborted) { this._closeHttpSpan(span); - }); - - this._logger.debug('makeRequestTrace return request'); - return request; - }; + } + }); + request.on('error', (error: Err) => { + utils.setSpanWithError(span, error, request); + this._closeHttpSpan(span); + }); + + this._logger.debug('_traceClientRequest return request'); + return request; } private _incomingRequestFunction( @@ -292,7 +284,6 @@ export class HttpPlugin extends BasePlugin { return original.apply(this, [event, ...args]); } - const propagation = plugin._tracer.getHttpTextFormat(); const headers = request.headers; const spanOptions: SpanOptions = { @@ -303,69 +294,65 @@ export class HttpPlugin extends BasePlugin { }), }; - // Using context directly like this is temporary. In a future PR, context - // will be managed by the scope manager (which may be renamed to context manager?) - const spanContext = getExtractedSpanContext( - propagation.extract(Context.TODO, headers) - ); - if (spanContext && isValid(spanContext)) { - spanOptions.parent = spanContext; - } - - const span = plugin._startHttpSpan(`${method} ${pathname}`, spanOptions); + return context.with(propagation.extract(headers), () => { + const span = plugin._startHttpSpan( + `${method} ${pathname}`, + spanOptions + ); - return plugin._tracer.withSpan(span, () => { - plugin._tracer.bind(request); - plugin._tracer.bind(response); - - // Wraps end (inspired by: - // https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/blob/master/src/plugins/plugin-connect.ts#L75) - const originalEnd = response.end; - response.end = function( - this: ServerResponse, - ...args: ResponseEndArgs - ) { - response.end = originalEnd; - // Cannot pass args of type ResponseEndArgs, - // tslint complains "Expected 1-2 arguments, but got 1 or more.", it does not make sense to me - const returned = plugin._safeExecute( - span, - // tslint:disable-next-line:no-any - () => response.end.apply(this, arguments as any), - true - ); + return plugin._tracer.withSpan(span, () => { + context.bind(request); + context.bind(response); + + // Wraps end (inspired by: + // https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/blob/master/src/plugins/plugin-connect.ts#L75) + const originalEnd = response.end; + response.end = function( + this: ServerResponse, + ...args: ResponseEndArgs + ) { + response.end = originalEnd; + // Cannot pass args of type ResponseEndArgs, + // tslint complains "Expected 1-2 arguments, but got 1 or more.", it does not make sense to me + const returned = plugin._safeExecute( + span, + // tslint:disable-next-line:no-any + () => response.end.apply(this, arguments as any), + true + ); - const attributes = utils.getIncomingRequestAttributesOnResponse( - request, - response - ); + const attributes = utils.getIncomingRequestAttributesOnResponse( + request, + response + ); - span - .setAttributes(attributes) - .setStatus(utils.parseResponseStatus(response.statusCode)); + span + .setAttributes(attributes) + .setStatus(utils.parseResponseStatus(response.statusCode)); - if (plugin._config.applyCustomAttributesOnSpan) { - plugin._safeExecute( - span, - () => - plugin._config.applyCustomAttributesOnSpan!( - span, - request, - response - ), - false - ); - } + if (plugin._config.applyCustomAttributesOnSpan) { + plugin._safeExecute( + span, + () => + plugin._config.applyCustomAttributesOnSpan!( + span, + request, + response + ), + false + ); + } - plugin._closeHttpSpan(span); - return returned; - }; + plugin._closeHttpSpan(span); + return returned; + }; - return plugin._safeExecute( - span, - () => original.apply(this, [event, ...args]), - true - ); + return plugin._safeExecute( + span, + () => original.apply(this, [event, ...args]), + true + ); + }); }); }; } @@ -393,10 +380,8 @@ export class HttpPlugin extends BasePlugin { extraOptions ); - options = optionsParsed; - if ( - utils.isOpenTelemetryRequest(options) || + utils.isOpenTelemetryRequest(optionsParsed) || utils.isIgnored( origin + pathname, plugin._config.ignoreOutgoingUrls, @@ -404,48 +389,30 @@ export class HttpPlugin extends BasePlugin { plugin._logger.error('caught ignoreOutgoingUrls error: ', e) ) ) { - return original.apply(this, [options, ...args]); + return original.apply(this, [optionsParsed, ...args]); } - const currentSpan = plugin._tracer.getCurrentSpan(); const operationName = `${method} ${pathname}`; const spanOptions: SpanOptions = { kind: SpanKind.CLIENT, - parent: currentSpan ? currentSpan : undefined, }; const span = plugin._startHttpSpan(operationName, spanOptions); - if (!options.headers) options.headers = {}; - plugin._tracer - .getHttpTextFormat() - // Using context directly like this is temporary. In a future PR, context - // will be managed by the scope manager (which may be renamed to context manager?) - .inject(setActiveSpan(Context.TODO, span), options.headers); - - const request: ClientRequest = plugin._safeExecute( - span, - () => original.apply(this, [options, ...args]), - true - ); - - plugin._logger.debug('%s plugin outgoingRequest', plugin.moduleName); - plugin._tracer.bind(request); + return plugin._tracer.withSpan(span, () => { + if (!optionsParsed.headers) optionsParsed.headers = {}; + propagation.inject(optionsParsed.headers); - // Checks if this outgoing request is part of an operation by checking - // if there is a current span, if so, we create a child span. In - // case there is no active span, this means that the outgoing request is - // the first operation, therefore we create a span and call withSpan method. - if (!currentSpan) { - plugin._logger.debug('outgoingRequest starting a span without context'); - return plugin._tracer.withSpan( + const request: ClientRequest = plugin._safeExecute( span, - plugin._getMakeRequestTraceFunction(request, options, span) + () => original.apply(this, [optionsParsed, ...args]), + true ); - } else { - plugin._logger.debug('outgoingRequest starting a child span'); - return plugin._getMakeRequestTraceFunction(request, options, span)(); - } + + plugin._logger.debug('%s plugin outgoingRequest', plugin.moduleName); + plugin._tracer.bind(request); + return plugin._traceClientRequest(request, optionsParsed, span); + }); }; } diff --git a/packages/opentelemetry-plugin-http/test/functionals/http-enable.test.ts b/packages/opentelemetry-plugin-http/test/functionals/http-enable.test.ts index 32042d66d1..9564763595 100644 --- a/packages/opentelemetry-plugin-http/test/functionals/http-enable.test.ts +++ b/packages/opentelemetry-plugin-http/test/functionals/http-enable.test.ts @@ -14,24 +14,32 @@ * limitations under the License. */ +import { + CanonicalCode, + Span as ISpan, + SpanKind, + propagation, + context, +} from '@opentelemetry/api'; +import { NoopLogger } from '@opentelemetry/core'; +import { NodeTracerProvider } from '@opentelemetry/node'; import { InMemorySpanExporter, SimpleSpanProcessor, } from '@opentelemetry/tracing'; -import { NoopLogger } from '@opentelemetry/core'; -import { NodeTracerProvider } from '@opentelemetry/node'; -import { CanonicalCode, Span as ISpan, SpanKind } from '@opentelemetry/api'; import * as assert from 'assert'; import * as http from 'http'; -import * as path from 'path'; import * as nock from 'nock'; +import * as path from 'path'; +import { AttributeNames } from '../../src/enums/AttributeNames'; import { HttpPlugin, plugin } from '../../src/http'; +import { Http, HttpPluginConfig } from '../../src/types'; +import { OT_REQUEST_HEADER } from '../../src/utils'; import { assertSpan } from '../utils/assertSpan'; import { DummyPropagation } from '../utils/DummyPropagation'; import { httpRequest } from '../utils/httpRequest'; -import { OT_REQUEST_HEADER } from '../../src/utils'; -import { HttpPluginConfig, Http } from '../../src/types'; -import { AttributeNames } from '../../src/enums/AttributeNames'; +import { ScopeManager } from '@opentelemetry/scope-base'; +import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; const applyCustomAttributesOnSpanErrorMessage = 'bad applyCustomAttributesOnSpan function'; @@ -43,13 +51,12 @@ const hostname = 'localhost'; const pathname = '/test'; const serverName = 'my.server.name'; const memoryExporter = new InMemorySpanExporter(); -const httpTextFormat = new DummyPropagation(); const logger = new NoopLogger(); const provider = new NodeTracerProvider({ logger, - httpTextFormat, }); provider.addSpanProcessor(new SimpleSpanProcessor(memoryExporter)); +propagation.initGlobalPropagator(new DummyPropagation()); function doNock( hostname: string, @@ -70,6 +77,17 @@ export const customAttributeFunction = (span: ISpan): void => { }; describe('HttpPlugin', () => { + let scopeManger: ScopeManager; + + beforeEach(() => { + scopeManger = new AsyncHooksScopeManager().enable(); + context.initGlobalContextManager(scopeManger); + }); + + afterEach(() => { + scopeManger.disable(); + }); + it('should return a plugin', () => { assert.ok(plugin instanceof HttpPlugin); }); diff --git a/packages/opentelemetry-plugin-http/test/functionals/http-package.test.ts b/packages/opentelemetry-plugin-http/test/functionals/http-package.test.ts index 4f00515562..c6880fda54 100644 --- a/packages/opentelemetry-plugin-http/test/functionals/http-package.test.ts +++ b/packages/opentelemetry-plugin-http/test/functionals/http-package.test.ts @@ -14,27 +14,26 @@ * limitations under the License. */ -import { NoopLogger } from '@opentelemetry/core'; import { SpanKind } from '@opentelemetry/api'; -import * as assert from 'assert'; -import * as http from 'http'; -import * as nock from 'nock'; -import { plugin } from '../../src/http'; -import { assertSpan } from '../utils/assertSpan'; -import { DummyPropagation } from '../utils/DummyPropagation'; -import * as url from 'url'; -import axios, { AxiosResponse } from 'axios'; -import * as superagent from 'superagent'; -import * as got from 'got'; -import * as request from 'request-promise-native'; -import * as path from 'path'; +import { NoopLogger } from '@opentelemetry/core'; import { NodeTracerProvider } from '@opentelemetry/node'; import { InMemorySpanExporter, SimpleSpanProcessor, } from '@opentelemetry/tracing'; - +import * as assert from 'assert'; +import axios, { AxiosResponse } from 'axios'; +import * as got from 'got'; +import * as http from 'http'; +import * as nock from 'nock'; +import * as path from 'path'; +import * as request from 'request-promise-native'; +import * as superagent from 'superagent'; +import * as url from 'url'; +import { plugin } from '../../src/http'; import { HttpPluginConfig } from '../../src/types'; +import { assertSpan } from '../utils/assertSpan'; +import { DummyPropagation } from '../utils/DummyPropagation'; import { customAttributeFunction } from './http-enable.test'; const memoryExporter = new InMemorySpanExporter(); @@ -42,12 +41,9 @@ const protocol = 'http'; describe('Packages', () => { describe('get', () => { - const httpTextFormat = new DummyPropagation(); const logger = new NoopLogger(); - const provider = new NodeTracerProvider({ logger, - httpTextFormat, }); provider.addSpanProcessor(new SimpleSpanProcessor(memoryExporter)); beforeEach(() => { diff --git a/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts index 8bfb673fcf..2e84582598 100644 --- a/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts +++ b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts @@ -14,10 +14,9 @@ * limitations under the License. */ +import { CanonicalCode, SpanKind } from '@opentelemetry/api'; import { NoopLogger } from '@opentelemetry/core'; -import { NoopScopeManager } from '@opentelemetry/scope-base'; import { BasicTracerProvider, Span } from '@opentelemetry/tracing'; -import { CanonicalCode, SpanKind } from '@opentelemetry/api'; import * as assert from 'assert'; import * as http from 'http'; import * as sinon from 'sinon'; @@ -255,9 +254,7 @@ describe('Utility', () => { const errorMessage = 'test error'; for (const obj of [undefined, { statusCode: 400 }]) { const span = new Span( - new BasicTracerProvider({ - scopeManager: new NoopScopeManager(), - }).getTracer('default'), + new BasicTracerProvider().getTracer('default'), 'test', { spanId: '', traceId: '' }, SpanKind.INTERNAL diff --git a/packages/opentelemetry-plugin-http/test/integrations/http-enable.test.ts b/packages/opentelemetry-plugin-http/test/integrations/http-enable.test.ts index 6f95768ef3..0a529c684e 100644 --- a/packages/opentelemetry-plugin-http/test/integrations/http-enable.test.ts +++ b/packages/opentelemetry-plugin-http/test/integrations/http-enable.test.ts @@ -58,11 +58,9 @@ describe('HttpPlugin Integration tests', () => { }); }); - const httpTextFormat = new DummyPropagation(); const logger = new NoopLogger(); const provider = new NodeTracerProvider({ logger, - httpTextFormat, }); provider.addSpanProcessor(new SimpleSpanProcessor(memoryExporter)); beforeEach(() => { diff --git a/packages/opentelemetry-plugin-http/test/utils/DummyPropagation.ts b/packages/opentelemetry-plugin-http/test/utils/DummyPropagation.ts index 5c65bd16d7..69f809b453 100644 --- a/packages/opentelemetry-plugin-http/test/utils/DummyPropagation.ts +++ b/packages/opentelemetry-plugin-http/test/utils/DummyPropagation.ts @@ -24,10 +24,14 @@ export class DummyPropagation implements HttpTextFormat { static TRACE_CONTEXT_KEY = 'x-dummy-trace-id'; static SPAN_CONTEXT_KEY = 'x-dummy-span-id'; extract(context: Context, carrier: http.OutgoingHttpHeaders) { - return setExtractedSpanContext(context, { + const extractedSpanContext = { traceId: carrier[DummyPropagation.TRACE_CONTEXT_KEY] as string, spanId: DummyPropagation.SPAN_CONTEXT_KEY, - }); + }; + if (extractedSpanContext.traceId && extractedSpanContext.spanId) { + return setExtractedSpanContext(context, extractedSpanContext); + } + return context; } inject(context: Context, headers: { [custom: string]: string }): void { const spanContext = getParentSpanContext(context); diff --git a/packages/opentelemetry-plugin-https/package.json b/packages/opentelemetry-plugin-https/package.json index e71888eb20..12ea4c4398 100644 --- a/packages/opentelemetry-plugin-https/package.json +++ b/packages/opentelemetry-plugin-https/package.json @@ -42,6 +42,8 @@ }, "devDependencies": { "@opentelemetry/node": "^0.4.0", + "@opentelemetry/scope-async-hooks": "^0.4.0", + "@opentelemetry/scope-base": "^0.4.0", "@opentelemetry/tracing": "^0.4.0", "@types/got": "^9.6.7", "@types/mocha": "^5.2.7", diff --git a/packages/opentelemetry-plugin-https/test/functionals/https-disable.test.ts b/packages/opentelemetry-plugin-https/test/functionals/https-disable.test.ts index 4f7e92339d..572ea6ea1a 100644 --- a/packages/opentelemetry-plugin-https/test/functionals/https-disable.test.ts +++ b/packages/opentelemetry-plugin-https/test/functionals/https-disable.test.ts @@ -23,7 +23,6 @@ import { AddressInfo } from 'net'; import * as nock from 'nock'; import * as sinon from 'sinon'; import { plugin } from '../../src/https'; -import { DummyPropagation } from '../utils/DummyPropagation'; import { httpsRequest } from '../utils/httpsRequest'; import { NodeTracerProvider } from '@opentelemetry/node'; import * as types from '@opentelemetry/api'; @@ -33,11 +32,9 @@ describe('HttpsPlugin', () => { let serverPort = 0; describe('disable()', () => { - const httpTextFormat = new DummyPropagation(); const logger = new NoopLogger(); const provider = new NodeTracerProvider({ logger, - httpTextFormat, }); // const tracer = provider.getTracer('test-https') let tracer: types.Tracer; diff --git a/packages/opentelemetry-plugin-https/test/functionals/https-enable.test.ts b/packages/opentelemetry-plugin-https/test/functionals/https-enable.test.ts index 5cd755a0e4..e393cc4d79 100644 --- a/packages/opentelemetry-plugin-https/test/functionals/https-enable.test.ts +++ b/packages/opentelemetry-plugin-https/test/functionals/https-enable.test.ts @@ -15,24 +15,32 @@ */ import { - InMemorySpanExporter, - SimpleSpanProcessor, -} from '@opentelemetry/tracing'; + CanonicalCode, + context, + propagation, + Span as ISpan, + SpanKind, +} from '@opentelemetry/api'; import { NoopLogger } from '@opentelemetry/core'; import { NodeTracerProvider } from '@opentelemetry/node'; import { + AttributeNames, Http, HttpPluginConfig, OT_REQUEST_HEADER, - AttributeNames, } from '@opentelemetry/plugin-http'; -import { CanonicalCode, Span as ISpan, SpanKind } from '@opentelemetry/api'; +import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; +import { ScopeManager } from '@opentelemetry/scope-base'; +import { + InMemorySpanExporter, + SimpleSpanProcessor, +} from '@opentelemetry/tracing'; import * as assert from 'assert'; import * as fs from 'fs'; import * as http from 'http'; import * as https from 'https'; -import * as path from 'path'; import * as nock from 'nock'; +import * as path from 'path'; import { HttpsPlugin, plugin } from '../../src/https'; import { assertSpan } from '../utils/assertSpan'; import { DummyPropagation } from '../utils/DummyPropagation'; @@ -48,14 +56,13 @@ const hostname = 'localhost'; const serverName = 'my.server.name'; const pathname = '/test'; const memoryExporter = new InMemorySpanExporter(); -const httpTextFormat = new DummyPropagation(); const logger = new NoopLogger(); const provider = new NodeTracerProvider({ logger, - httpTextFormat, }); const tracer = provider.getTracer('test-https'); provider.addSpanProcessor(new SimpleSpanProcessor(memoryExporter)); +propagation.initGlobalPropagator(new DummyPropagation()); function doNock( hostname: string, @@ -76,6 +83,17 @@ export const customAttributeFunction = (span: ISpan): void => { }; describe('HttpsPlugin', () => { + let scopeManger: ScopeManager; + + beforeEach(() => { + scopeManger = new AsyncHooksScopeManager().enable(); + context.initGlobalContextManager(scopeManger); + }); + + afterEach(() => { + scopeManger.disable(); + }); + it('should return a plugin', () => { assert.ok(plugin instanceof HttpsPlugin); }); diff --git a/packages/opentelemetry-plugin-https/test/functionals/https-package.test.ts b/packages/opentelemetry-plugin-https/test/functionals/https-package.test.ts index bf4d0e67c0..106d00a32e 100644 --- a/packages/opentelemetry-plugin-https/test/functionals/https-package.test.ts +++ b/packages/opentelemetry-plugin-https/test/functionals/https-package.test.ts @@ -44,12 +44,10 @@ export const customAttributeFunction = (span: Span): void => { describe('Packages', () => { describe('get', () => { - const httpTextFormat = new DummyPropagation(); const logger = new NoopLogger(); const provider = new NodeTracerProvider({ logger, - httpTextFormat, }); provider.addSpanProcessor(new SimpleSpanProcessor(memoryExporter)); beforeEach(() => { diff --git a/packages/opentelemetry-plugin-https/test/integrations/https-enable.test.ts b/packages/opentelemetry-plugin-https/test/integrations/https-enable.test.ts index 47bb93c03e..fa8be0e518 100644 --- a/packages/opentelemetry-plugin-https/test/integrations/https-enable.test.ts +++ b/packages/opentelemetry-plugin-https/test/integrations/https-enable.test.ts @@ -62,12 +62,9 @@ describe('HttpsPlugin Integration tests', () => { done(); }); }); - - const httpTextFormat = new DummyPropagation(); const logger = new NoopLogger(); const provider = new NodeTracerProvider({ logger, - httpTextFormat, }); provider.addSpanProcessor(new SimpleSpanProcessor(memoryExporter)); beforeEach(() => { diff --git a/packages/opentelemetry-plugin-https/test/utils/DummyPropagation.ts b/packages/opentelemetry-plugin-https/test/utils/DummyPropagation.ts index 5c65bd16d7..69f809b453 100644 --- a/packages/opentelemetry-plugin-https/test/utils/DummyPropagation.ts +++ b/packages/opentelemetry-plugin-https/test/utils/DummyPropagation.ts @@ -24,10 +24,14 @@ export class DummyPropagation implements HttpTextFormat { static TRACE_CONTEXT_KEY = 'x-dummy-trace-id'; static SPAN_CONTEXT_KEY = 'x-dummy-span-id'; extract(context: Context, carrier: http.OutgoingHttpHeaders) { - return setExtractedSpanContext(context, { + const extractedSpanContext = { traceId: carrier[DummyPropagation.TRACE_CONTEXT_KEY] as string, spanId: DummyPropagation.SPAN_CONTEXT_KEY, - }); + }; + if (extractedSpanContext.traceId && extractedSpanContext.spanId) { + return setExtractedSpanContext(context, extractedSpanContext); + } + return context; } inject(context: Context, headers: { [custom: string]: string }): void { const spanContext = getParentSpanContext(context); diff --git a/packages/opentelemetry-plugin-ioredis/package.json b/packages/opentelemetry-plugin-ioredis/package.json index 320d8cd829..7e1b5d31cf 100644 --- a/packages/opentelemetry-plugin-ioredis/package.json +++ b/packages/opentelemetry-plugin-ioredis/package.json @@ -45,6 +45,7 @@ }, "devDependencies": { "@opentelemetry/node": "^0.4.0", + "@opentelemetry/scope-async-hooks": "^0.4.0", "@opentelemetry/test-utils": "^0.4.0", "@opentelemetry/tracing": "^0.4.0", "@types/ioredis": "^4.14.3", diff --git a/packages/opentelemetry-plugin-ioredis/src/utils.ts b/packages/opentelemetry-plugin-ioredis/src/utils.ts index 44f9f573ed..f27d9672ba 100644 --- a/packages/opentelemetry-plugin-ioredis/src/utils.ts +++ b/packages/opentelemetry-plugin-ioredis/src/utils.ts @@ -34,10 +34,8 @@ const endSpan = (span: Span, err: NodeJS.ErrnoException | null | undefined) => { export const traceConnection = (tracer: Tracer, original: Function) => { return function(this: ioredisTypes.Redis & IORedisPluginClientTypes) { - const parentSpan = tracer.getCurrentSpan(); const span = tracer.startSpan('connect', { kind: SpanKind.CLIENT, - parent: parentSpan, attributes: { [AttributeNames.COMPONENT]: IORedisPlugin.COMPONENT, [AttributeNames.DB_TYPE]: IORedisPlugin.DB_TYPE, @@ -67,12 +65,9 @@ export const traceSendCommand = (tracer: Tracer, original: Function) => { this: ioredisTypes.Redis & IORedisPluginClientTypes, cmd?: IORedisCommand ) { - const parentSpan = tracer.getCurrentSpan(); - if (arguments.length >= 1 && typeof cmd === 'object') { const span = tracer.startSpan(cmd.name, { kind: SpanKind.CLIENT, - parent: parentSpan, attributes: { [AttributeNames.COMPONENT]: IORedisPlugin.COMPONENT, [AttributeNames.DB_TYPE]: IORedisPlugin.DB_TYPE, diff --git a/packages/opentelemetry-plugin-ioredis/test/ioredis.test.ts b/packages/opentelemetry-plugin-ioredis/test/ioredis.test.ts index 5dca23af19..0b5f6b1930 100644 --- a/packages/opentelemetry-plugin-ioredis/test/ioredis.test.ts +++ b/packages/opentelemetry-plugin-ioredis/test/ioredis.test.ts @@ -14,17 +14,18 @@ * limitations under the License. */ -import * as assert from 'assert'; +import { CanonicalCode, context, SpanKind, Status } from '@opentelemetry/api'; +import { NoopLogger } from '@opentelemetry/core'; +import { NodeTracerProvider } from '@opentelemetry/node'; +import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; +import * as testUtils from '@opentelemetry/test-utils'; import { InMemorySpanExporter, SimpleSpanProcessor, } from '@opentelemetry/tracing'; -import { NodeTracerProvider } from '@opentelemetry/node'; -import { plugin, IORedisPlugin } from '../src'; +import * as assert from 'assert'; import * as ioredisTypes from 'ioredis'; -import { NoopLogger } from '@opentelemetry/core'; -import * as testUtils from '@opentelemetry/test-utils'; -import { SpanKind, Status, CanonicalCode } from '@opentelemetry/api'; +import { IORedisPlugin, plugin } from '../src'; import { AttributeNames } from '../src/enums'; const memoryExporter = new InMemorySpanExporter(); @@ -54,6 +55,16 @@ describe('ioredis', () => { const shouldTestLocal = process.env.RUN_REDIS_TESTS_LOCAL; const shouldTest = process.env.RUN_REDIS_TESTS || shouldTestLocal; + let scopeManager: AsyncHooksScopeManager; + beforeEach(() => { + scopeManager = new AsyncHooksScopeManager().enable(); + context.initGlobalContextManager(scopeManager); + }); + + afterEach(() => { + scopeManager.disable(); + }); + before(function() { // needs to be "function" to have MochaContext "this" scope if (!shouldTest) { diff --git a/packages/opentelemetry-plugin-mongodb/package.json b/packages/opentelemetry-plugin-mongodb/package.json index a88514d2bb..800f9824d5 100644 --- a/packages/opentelemetry-plugin-mongodb/package.json +++ b/packages/opentelemetry-plugin-mongodb/package.json @@ -42,6 +42,7 @@ }, "devDependencies": { "@opentelemetry/node": "^0.4.0", + "@opentelemetry/scope-async-hooks": "^0.4.0", "@opentelemetry/tracing": "^0.4.0", "@types/mocha": "^5.2.7", "@types/mongodb": "^3.2.3", diff --git a/packages/opentelemetry-plugin-mongodb/src/mongodb.ts b/packages/opentelemetry-plugin-mongodb/src/mongodb.ts index 93eed568df..01da629090 100644 --- a/packages/opentelemetry-plugin-mongodb/src/mongodb.ts +++ b/packages/opentelemetry-plugin-mongodb/src/mongodb.ts @@ -120,7 +120,6 @@ export class MongoDBPlugin extends BasePlugin { ? operationName : commandType; const span = plugin._tracer.startSpan(`mongodb.${type}`, { - parent: currentSpan, kind: SpanKind.CLIENT, }); plugin._populateAttributes( @@ -215,7 +214,6 @@ export class MongoDBPlugin extends BasePlugin { return original.apply(this, args); } const span = plugin._tracer.startSpan(`mongodb.query`, { - parent: currentSpan, kind: SpanKind.CLIENT, }); plugin._populateAttributes(span, this.ns, this.cmd, this.topology); diff --git a/packages/opentelemetry-plugin-mongodb/test/mongodb.test.ts b/packages/opentelemetry-plugin-mongodb/test/mongodb.test.ts index 8ad83efe58..effe65cf20 100644 --- a/packages/opentelemetry-plugin-mongodb/test/mongodb.test.ts +++ b/packages/opentelemetry-plugin-mongodb/test/mongodb.test.ts @@ -14,18 +14,19 @@ * limitations under the License. */ -import { NodeTracerProvider } from '@opentelemetry/node'; -import * as assert from 'assert'; -import * as mongodb from 'mongodb'; -import { plugin } from '../src'; -import { SpanKind, CanonicalCode } from '@opentelemetry/api'; +import { CanonicalCode, context, SpanKind } from '@opentelemetry/api'; import { NoopLogger } from '@opentelemetry/core'; -import { AttributeNames } from '../src/types'; +import { NodeTracerProvider } from '@opentelemetry/node'; +import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; import { InMemorySpanExporter, - SimpleSpanProcessor, ReadableSpan, + SimpleSpanProcessor, } from '@opentelemetry/tracing'; +import * as assert from 'assert'; +import * as mongodb from 'mongodb'; +import { plugin } from '../src'; +import { AttributeNames } from '../src/types'; interface MongoDBAccess { client: mongodb.MongoClient; @@ -102,6 +103,7 @@ describe('MongoDBPlugin', () => { const DB_NAME = process.env.MONGODB_DB || 'opentelemetry-tests'; const COLLECTION_NAME = 'test'; + let scopeManager: AsyncHooksScopeManager; let client: mongodb.MongoClient; let collection: mongodb.Collection; const logger = new NoopLogger(); @@ -140,10 +142,13 @@ describe('MongoDBPlugin', () => { collection.insertMany(insertData, (err, result) => { done(); }); + scopeManager = new AsyncHooksScopeManager().enable(); + context.initGlobalContextManager(scopeManager); }); afterEach(done => { collection.deleteOne({}, done); + scopeManager.disable(); }); after(() => { diff --git a/packages/opentelemetry-plugin-mysql/package.json b/packages/opentelemetry-plugin-mysql/package.json index 63d75df0b5..577b84b1d6 100644 --- a/packages/opentelemetry-plugin-mysql/package.json +++ b/packages/opentelemetry-plugin-mysql/package.json @@ -42,6 +42,7 @@ }, "devDependencies": { "@opentelemetry/node": "^0.4.0", + "@opentelemetry/scope-async-hooks": "^0.4.0", "@opentelemetry/test-utils": "^0.4.0", "@opentelemetry/tracing": "^0.4.0", "@types/mocha": "^5.2.7", diff --git a/packages/opentelemetry-plugin-mysql/src/mysql.ts b/packages/opentelemetry-plugin-mysql/src/mysql.ts index f5106d1032..35985fdfd1 100644 --- a/packages/opentelemetry-plugin-mysql/src/mysql.ts +++ b/packages/opentelemetry-plugin-mysql/src/mysql.ts @@ -209,7 +209,6 @@ export class MysqlPlugin extends BasePlugin { const spanName = getSpanName(query); const span = thisPlugin._tracer.startSpan(spanName, { - parent: thisPlugin._tracer.getCurrentSpan() || undefined, kind: SpanKind.CLIENT, attributes: { ...MysqlPlugin.COMMON_ATTRIBUTES, diff --git a/packages/opentelemetry-plugin-mysql/test/mysql.test.ts b/packages/opentelemetry-plugin-mysql/test/mysql.test.ts index 42683793f5..617bee06af 100644 --- a/packages/opentelemetry-plugin-mysql/test/mysql.test.ts +++ b/packages/opentelemetry-plugin-mysql/test/mysql.test.ts @@ -14,19 +14,20 @@ * limitations under the License. */ +import { CanonicalCode, context } from '@opentelemetry/api'; import { NoopLogger } from '@opentelemetry/core'; import { NodeTracerProvider } from '@opentelemetry/node'; +import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; +import * as testUtils from '@opentelemetry/test-utils'; import { InMemorySpanExporter, - SimpleSpanProcessor, ReadableSpan, + SimpleSpanProcessor, } from '@opentelemetry/tracing'; import * as assert from 'assert'; import * as mysql from 'mysql'; import { MysqlPlugin, plugin } from '../src'; -import * as testUtils from '@opentelemetry/test-utils'; import { AttributeNames } from '../src/enums'; -import { CanonicalCode } from '@opentelemetry/api'; const port = parseInt(process.env.MYSQL_PORT || '33306', 10); const database = process.env.MYSQL_DATABASE || 'test_db'; @@ -35,6 +36,7 @@ const user = process.env.MYSQL_USER || 'otel'; const password = process.env.MYSQL_PASSWORD || 'secret'; describe('mysql@2.x', () => { + let scopeManager: AsyncHooksScopeManager; let connection: mysql.Connection; let pool: mysql.Pool; let poolCluster: mysql.PoolCluster; @@ -71,6 +73,8 @@ describe('mysql@2.x', () => { }); beforeEach(function() { + scopeManager = new AsyncHooksScopeManager().enable(); + context.initGlobalContextManager(scopeManager); plugin.enable(mysql, provider, logger); connection = mysql.createConnection({ port, @@ -97,6 +101,7 @@ describe('mysql@2.x', () => { }); afterEach(done => { + scopeManager.disable(); memoryExporter.reset(); plugin.disable(); connection.end(() => { diff --git a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/package.json b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/package.json index ca17610db5..d03e4ceeb9 100644 --- a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/package.json +++ b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/package.json @@ -47,6 +47,7 @@ }, "devDependencies": { "@opentelemetry/plugin-pg": "^0.4.0", + "@opentelemetry/scope-async-hooks": "^0.4.0", "@opentelemetry/test-utils": "^0.4.0", "@types/mocha": "^5.2.7", "@types/node": "^12.6.9", diff --git a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/src/pg-pool.ts b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/src/pg-pool.ts index c6ef88ab5a..2c624b5423 100644 --- a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/src/pg-pool.ts +++ b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/src/pg-pool.ts @@ -67,7 +67,6 @@ export class PostgresPoolPlugin extends BasePlugin { `${PostgresPoolPlugin.COMPONENT}.connect`, { kind: SpanKind.CLIENT, - parent: plugin._tracer.getCurrentSpan() || undefined, attributes: { [AttributeNames.COMPONENT]: PostgresPoolPlugin.COMPONENT, // required [AttributeNames.DB_TYPE]: PostgresPoolPlugin.DB_TYPE, // required diff --git a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/test/pg-pool.test.ts b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/test/pg-pool.test.ts index 7da3cfd58f..fe6ed7951a 100644 --- a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/test/pg-pool.test.ts +++ b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg-pool/test/pg-pool.test.ts @@ -14,27 +14,29 @@ * limitations under the License. */ -import { NoopLogger } from '@opentelemetry/core'; -import { NodeTracerProvider } from '@opentelemetry/node'; -import { - InMemorySpanExporter, - SimpleSpanProcessor, -} from '@opentelemetry/tracing'; import { - SpanKind, Attributes, - TimedEvent, - Span, CanonicalCode, + context, + Span, + SpanKind, Status, + TimedEvent, } from '@opentelemetry/api'; +import { NoopLogger } from '@opentelemetry/core'; +import { NodeTracerProvider } from '@opentelemetry/node'; import { plugin as pgPlugin, PostgresPlugin } from '@opentelemetry/plugin-pg'; -import { plugin, PostgresPoolPlugin } from '../src'; -import { AttributeNames } from '../src/enums'; +import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; +import * as testUtils from '@opentelemetry/test-utils'; +import { + InMemorySpanExporter, + SimpleSpanProcessor, +} from '@opentelemetry/tracing'; import * as assert from 'assert'; import * as pg from 'pg'; import * as pgPool from 'pg-pool'; -import * as testUtils from '@opentelemetry/test-utils'; +import { plugin, PostgresPoolPlugin } from '../src'; +import { AttributeNames } from '../src/enums'; const memoryExporter = new InMemorySpanExporter(); @@ -92,6 +94,7 @@ const runCallbackTest = ( describe('pg-pool@2.x', () => { let pool: pgPool; + let scopeManager: AsyncHooksScopeManager; const provider = new NodeTracerProvider(); const logger = new NoopLogger(); const testPostgres = process.env.RUN_POSTGRES_TESTS; // For CI: assumes local postgres db is already available @@ -125,12 +128,15 @@ describe('pg-pool@2.x', () => { beforeEach(function() { plugin.enable(pgPool, provider, logger); pgPlugin.enable(pg, provider, logger); + scopeManager = new AsyncHooksScopeManager().enable(); + context.initGlobalContextManager(scopeManager); }); afterEach(() => { memoryExporter.reset(); plugin.disable(); pgPlugin.disable(); + scopeManager.disable(); }); it('should return a plugin', () => { diff --git a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/package.json b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/package.json index 56ad863f40..fa3b18a722 100644 --- a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/package.json +++ b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/package.json @@ -46,6 +46,7 @@ }, "devDependencies": { "@opentelemetry/node": "^0.4.0", + "@opentelemetry/scope-async-hooks": "^0.4.0", "@opentelemetry/test-utils": "^0.4.0", "@opentelemetry/tracing": "^0.4.0", "@types/mocha": "^5.2.7", diff --git a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/utils.ts b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/utils.ts index 373426e79a..9fa9c168dc 100644 --- a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/utils.ts +++ b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/utils.ts @@ -52,7 +52,6 @@ function pgStartSpan( const jdbcString = getJDBCString(client.connectionParameters); return tracer.startSpan(name, { kind: SpanKind.CLIENT, - parent: tracer.getCurrentSpan(), attributes: { [AttributeNames.COMPONENT]: PostgresPlugin.COMPONENT, // required [AttributeNames.DB_INSTANCE]: client.connectionParameters.database, // required diff --git a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/test/pg.test.ts b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/test/pg.test.ts index d4ff201de2..2d4c9dc589 100644 --- a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/test/pg.test.ts +++ b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/test/pg.test.ts @@ -14,25 +14,27 @@ * limitations under the License. */ +import { + Attributes, + CanonicalCode, + context, + Span, + SpanKind, + Status, + TimedEvent, +} from '@opentelemetry/api'; import { NoopLogger } from '@opentelemetry/core'; import { NodeTracerProvider } from '@opentelemetry/node'; +import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; +import * as testUtils from '@opentelemetry/test-utils'; import { InMemorySpanExporter, SimpleSpanProcessor, } from '@opentelemetry/tracing'; -import { - SpanKind, - Attributes, - TimedEvent, - Span, - CanonicalCode, - Status, -} from '@opentelemetry/api'; -import { plugin, PostgresPlugin } from '../src'; -import { AttributeNames } from '../src/enums'; import * as assert from 'assert'; import * as pg from 'pg'; -import * as testUtils from '@opentelemetry/test-utils'; +import { plugin, PostgresPlugin } from '../src'; +import { AttributeNames } from '../src/enums'; const memoryExporter = new InMemorySpanExporter(); @@ -81,6 +83,7 @@ const runCallbackTest = ( describe('pg@7.x', () => { let client: pg.Client; + let scopeManager: AsyncHooksScopeManager; const provider = new NodeTracerProvider(); const tracer = provider.getTracer('external'); const logger = new NoopLogger(); @@ -117,11 +120,14 @@ describe('pg@7.x', () => { beforeEach(function() { plugin.enable(pg, provider, logger); + scopeManager = new AsyncHooksScopeManager().enable(); + context.initGlobalContextManager(scopeManager); }); afterEach(() => { memoryExporter.reset(); plugin.disable(); + scopeManager.disable(); }); it('should return a plugin', () => { @@ -359,15 +365,18 @@ describe('pg@7.x', () => { it('should handle the same callback being given to multiple client.query()s', done => { let events = 0; + const parent = tracer.startSpan('parent'); - const queryHandler = (err: Error, res: pg.QueryResult) => { + const queryHandler = (err?: Error, res?: pg.QueryResult) => { const span = tracer.getCurrentSpan(); - assert.ok(span); - assert.strictEqual((span as any)['_ended'], false); + assert.deepStrictEqual(span!.context(), parent.context()); if (err) { throw err; } events += 1; + if (events === 7) { + done(); + } }; const config = { @@ -375,16 +384,17 @@ describe('pg@7.x', () => { callback: queryHandler, }; - client.query(config.text, config.callback); // 1 - client.query(config); // 2 - client.query(config.text, queryHandler); // 3 - client.query(config.text, queryHandler); // 4 - client.query(config.text); // Not using queryHandler - client.query(config); // 5 - client.query(config); // 6 - client.query(config.text, (err, res) => { - assert.strictEqual(events, 6); - done(); + tracer.withSpan(parent, () => { + client.query(config.text, config.callback); // 1 + client.query(config); // 2 + client.query(config.text, queryHandler); // 3 + client.query(config.text, queryHandler); // 4 + client + .query(config.text) + .then(result => queryHandler(undefined, result)) + .catch(err => queryHandler(err)); // 5 + client.query(config); // 6 + client.query(config); // 7 }); }); diff --git a/packages/opentelemetry-plugin-redis/package.json b/packages/opentelemetry-plugin-redis/package.json index 0b64a6a6fb..14cfe0cae4 100644 --- a/packages/opentelemetry-plugin-redis/package.json +++ b/packages/opentelemetry-plugin-redis/package.json @@ -44,6 +44,7 @@ }, "devDependencies": { "@opentelemetry/node": "^0.4.0", + "@opentelemetry/scope-async-hooks": "^0.4.0", "@opentelemetry/test-utils": "^0.4.0", "@opentelemetry/tracing": "^0.4.0", "@types/mocha": "^5.2.7", diff --git a/packages/opentelemetry-plugin-redis/src/utils.ts b/packages/opentelemetry-plugin-redis/src/utils.ts index 8cf65426b3..4a0d37b69c 100644 --- a/packages/opentelemetry-plugin-redis/src/utils.ts +++ b/packages/opentelemetry-plugin-redis/src/utils.ts @@ -77,7 +77,6 @@ export const getTracedInternalSendCommand = ( if (arguments.length === 1 && typeof cmd === 'object') { const span = tracer.startSpan(`${RedisPlugin.COMPONENT}-${cmd.command}`, { kind: SpanKind.CLIENT, - parent: tracer.getCurrentSpan(), attributes: { [AttributeNames.COMPONENT]: RedisPlugin.COMPONENT, [AttributeNames.DB_STATEMENT]: cmd.command, diff --git a/packages/opentelemetry-plugin-redis/test/redis.test.ts b/packages/opentelemetry-plugin-redis/test/redis.test.ts index e991c733a0..a5cbe96346 100644 --- a/packages/opentelemetry-plugin-redis/test/redis.test.ts +++ b/packages/opentelemetry-plugin-redis/test/redis.test.ts @@ -14,17 +14,18 @@ * limitations under the License. */ -import * as assert from 'assert'; +import { CanonicalCode, context, SpanKind, Status } from '@opentelemetry/api'; +import { NoopLogger } from '@opentelemetry/core'; +import { NodeTracerProvider } from '@opentelemetry/node'; +import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; +import * as testUtils from '@opentelemetry/test-utils'; import { InMemorySpanExporter, SimpleSpanProcessor, } from '@opentelemetry/tracing'; -import { NodeTracerProvider } from '@opentelemetry/node'; -import { plugin, RedisPlugin } from '../src'; +import * as assert from 'assert'; import * as redisTypes from 'redis'; -import { NoopLogger } from '@opentelemetry/core'; -import * as testUtils from '@opentelemetry/test-utils'; -import { SpanKind, Status, CanonicalCode } from '@opentelemetry/api'; +import { plugin, RedisPlugin } from '../src'; import { AttributeNames } from '../src/enums'; const memoryExporter = new InMemorySpanExporter(); @@ -54,6 +55,16 @@ describe('redis@2.x', () => { const shouldTestLocal = process.env.RUN_REDIS_TESTS_LOCAL; const shouldTest = process.env.RUN_REDIS_TESTS || shouldTestLocal; + let scopeManager: AsyncHooksScopeManager; + beforeEach(() => { + scopeManager = new AsyncHooksScopeManager().enable(); + context.initGlobalContextManager(scopeManager); + }); + + afterEach(() => { + scopeManager.disable(); + }); + before(function() { // needs to be "function" to have MochaContext "this" scope if (!shouldTest) { diff --git a/packages/opentelemetry-plugin-user-interaction/src/userInteraction.ts b/packages/opentelemetry-plugin-user-interaction/src/userInteraction.ts index 3ce4028273..5e6037ad33 100644 --- a/packages/opentelemetry-plugin-user-interaction/src/userInteraction.ts +++ b/packages/opentelemetry-plugin-user-interaction/src/userInteraction.ts @@ -96,7 +96,6 @@ export class UserInteractionPlugin extends BasePlugin { [AttributeNames.HTTP_URL]: window.location.href, [AttributeNames.HTTP_USER_AGENT]: navigator.userAgent, }, - parent: this._tracer.getCurrentSpan(), }); this._spansData.set(span, { diff --git a/packages/opentelemetry-plugin-user-interaction/test/userInteraction.nozone.test.ts b/packages/opentelemetry-plugin-user-interaction/test/userInteraction.nozone.test.ts index c3091d3cfc..fb2133a4a9 100644 --- a/packages/opentelemetry-plugin-user-interaction/test/userInteraction.nozone.test.ts +++ b/packages/opentelemetry-plugin-user-interaction/test/userInteraction.nozone.test.ts @@ -18,14 +18,15 @@ // code outside zone.js. This needs to be done before all const originalSetTimeout = window.setTimeout; -import * as assert from 'assert'; -import * as sinon from 'sinon'; +import { context } from '@opentelemetry/api'; import { isWrapped, LogLevel } from '@opentelemetry/core'; +import { XMLHttpRequestPlugin } from '@opentelemetry/plugin-xml-http-request'; +import { ZoneScopeManager } from '@opentelemetry/scope-zone-peer-dep'; import * as tracing from '@opentelemetry/tracing'; import { WebTracerProvider } from '@opentelemetry/web'; -import { XMLHttpRequestPlugin } from '@opentelemetry/plugin-xml-http-request'; +import * as assert from 'assert'; +import * as sinon from 'sinon'; import { UserInteractionPlugin } from '../src'; - import { assertClickSpan, DummySpanExporter, @@ -38,6 +39,7 @@ const FILE_URL = describe('UserInteractionPlugin', () => { describe('when zone.js is NOT available', () => { + let scopeManager: ZoneScopeManager; let userInteractionPlugin: UserInteractionPlugin; let sandbox: sinon.SinonSandbox; let webTracerProvider: WebTracerProvider; @@ -45,6 +47,8 @@ describe('UserInteractionPlugin', () => { let exportSpy: sinon.SinonSpy; let requests: sinon.SinonFakeXMLHttpRequest[] = []; beforeEach(() => { + scopeManager = new ZoneScopeManager().enable(); + context.initGlobalContextManager(scopeManager); sandbox = sinon.createSandbox(); const fakeXhr = sandbox.useFakeXMLHttpRequest(); fakeXhr.onCreate = function(xhr: sinon.SinonFakeXMLHttpRequest) { @@ -85,6 +89,7 @@ describe('UserInteractionPlugin', () => { requests = []; sandbox.restore(); exportSpy.restore(); + scopeManager.disable(); }); it('should handle task without async operation', () => { diff --git a/packages/opentelemetry-plugin-user-interaction/test/userInteraction.test.ts b/packages/opentelemetry-plugin-user-interaction/test/userInteraction.test.ts index 85507feff0..c38f6fb2cd 100644 --- a/packages/opentelemetry-plugin-user-interaction/test/userInteraction.test.ts +++ b/packages/opentelemetry-plugin-user-interaction/test/userInteraction.test.ts @@ -18,15 +18,15 @@ // code outside zone.js. This needs to be done before all const originalSetTimeout = window.setTimeout; -import 'zone.js'; - -import * as assert from 'assert'; -import * as sinon from 'sinon'; +import { context } from '@opentelemetry/api'; import { isWrapped, LogLevel } from '@opentelemetry/core'; +import { XMLHttpRequestPlugin } from '@opentelemetry/plugin-xml-http-request'; +import { ZoneScopeManager } from '@opentelemetry/scope-zone-peer-dep'; import * as tracing from '@opentelemetry/tracing'; import { WebTracerProvider } from '@opentelemetry/web'; -import { ZoneScopeManager } from '@opentelemetry/scope-zone-peer-dep'; -import { XMLHttpRequestPlugin } from '@opentelemetry/plugin-xml-http-request'; +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import 'zone.js'; import { UserInteractionPlugin } from '../src'; import { WindowWithZone } from '../src/types'; import { @@ -42,6 +42,7 @@ const FILE_URL = describe('UserInteractionPlugin', () => { describe('when zone.js is available', () => { + let scopeManager: ZoneScopeManager; let userInteractionPlugin: UserInteractionPlugin; let sandbox: sinon.SinonSandbox; let webTracerProvider: WebTracerProvider; @@ -49,6 +50,8 @@ describe('UserInteractionPlugin', () => { let exportSpy: sinon.SinonSpy; let requests: sinon.SinonFakeXMLHttpRequest[] = []; beforeEach(() => { + scopeManager = new ZoneScopeManager().enable(); + context.initGlobalContextManager(scopeManager); sandbox = sinon.createSandbox(); history.pushState({ test: 'testing' }, '', `${location.pathname}`); const fakeXhr = sandbox.useFakeXMLHttpRequest(); @@ -68,7 +71,6 @@ describe('UserInteractionPlugin', () => { userInteractionPlugin = new UserInteractionPlugin(); webTracerProvider = new WebTracerProvider({ logLevel: LogLevel.ERROR, - scopeManager: new ZoneScopeManager(), plugins: [userInteractionPlugin, new XMLHttpRequestPlugin()], }); dummySpanExporter = new DummySpanExporter(); @@ -85,6 +87,7 @@ describe('UserInteractionPlugin', () => { requests = []; sandbox.restore(); exportSpy.restore(); + scopeManager.disable(); }); it('should handle task without async operation', () => { diff --git a/packages/opentelemetry-plugin-xml-http-request/src/xhr.ts b/packages/opentelemetry-plugin-xml-http-request/src/xhr.ts index cef6eff144..4f31b06958 100644 --- a/packages/opentelemetry-plugin-xml-http-request/src/xhr.ts +++ b/packages/opentelemetry-plugin-xml-http-request/src/xhr.ts @@ -14,14 +14,13 @@ * limitations under the License. */ -import * as types from '@opentelemetry/api'; +import * as api from '@opentelemetry/api'; import { BasePlugin, hrTime, isUrlIgnored, isWrapped, otperformance, - setActiveSpan, urlMatches, } from '@opentelemetry/core'; import { @@ -50,7 +49,7 @@ const OBSERVER_WAIT_TIME_MS = 300; /** * XMLHttpRequest config */ -export interface XMLHttpRequestPluginConfig extends types.PluginConfig { +export interface XMLHttpRequestPluginConfig extends api.PluginConfig { // the number of timing resources is limited, after the limit // (chrome 250, safari 150) the information is not collected anymore // the only way to prevent that is to regularly clean the resources @@ -83,17 +82,12 @@ export class XMLHttpRequestPlugin extends BasePlugin { * @param span * @private */ - private _addHeaders(xhr: XMLHttpRequest, span: types.Span, spanUrl: string) { + private _addHeaders(xhr: XMLHttpRequest, spanUrl: string) { if (!this._shouldPropagateTraceHeaders(spanUrl)) { return; } const headers: { [key: string]: unknown } = {}; - this._tracer - .getHttpTextFormat() - // Using context direclty like this is temporary. In a future PR, context - // will be managed by the scope manager (which may be renamed to context manager?) - .inject(setActiveSpan(types.Context.TODO, span), headers); - + api.propagation.inject(headers); Object.keys(headers).forEach(key => { xhr.setRequestHeader(key, String(headers[key])); }); @@ -134,15 +128,16 @@ export class XMLHttpRequestPlugin extends BasePlugin { * @private */ private _addChildSpan( - span: types.Span, + span: api.Span, corsPreFlightRequest: PerformanceResourceTiming ): void { - const childSpan = this._tracer.startSpan('CORS Preflight', { - startTime: corsPreFlightRequest[PTN.FETCH_START], - parent: span, - }) as types.Span; - this._addSpanNetworkEvents(childSpan, corsPreFlightRequest); - childSpan.end(corsPreFlightRequest[PTN.RESPONSE_END]); + this._tracer.withSpan(span, () => { + const childSpan = this._tracer.startSpan('CORS Preflight', { + startTime: corsPreFlightRequest[PTN.FETCH_START], + }); + this._addSpanNetworkEvents(childSpan, corsPreFlightRequest); + childSpan.end(corsPreFlightRequest[PTN.RESPONSE_END]); + }); } /** @@ -152,7 +147,7 @@ export class XMLHttpRequestPlugin extends BasePlugin { * @param spanUrl * @private */ - _addFinalSpanAttributes(span: types.Span, xhrMem: XhrMem, spanUrl?: string) { + _addFinalSpanAttributes(span: api.Span, xhrMem: XhrMem, spanUrl?: string) { if (typeof spanUrl === 'string') { const parsedUrl = parseUrl(spanUrl); @@ -177,7 +172,7 @@ export class XMLHttpRequestPlugin extends BasePlugin { * @private */ private _addSpanNetworkEvents( - span: types.Span, + span: api.Span, resource: PerformanceResourceTiming ) { addSpanNetworkEvent(span, PTN.FETCH_START, resource); @@ -244,10 +239,10 @@ export class XMLHttpRequestPlugin extends BasePlugin { */ private _findResourceAndAddNetworkEvents( xhrMem: XhrMem, - span: types.Span, + span: api.Span, spanUrl?: string, - startTime?: types.HrTime, - endTime?: types.HrTime + startTime?: api.HrTime, + endTime?: api.HrTime ): void { if (!spanUrl || !startTime || !endTime || !xhrMem.createdResources) { return; @@ -314,14 +309,13 @@ export class XMLHttpRequestPlugin extends BasePlugin { xhr: XMLHttpRequest, url: string, method: string - ): types.Span | undefined { + ): api.Span | undefined { if (isUrlIgnored(url, this._config.ignoreUrls)) { this._logger.debug('ignoring span as url matches ignored url'); return; } const currentSpan = this._tracer.startSpan(url, { - parent: this._tracer.getCurrentSpan(), attributes: { [AttributeNames.COMPONENT]: this.component, [AttributeNames.HTTP_METHOD]: method, @@ -387,7 +381,7 @@ export class XMLHttpRequestPlugin extends BasePlugin { function endSpanTimeout( eventName: string, xhrMem: XhrMem, - endTime: types.HrTime + endTime: api.HrTime ) { const callbackToRemoveEvents = xhrMem.callbackToRemoveEvents; @@ -472,23 +466,25 @@ export class XMLHttpRequestPlugin extends BasePlugin { const spanUrl = xhrMem.spanUrl; if (currentSpan && spanUrl) { - plugin._tasksCount++; - xhrMem.sendStartTime = hrTime(); - currentSpan.addEvent(EventNames.METHOD_SEND); - - this.addEventListener('abort', onAbort); - this.addEventListener('error', onError); - this.addEventListener('load', onLoad); - this.addEventListener('timeout', onTimeout); - - xhrMem.callbackToRemoveEvents = () => { - unregister(this); - if (xhrMem.createdResources) { - xhrMem.createdResources.observer.disconnect(); - } - }; - plugin._addHeaders(this, currentSpan, spanUrl); - plugin._addResourceObserver(this, spanUrl); + plugin._tracer.withSpan(currentSpan, () => { + plugin._tasksCount++; + xhrMem.sendStartTime = hrTime(); + currentSpan.addEvent(EventNames.METHOD_SEND); + + this.addEventListener('abort', onAbort); + this.addEventListener('error', onError); + this.addEventListener('load', onLoad); + this.addEventListener('timeout', onTimeout); + + xhrMem.callbackToRemoveEvents = () => { + unregister(this); + if (xhrMem.createdResources) { + xhrMem.createdResources.observer.disconnect(); + } + }; + plugin._addHeaders(this, spanUrl); + plugin._addResourceObserver(this, spanUrl); + }); } return original.apply(this, args); }; diff --git a/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts b/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts index 97a28908c0..9ee2a96d74 100644 --- a/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts +++ b/packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as assert from 'assert'; -import * as sinon from 'sinon'; - +import * as types from '@opentelemetry/api'; import { B3Format, LogLevel, @@ -26,11 +24,12 @@ import { } from '@opentelemetry/core'; import { ZoneScopeManager } from '@opentelemetry/scope-zone'; import * as tracing from '@opentelemetry/tracing'; -import * as types from '@opentelemetry/api'; import { PerformanceTimingNames as PTN, WebTracerProvider, } from '@opentelemetry/web'; +import * as assert from 'assert'; +import * as sinon from 'sinon'; import { AttributeNames } from '../src/enums/AttributeNames'; import { EventNames } from '../src/enums/EventNames'; import { XMLHttpRequestPlugin } from '../src/xhr'; @@ -98,6 +97,20 @@ describe('xhr', () => { let requests: any[] = []; let prepareData: any; let clearData: any; + let scopeManager: ZoneScopeManager; + + beforeEach(() => { + scopeManager = new ZoneScopeManager().enable(); + types.context.initGlobalContextManager(scopeManager); + }); + + afterEach(() => { + scopeManager.disable(); + }); + + before(() => { + types.propagation.initGlobalPropagator(new B3Format()); + }); describe('when request is successful', () => { let webTracerWithZone: types.Tracer; @@ -142,8 +155,6 @@ describe('xhr', () => { webTracerProviderWithZone = new WebTracerProvider({ logLevel: LogLevel.ERROR, - httpTextFormat: new B3Format(), - scopeManager: new ZoneScopeManager(), plugins: [ new XMLHttpRequestPlugin({ propagateTraceHeaderCorsUrls: propagateTraceHeaderCorsUrls, @@ -158,7 +169,6 @@ describe('xhr', () => { ); rootSpan = webTracerWithZone.startSpan('root'); - webTracerWithZone.withSpan(rootSpan, () => { getData(fileUrl, () => { fakeNow = 100; @@ -186,14 +196,6 @@ describe('xhr', () => { clearData(); }); - it('current span should be root span', () => { - assert.strictEqual( - webTracerWithZone.getCurrentSpan(), - rootSpan, - 'root span is wrong' - ); - }); - it('should create a span with correct root span', () => { const span: tracing.ReadableSpan = exportSpy.args[0][0][0]; assert.strictEqual( @@ -442,7 +444,6 @@ describe('xhr', () => { webTracerWithZoneProvider = new WebTracerProvider({ logLevel: LogLevel.ERROR, - scopeManager: new ZoneScopeManager(), plugins: [new XMLHttpRequestPlugin()], }); dummySpanExporter = new DummySpanExporter(); diff --git a/packages/opentelemetry-shim-opentracing/src/shim.ts b/packages/opentelemetry-shim-opentracing/src/shim.ts index 07fb689ccf..a919393486 100644 --- a/packages/opentelemetry-shim-opentracing/src/shim.ts +++ b/packages/opentelemetry-shim-opentracing/src/shim.ts @@ -14,18 +14,17 @@ * limitations under the License. */ -import * as types from '@opentelemetry/api'; +import * as api from '@opentelemetry/api'; import { getExtractedSpanContext, NoopLogger, setExtractedSpanContext, + setActiveSpan, } from '@opentelemetry/core'; import * as opentracing from 'opentracing'; -function translateReferences( - references: opentracing.Reference[] -): types.Link[] { - const links: types.Link[] = []; +function translateReferences(references: opentracing.Reference[]): api.Link[] { + const links: api.Link[] = []; for (const reference of references) { const context = reference.referencedContext(); if (context instanceof SpanContextShim) { @@ -40,8 +39,8 @@ function translateReferences( function translateSpanOptions( options: opentracing.SpanOptions -): types.SpanOptions { - const opts: types.SpanOptions = { +): api.SpanOptions { + const opts: api.SpanOptions = { startTime: options.startTime, }; @@ -49,14 +48,21 @@ function translateSpanOptions( opts.links = translateReferences(options.references); } + return opts; +} + +function getContextWithParent(options: opentracing.SpanOptions) { if (options.childOf) { if (options.childOf instanceof SpanShim) { - opts.parent = (options.childOf as SpanShim).getSpan(); + return setActiveSpan(api.context.active(), options.childOf.getSpan()); } else if (options.childOf instanceof SpanContextShim) { - opts.parent = (options.childOf as SpanContextShim).getSpanContext(); + return setExtractedSpanContext( + api.context.active(), + options.childOf.getSpanContext() + ); } } - return opts; + return api.context.active(); } /** @@ -64,9 +70,9 @@ function translateSpanOptions( * OpenTracing span context API. */ export class SpanContextShim extends opentracing.SpanContext { - private readonly _spanContext: types.SpanContext; + private readonly _spanContext: api.SpanContext; - constructor(spanContext: types.SpanContext) { + constructor(spanContext: api.SpanContext) { super(); this._spanContext = spanContext; } @@ -74,7 +80,7 @@ export class SpanContextShim extends opentracing.SpanContext { /** * Returns the underlying {@link types.SpanContext} */ - getSpanContext(): types.SpanContext { + getSpanContext(): api.SpanContext { return this._spanContext; } @@ -98,10 +104,10 @@ export class SpanContextShim extends opentracing.SpanContext { * OpenTracing tracer API. */ export class TracerShim extends opentracing.Tracer { - private readonly _tracer: types.Tracer; - private readonly _logger: types.Logger; + private readonly _tracer: api.Tracer; + private readonly _logger: api.Logger; - constructor(tracer: types.Tracer, logger?: types.Logger) { + constructor(tracer: api.Tracer, logger?: api.Logger) { super(); this._tracer = tracer; @@ -112,7 +118,11 @@ export class TracerShim extends opentracing.Tracer { name: string, options: opentracing.SpanOptions = {} ): opentracing.Span { - const span = this._tracer.startSpan(name, translateSpanOptions(options)); + const span = this._tracer.startSpan( + name, + translateSpanOptions(options), + getContextWithParent(options) + ); if (options.tags) { span.setAttributes(options.tags); @@ -124,23 +134,21 @@ export class TracerShim extends opentracing.Tracer { _inject( spanContext: opentracing.SpanContext, format: string, - carrier: types.Carrier + carrier: api.Carrier ): void { - const opentelemSpanContext: types.SpanContext = (spanContext as SpanContextShim).getSpanContext(); + const opentelemSpanContext: api.SpanContext = (spanContext as SpanContextShim).getSpanContext(); if (!carrier || typeof carrier !== 'object') return; switch (format) { // tslint:disable-next-line:no-switch-case-fall-through case opentracing.FORMAT_HTTP_HEADERS: case opentracing.FORMAT_TEXT_MAP: - this._tracer - .getHttpTextFormat() - .inject( - setExtractedSpanContext( - types.Context.ROOT_CONTEXT, - opentelemSpanContext - ), - carrier - ); + api.propagation.inject( + carrier, + setExtractedSpanContext( + api.Context.ROOT_CONTEXT, + opentelemSpanContext + ) + ); return; case opentracing.FORMAT_BINARY: this._logger.warn( @@ -154,16 +162,14 @@ export class TracerShim extends opentracing.Tracer { _extract( format: string, - carrier: types.Carrier + carrier: api.Carrier ): opentracing.SpanContext | null { switch (format) { // tslint:disable-next-line:no-switch-case-fall-through case opentracing.FORMAT_HTTP_HEADERS: case opentracing.FORMAT_TEXT_MAP: const context = getExtractedSpanContext( - this._tracer - .getHttpTextFormat() - .extract(types.Context.ROOT_CONTEXT, carrier) + api.propagation.extract(carrier) ); if (!context) { return null; @@ -189,11 +195,11 @@ export class TracerShim extends opentracing.Tracer { export class SpanShim extends opentracing.Span { // _span is the original OpenTelemetry span that we are wrapping with // an opentracing interface. - private readonly _span: types.Span; + private readonly _span: api.Span; private readonly _contextShim: SpanContextShim; private readonly _tracerShim: TracerShim; - constructor(tracerShim: TracerShim, span: types.Span) { + constructor(tracerShim: TracerShim, span: api.Span) { super(); this._span = span; this._contextShim = new SpanContextShim(span.context()); @@ -242,7 +248,7 @@ export class SpanShim extends opentracing.Span { * @param payload an arbitrary object to be attached to the event. */ logEvent(eventName: string, payload?: unknown): void { - let attrs: types.Attributes = {}; + let attrs: api.Attributes = {}; if (payload) { attrs = { payload }; } @@ -279,7 +285,7 @@ export class SpanShim extends opentracing.Span { key === opentracing.Tags.ERROR && (value === true || value === 'true') ) { - this._span.setStatus({ code: types.CanonicalCode.UNKNOWN }); + this._span.setStatus({ code: api.CanonicalCode.UNKNOWN }); return this; } @@ -301,7 +307,7 @@ export class SpanShim extends opentracing.Span { * Returns the underlying {@link types.Span} that the shim * is wrapping. */ - getSpan(): types.Span { + getSpan(): api.Span { return this._span; } } diff --git a/packages/opentelemetry-shim-opentracing/test/Shim.test.ts b/packages/opentelemetry-shim-opentracing/test/Shim.test.ts index fbecde28f4..ab7237c80e 100644 --- a/packages/opentelemetry-shim-opentracing/test/Shim.test.ts +++ b/packages/opentelemetry-shim-opentracing/test/Shim.test.ts @@ -18,7 +18,12 @@ import * as assert from 'assert'; import * as opentracing from 'opentracing'; import { BasicTracerProvider, Span } from '@opentelemetry/tracing'; import { TracerShim, SpanShim, SpanContextShim } from '../src/shim'; -import { INVALID_SPAN_CONTEXT, timeInputToHrTime } from '@opentelemetry/core'; +import { + INVALID_SPAN_CONTEXT, + timeInputToHrTime, + HttpTraceContext, +} from '@opentelemetry/core'; +import { propagation } from '@opentelemetry/api'; import { performance } from 'perf_hooks'; describe('OpenTracing Shim', () => { @@ -27,6 +32,7 @@ describe('OpenTracing Shim', () => { provider.getTracer('default') ); opentracing.initGlobalTracer(shimTracer); + propagation.initGlobalPropagator(new HttpTraceContext()); describe('TracerShim', () => { let span: opentracing.Span; diff --git a/packages/opentelemetry-tracing/src/Tracer.ts b/packages/opentelemetry-tracing/src/Tracer.ts index 09b9011712..088c4f16cb 100644 --- a/packages/opentelemetry-tracing/src/Tracer.ts +++ b/packages/opentelemetry-tracing/src/Tracer.ts @@ -14,17 +14,17 @@ * limitations under the License. */ -import * as types from '@opentelemetry/api'; +import * as api from '@opentelemetry/api'; import { ConsoleLogger, getActiveSpan, + getParentSpanContext, isValid, NoRecordingSpan, randomSpanId, randomTraceId, setActiveSpan, } from '@opentelemetry/core'; -import { ScopeManager } from '@opentelemetry/scope-base'; import { BasicTracerProvider } from './BasicTracerProvider'; import { DEFAULT_CONFIG } from './config'; import { Span } from './Span'; @@ -34,13 +34,11 @@ import { mergeConfig } from './utility'; /** * This class represents a basic tracer. */ -export class Tracer implements types.Tracer { - private readonly _defaultAttributes: types.Attributes; - private readonly _httpTextFormat: types.HttpTextFormat; - private readonly _sampler: types.Sampler; - private readonly _scopeManager: ScopeManager; +export class Tracer implements api.Tracer { + private readonly _defaultAttributes: api.Attributes; + private readonly _sampler: api.Sampler; private readonly _traceParams: TraceParams; - readonly logger: types.Logger; + readonly logger: api.Logger; /** * Constructs a new Tracer instance. @@ -51,9 +49,7 @@ export class Tracer implements types.Tracer { ) { const localConfig = mergeConfig(config); this._defaultAttributes = localConfig.defaultAttributes; - this._httpTextFormat = localConfig.httpTextFormat; this._sampler = localConfig.sampler; - this._scopeManager = localConfig.scopeManager; this._traceParams = localConfig.traceParams; this.logger = config.logger || new ConsoleLogger(config.logLevel); } @@ -62,8 +58,14 @@ export class Tracer implements types.Tracer { * Starts a new Span or returns the default NoopSpan based on the sampling * decision. */ - startSpan(name: string, options: types.SpanOptions = {}): types.Span { - const parentContext = this._getParentSpanContext(options.parent); + startSpan( + name: string, + options: api.SpanOptions = {}, + context = api.context.active() + ): api.Span { + const parentContext = options.parent + ? getContext(options.parent) + : getParentSpanContext(context); // make sampling decision const samplingDecision = this._sampler.shouldSample(parentContext); const spanId = randomSpanId(); @@ -78,8 +80,8 @@ export class Tracer implements types.Tracer { traceState = parentContext.traceState; } const traceFlags = samplingDecision - ? types.TraceFlags.SAMPLED - : types.TraceFlags.UNSAMPLED; + ? api.TraceFlags.SAMPLED + : api.TraceFlags.UNSAMPLED; const spanContext = { traceId, spanId, traceFlags, traceState }; const recordEvents = options.isRecording || false; if (!recordEvents && !samplingDecision) { @@ -91,7 +93,7 @@ export class Tracer implements types.Tracer { this, name, spanContext, - options.kind || types.SpanKind.INTERNAL, + options.kind || api.SpanKind.INTERNAL, parentContext ? parentContext.spanId : undefined, options.links || [], options.startTime @@ -108,44 +110,33 @@ export class Tracer implements types.Tracer { * * If there is no Span associated with the current context, undefined is returned. */ - getCurrentSpan(): types.Span | undefined { + getCurrentSpan(): api.Span | undefined { + const ctx = api.context.active(); // Get the current Span from the context or null if none found. - return getActiveSpan(this._scopeManager.active()); + return getActiveSpan(ctx); } /** * Enters the scope of code where the given Span is in the current context. */ withSpan ReturnType>( - span: types.Span, + span: api.Span, fn: T ): ReturnType { // Set given span to context. - return this._scopeManager.with( - setActiveSpan(this._scopeManager.active(), span), - fn - ); + return api.context.with(setActiveSpan(api.context.active(), span), fn); } /** * Bind a span (or the current one) to the target's scope */ - bind(target: T, span?: types.Span): T { - return this._scopeManager.bind( + bind(target: T, span?: api.Span): T { + return api.context.bind( target, - span - ? setActiveSpan(this._scopeManager.active(), span) - : this._scopeManager.active() + span ? setActiveSpan(api.context.active(), span) : api.context.active() ); } - /** - * Returns the HTTP text format interface which can inject/extract Spans. - */ - getHttpTextFormat(): types.HttpTextFormat { - return this._httpTextFormat; - } - /** Returns the active {@link TraceParams}. */ getActiveTraceParams(): TraceParams { return this._traceParams; @@ -154,20 +145,12 @@ export class Tracer implements types.Tracer { getActiveSpanProcessor() { return this._tracerProvider.getActiveSpanProcessor(); } +} - private _getParentSpanContext( - parent?: types.Span | types.SpanContext | null - ): types.SpanContext | undefined { - if (!parent) return undefined; - - // parent is a SpanContext - if ((parent as types.SpanContext).traceId) { - return parent as types.SpanContext; - } +function getContext(span: api.Span | api.SpanContext) { + return isSpan(span) ? span.context() : span; +} - if (typeof (parent as types.Span).context === 'function') { - return (parent as Span).context(); - } - return undefined; - } +function isSpan(span: api.Span | api.SpanContext): span is api.Span { + return typeof (span as api.Span).context === 'function'; } diff --git a/packages/opentelemetry-tracing/src/types.ts b/packages/opentelemetry-tracing/src/types.ts index 7ab7c3b1bc..3fdd9240f8 100644 --- a/packages/opentelemetry-tracing/src/types.ts +++ b/packages/opentelemetry-tracing/src/types.ts @@ -14,13 +14,7 @@ * limitations under the License. */ -import { ScopeManager } from '@opentelemetry/scope-base'; -import { - Attributes, - HttpTextFormat, - Logger, - Sampler, -} from '@opentelemetry/api'; +import { Attributes, Logger, Sampler } from '@opentelemetry/api'; import { LogLevel } from '@opentelemetry/core'; /** @@ -33,11 +27,6 @@ export interface TracerConfig { */ defaultAttributes?: Attributes; - /** - * HTTP text formatter which can inject/extract Spans. - */ - httpTextFormat?: HttpTextFormat; - /** * User provided logger. */ @@ -51,11 +40,6 @@ export interface TracerConfig { */ sampler?: Sampler; - /** - * Scope manager keeps context across in-process operations. - */ - scopeManager?: ScopeManager; - /** Trace Parameters */ traceParams?: TraceParams; } diff --git a/packages/opentelemetry-tracing/test/BasicTracerRegistry.test.ts b/packages/opentelemetry-tracing/test/BasicTracerRegistry.test.ts index 19665036e9..1ce6c12c63 100644 --- a/packages/opentelemetry-tracing/test/BasicTracerRegistry.test.ts +++ b/packages/opentelemetry-tracing/test/BasicTracerRegistry.test.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { Context, TraceFlags } from '@opentelemetry/api'; +import { Context, context, SpanContext, TraceFlags } from '@opentelemetry/api'; import { ALWAYS_SAMPLER, - HttpTraceContext, NEVER_SAMPLER, NoopLogger, NoRecordingSpan, setActiveSpan, + setExtractedSpanContext, TraceState, } from '@opentelemetry/core'; import { NoopScopeManager, ScopeManager } from '@opentelemetry/scope-base'; @@ -29,40 +29,32 @@ import * as assert from 'assert'; import { BasicTracerProvider, Span } from '../src'; describe('BasicTracerProvider', () => { + beforeEach(() => { + context.initGlobalContextManager(new NoopScopeManager()); + }); + describe('constructor', () => { it('should construct an instance without any options', () => { const provider = new BasicTracerProvider(); assert.ok(provider instanceof BasicTracerProvider); }); - it('should construct an instance with http text format', () => { - const provider = new BasicTracerProvider({ - httpTextFormat: new HttpTraceContext(), - scopeManager: new NoopScopeManager(), - }); - assert.ok(provider instanceof BasicTracerProvider); - }); - it('should construct an instance with logger', () => { const provider = new BasicTracerProvider({ logger: new NoopLogger(), - scopeManager: new NoopScopeManager(), }); assert.ok(provider instanceof BasicTracerProvider); }); it('should construct an instance with sampler', () => { const provider = new BasicTracerProvider({ - scopeManager: new NoopScopeManager(), sampler: ALWAYS_SAMPLER, }); assert.ok(provider instanceof BasicTracerProvider); }); it('should construct an instance with default trace params', () => { - const tracer = new BasicTracerProvider({ - scopeManager: new NoopScopeManager(), - }).getTracer('default'); + const tracer = new BasicTracerProvider({}).getTracer('default'); assert.deepStrictEqual(tracer.getActiveTraceParams(), { numberOfAttributesPerSpan: 32, numberOfEventsPerSpan: 128, @@ -72,7 +64,6 @@ describe('BasicTracerProvider', () => { it('should construct an instance with customized numberOfAttributesPerSpan trace params', () => { const tracer = new BasicTracerProvider({ - scopeManager: new NoopScopeManager(), traceParams: { numberOfAttributesPerSpan: 100, }, @@ -86,7 +77,6 @@ describe('BasicTracerProvider', () => { it('should construct an instance with customized numberOfEventsPerSpan trace params', () => { const tracer = new BasicTracerProvider({ - scopeManager: new NoopScopeManager(), traceParams: { numberOfEventsPerSpan: 300, }, @@ -100,7 +90,6 @@ describe('BasicTracerProvider', () => { it('should construct an instance with customized numberOfLinksPerSpan trace params', () => { const tracer = new BasicTracerProvider({ - scopeManager: new NoopScopeManager(), traceParams: { numberOfLinksPerSpan: 10, }, @@ -176,13 +165,16 @@ describe('BasicTracerProvider', () => { it('should start a span with name and parent spancontext', () => { const tracer = new BasicTracerProvider().getTracer('default'); const state = new TraceState('a=1,b=2'); - const span = tracer.startSpan('my-span', { - parent: { + + const span = tracer.startSpan( + 'my-span', + {}, + setExtractedSpanContext(Context.ROOT_CONTEXT, { traceId: 'd4cda95b652f4a1592b449d5929fda1b', spanId: '6e0c63257de34c92', traceState: state, - }, - }); + }) + ); assert.ok(span instanceof Span); const context = span.context(); assert.strictEqual(context.traceId, 'd4cda95b652f4a1592b449d5929fda1b'); @@ -194,9 +186,11 @@ describe('BasicTracerProvider', () => { it('should start a span with name and parent span', () => { const tracer = new BasicTracerProvider().getTracer('default'); const span = tracer.startSpan('my-span'); - const childSpan = tracer.startSpan('child-span', { - parent: span, - }); + const childSpan = tracer.startSpan( + 'child-span', + {}, + setActiveSpan(Context.ROOT_CONTEXT, span) + ); const context = childSpan.context(); assert.strictEqual(context.traceId, span.context().traceId); assert.strictEqual(context.traceFlags, TraceFlags.SAMPLED); @@ -204,19 +198,66 @@ describe('BasicTracerProvider', () => { childSpan.end(); }); + it('should override context parent with option parent', () => { + const tracer = new BasicTracerProvider().getTracer('default'); + const span = tracer.startSpan('my-span'); + const overrideParent = tracer.startSpan('my-parent-override-span'); + const childSpan = tracer.startSpan( + 'child-span', + { + parent: overrideParent, + }, + setActiveSpan(Context.ROOT_CONTEXT, span) + ); + const context = childSpan.context(); + assert.strictEqual(context.traceId, overrideParent.context().traceId); + assert.strictEqual(context.traceFlags, TraceFlags.SAMPLED); + span.end(); + childSpan.end(); + }); + + it('should override context parent with option parent context', () => { + const tracer = new BasicTracerProvider().getTracer('default'); + const span = tracer.startSpan('my-span'); + const overrideParent = tracer.startSpan('my-parent-override-span'); + const childSpan = tracer.startSpan( + 'child-span', + { + parent: overrideParent.context(), + }, + setActiveSpan(Context.ROOT_CONTEXT, span) + ); + const context = childSpan.context(); + assert.strictEqual(context.traceId, overrideParent.context().traceId); + assert.strictEqual(context.traceFlags, TraceFlags.SAMPLED); + span.end(); + childSpan.end(); + }); + it('should start a span with name and with invalid parent span', () => { const tracer = new BasicTracerProvider().getTracer('default'); - const span = tracer.startSpan('my-span', { - parent: ('invalid-parent' as unknown) as undefined, - }) as Span; - assert.deepStrictEqual(span.parentSpanId, undefined); + const span = tracer.startSpan( + 'my-span', + {}, + setExtractedSpanContext( + Context.ROOT_CONTEXT, + ('invalid-parent' as unknown) as SpanContext + ) + ); + assert.ok(span instanceof Span); + assert.deepStrictEqual((span as Span).parentSpanId, undefined); }); it('should start a span with name and with invalid spancontext', () => { const tracer = new BasicTracerProvider().getTracer('default'); - const span = tracer.startSpan('my-span', { - parent: { traceId: '0', spanId: '0' }, - }); + const span = tracer.startSpan( + 'my-span', + {}, + setExtractedSpanContext(Context.ROOT_CONTEXT, { + traceId: '0', + spanId: '0', + }) + ); assert.ok(span instanceof Span); const context = span.context(); assert.ok(context.traceId.match(/[a-f0-9]{32}/)); @@ -275,7 +316,6 @@ describe('BasicTracerProvider', () => { it('should create real span when sampled and recording events true', () => { const tracer = new BasicTracerProvider({ sampler: ALWAYS_SAMPLER, - scopeManager: new NoopScopeManager(), }).getTracer('default'); const span = tracer.startSpan('my-span', { isRecording: true }); assert.ok(span instanceof Span); @@ -288,7 +328,6 @@ describe('BasicTracerProvider', () => { foo: 'bar', }; const tracer = new BasicTracerProvider({ - scopeManager: new NoopScopeManager(), defaultAttributes, }).getTracer('default'); @@ -299,19 +338,13 @@ describe('BasicTracerProvider', () => { }); describe('.getCurrentSpan()', () => { - it('should return null with NoopScopeManager', () => { - const tracer = new BasicTracerProvider().getTracer('default'); - const currentSpan = tracer.getCurrentSpan(); - assert.deepStrictEqual(currentSpan, undefined); - }); - it('should return current span when it exists', () => { - const tracer = new BasicTracerProvider({ - scopeManager: { - active: () => - setActiveSpan(Context.ROOT_CONTEXT, ('foo' as any) as Span), - } as ScopeManager, - }).getTracer('default'); + context.initGlobalContextManager({ + active: () => + setActiveSpan(Context.ROOT_CONTEXT, ('foo' as any) as Span), + } as ScopeManager); + + const tracer = new BasicTracerProvider().getTracer('default'); assert.deepStrictEqual(tracer.getCurrentSpan(), 'foo'); }); }); @@ -339,11 +372,4 @@ describe('BasicTracerProvider', () => { return patchedFn(); }); }); - - describe('.getHttpTextFormat()', () => { - it('should get default HTTP text formatter', () => { - const tracer = new BasicTracerProvider().getTracer('default'); - assert.ok(tracer.getHttpTextFormat() instanceof HttpTraceContext); - }); - }); }); diff --git a/packages/opentelemetry-tracing/test/export/InMemorySpanExporter.test.ts b/packages/opentelemetry-tracing/test/export/InMemorySpanExporter.test.ts index e0e3766c8d..cfd215ff88 100644 --- a/packages/opentelemetry-tracing/test/export/InMemorySpanExporter.test.ts +++ b/packages/opentelemetry-tracing/test/export/InMemorySpanExporter.test.ts @@ -21,6 +21,8 @@ import { BasicTracerProvider, } from '../../src'; import { ExportResult } from '@opentelemetry/base'; +import { context } from '@opentelemetry/api'; +import { setActiveSpan } from '@opentelemetry/core'; describe('InMemorySpanExporter', () => { const memoryExporter = new InMemorySpanExporter(); @@ -36,10 +38,10 @@ describe('InMemorySpanExporter', () => { const root = provider.getTracer('default').startSpan('root'); const child = provider .getTracer('default') - .startSpan('child', { parent: root }); + .startSpan('child', {}, setActiveSpan(context.active(), root)); const grandChild = provider .getTracer('default') - .startSpan('grand-child', { parent: child }); + .startSpan('grand-child', {}, setActiveSpan(context.active(), child)); assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); grandChild.end(); @@ -59,11 +61,12 @@ describe('InMemorySpanExporter', () => { assert.strictEqual(span2.parentSpanId, span3.spanContext.spanId); }); - it('should shutdown the exorter', () => { + it('should shutdown the exporter', () => { const root = provider.getTracer('default').startSpan('root'); + provider .getTracer('default') - .startSpan('child', { parent: root }) + .startSpan('child', {}, setActiveSpan(context.active(), root)) .end(); root.end(); assert.strictEqual(memoryExporter.getFinishedSpans().length, 2); @@ -73,7 +76,7 @@ describe('InMemorySpanExporter', () => { // after shutdown no new spans are accepted provider .getTracer('default') - .startSpan('child1', { parent: root }) + .startSpan('child1', {}, setActiveSpan(context.active(), root)) .end(); assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); }); diff --git a/packages/opentelemetry-web/src/WebTracerProvider.ts b/packages/opentelemetry-web/src/WebTracerProvider.ts index 831fbc475f..05f066cd53 100644 --- a/packages/opentelemetry-web/src/WebTracerProvider.ts +++ b/packages/opentelemetry-web/src/WebTracerProvider.ts @@ -16,7 +16,6 @@ import { BasePlugin } from '@opentelemetry/core'; import { BasicTracerProvider, TracerConfig } from '@opentelemetry/tracing'; -import { StackScopeManager } from './StackScopeManager'; /** * WebTracerConfig provides an interface for configuring a Web Tracer. @@ -37,15 +36,10 @@ export class WebTracerProvider extends BasicTracerProvider { * @param config Web Tracer config */ constructor(config: WebTracerConfig = {}) { - if (typeof config.scopeManager === 'undefined') { - config.scopeManager = new StackScopeManager(); - } if (typeof config.plugins === 'undefined') { config.plugins = []; } - super(Object.assign({}, { scopeManager: config.scopeManager }, config)); - - config.scopeManager.enable(); + super(config); for (const plugin of config.plugins) { plugin.enable([], this, this.logger); diff --git a/packages/opentelemetry-web/test/WebTracerProvider.test.ts b/packages/opentelemetry-web/test/WebTracerProvider.test.ts index 266cd119ae..63e1f1cb03 100644 --- a/packages/opentelemetry-web/test/WebTracerProvider.test.ts +++ b/packages/opentelemetry-web/test/WebTracerProvider.test.ts @@ -14,13 +14,14 @@ * limitations under the License. */ +import { context } from '@opentelemetry/api'; import { BasePlugin } from '@opentelemetry/core'; +import { ScopeManager } from '@opentelemetry/scope-base'; import { ZoneScopeManager } from '@opentelemetry/scope-zone'; -import { Tracer, TracerConfig } from '@opentelemetry/tracing'; +import { Tracer } from '@opentelemetry/tracing'; import * as assert from 'assert'; import * as sinon from 'sinon'; import { WebTracerConfig } from '../src'; -import { StackScopeManager } from '../src/StackScopeManager'; import { WebTracerProvider } from '../src/WebTracerProvider'; class DummyPlugin extends BasePlugin { @@ -36,11 +37,16 @@ class DummyPlugin extends BasePlugin { describe('WebTracerProvider', () => { describe('constructor', () => { let defaultOptions: WebTracerConfig; + let scopeManager: ScopeManager; beforeEach(() => { - defaultOptions = { - scopeManager: new StackScopeManager(), - }; + defaultOptions = {}; + scopeManager = new ZoneScopeManager().enable(); + context.initGlobalContextManager(scopeManager); + }); + + afterEach(() => { + scopeManager.disable(); }); it('should construct an instance with required only options', () => { @@ -50,17 +56,6 @@ describe('WebTracerProvider', () => { assert.ok(tracer instanceof Tracer); }); - it('should enable the scope manager', () => { - let options: TracerConfig; - const scopeManager = new StackScopeManager(); - options = { scopeManager }; - - const spy = sinon.spy(scopeManager, 'enable'); - new WebTracerProvider(options); - - assert.ok(spy.calledOnce === true); - }); - it('should enable all plugins', () => { let options: WebTracerConfig; const dummyPlugin1 = new DummyPlugin(); @@ -68,11 +63,9 @@ describe('WebTracerProvider', () => { const spyEnable1 = sinon.spy(dummyPlugin1, 'enable'); const spyEnable2 = sinon.spy(dummyPlugin2, 'enable'); - const scopeManager = new StackScopeManager(); - const plugins = [dummyPlugin1, dummyPlugin2]; - options = { plugins, scopeManager }; + options = { plugins }; new WebTracerProvider(options); assert.ok(spyEnable1.calledOnce === true); @@ -87,9 +80,7 @@ describe('WebTracerProvider', () => { describe('when scopeManager is "ZoneScopeManager"', () => { it('should correctly return the scopes for 2 parallel actions', () => { - const webTracerWithZone = new WebTracerProvider({ - scopeManager: new ZoneScopeManager(), - }).getTracer('default'); + const webTracerWithZone = new WebTracerProvider().getTracer('default'); const rootSpan = webTracerWithZone.startSpan('rootSpan');