From d4f39d1b8de045b3c8d5e6ccd0808d87167d0211 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Thu, 26 Mar 2020 22:09:16 +0100 Subject: [PATCH 1/3] chore: fixing documentation for web tracer provider, fixing examples for web, enable manager when registering --- .../examples/document-load/index.js | 69 ++++++++++--------- .../examples/xml-http-request/index.js | 2 - .../opentelemetry-context-base/package.json | 3 +- .../src/documentLoad.ts | 2 +- packages/opentelemetry-web/README.md | 8 ++- .../src/WebTracerProvider.ts | 10 +++ .../test/WebTracerProvider.test.ts | 37 +++++++++- 7 files changed, 91 insertions(+), 40 deletions(-) diff --git a/examples/tracer-web/examples/document-load/index.js b/examples/tracer-web/examples/document-load/index.js index 87cf8bf395..4228e8730b 100644 --- a/examples/tracer-web/examples/document-load/index.js +++ b/examples/tracer-web/examples/document-load/index.js @@ -10,19 +10,13 @@ const provider = new WebTracerProvider({ ], }); provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); +provider.addSpanProcessor(new SimpleSpanProcessor(new CollectorExporter())); -const providerWithZone = new WebTracerProvider({ +provider.register({ contextManager: new ZoneContextManager(), - plugins: [ - new DocumentLoad(), - ], }); -providerWithZone.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); -providerWithZone.addSpanProcessor(new SimpleSpanProcessor(new CollectorExporter())); -const tracerWithZone = providerWithZone.getTracer('example-tracer-web'); -let window; -console.log('Current span is window', tracerWithZone.getCurrentSpan() === window); +const tracer = provider.getTracer('example-tracer-web'); const getData = (url) => new Promise((resolve, reject) => { // eslint-disable-next-line no-undef @@ -45,37 +39,48 @@ const prepareClickEvent = () => { const url1 = 'https://raw.githubusercontent.com/open-telemetry/opentelemetry-js/master/package.json'; const url2 = 'https://raw.githubusercontent.com/open-telemetry/opentelemetry-js/master/packages/opentelemetry-web/package.json'; - let document; const element = document.getElementById('button1'); - const mainSpan = tracerWithZone.startSpan('main-span'); - tracerWithZone.bind(element, mainSpan); const onClick = () => { - const span1 = tracerWithZone.startSpan('files-series-info-1', { - parent: tracerWithZone.getCurrentSpan(), - }); + let count = 0; - const span2 = tracerWithZone.startSpan('files-series-info-2', { - parent: tracerWithZone.getCurrentSpan(), - }); + function finish() { + count++; + if (count === 2) { + mainSpan.end(); + } + } - tracerWithZone.withSpan(span1, () => { - getData(url1).then((data) => { - console.log('current span is span1', tracerWithZone.getCurrentSpan() === span1); - console.log('info from package.json', data.description, data.version); - tracerWithZone.getCurrentSpan().addEvent('fetching-span1-completed'); - span1.end(); + const mainSpan = tracer.startSpan('click button'); + tracer.withSpan(mainSpan, () => { + const span1 = tracer.startSpan('files-series-info-1', { + parent: tracer.getCurrentSpan(), + }); + + const span2 = tracer.startSpan('files-series-info-2', { + parent: tracer.getCurrentSpan(), }); - }); - tracerWithZone.withSpan(span2, () => { - getData(url2).then((data) => { - setTimeout(() => { - console.log('current span is span2', tracerWithZone.getCurrentSpan() === span2); + tracer.withSpan(span1, () => { + getData(url1).then((data) => { + console.log('current span is span1', tracer.getCurrentSpan() === span1); console.log('info from package.json', data.description, data.version); - tracerWithZone.getCurrentSpan().addEvent('fetching-span2-completed'); - span2.end(); - }, 100); + tracer.getCurrentSpan().addEvent('fetching-span1-completed'); + span1.end(); + finish(); + }); + }); + + tracer.withSpan(span2, () => { + getData(url2).then((data) => { + setTimeout(() => { + console.log('current span is span2', tracer.getCurrentSpan() === span2); + console.log('info from package.json', data.description, data.version); + tracer.getCurrentSpan().addEvent('fetching-span2-completed'); + span2.end(); + finish(); + }, 100); + }); }); }); }; diff --git a/examples/tracer-web/examples/xml-http-request/index.js b/examples/tracer-web/examples/xml-http-request/index.js index 0b7a021836..7f025ea14b 100644 --- a/examples/tracer-web/examples/xml-http-request/index.js +++ b/examples/tracer-web/examples/xml-http-request/index.js @@ -42,7 +42,6 @@ const getData = (url) => new Promise((resolve, _reject) => { const prepareClickEvent = () => { const url1 = 'https://httpbin.org/get'; - let document; const element = document.getElementById('button1'); const onClick = () => { @@ -61,5 +60,4 @@ const prepareClickEvent = () => { element.addEventListener('click', onClick); }; -let window; window.addEventListener('load', prepareClickEvent); diff --git a/packages/opentelemetry-context-base/package.json b/packages/opentelemetry-context-base/package.json index 8cc2ebedd7..a469fdbc0a 100644 --- a/packages/opentelemetry-context-base/package.json +++ b/packages/opentelemetry-context-base/package.json @@ -15,7 +15,8 @@ "precompile": "tsc --version", "version:update": "node ../../scripts/version-update.js", "compile": "npm run version:update && tsc -p .", - "prepare": "npm run compile" + "prepare": "npm run compile", + "watch": "tsc -w" }, "keywords": [ "opentelemetry", diff --git a/packages/opentelemetry-plugin-document-load/src/documentLoad.ts b/packages/opentelemetry-plugin-document-load/src/documentLoad.ts index 666d11c061..dd91efd4cb 100644 --- a/packages/opentelemetry-plugin-document-load/src/documentLoad.ts +++ b/packages/opentelemetry-plugin-document-load/src/documentLoad.ts @@ -76,7 +76,7 @@ export class DocumentLoad extends BasePlugin { ) as PerformanceResourceTiming[]; if (resources) { resources.forEach(resource => { - this._initResourceSpan(resource); + this._initResourceSpan(resource, { parent: rootSpan }); }); } } diff --git a/packages/opentelemetry-web/README.md b/packages/opentelemetry-web/README.md index 387a7a2b32..2d8ceeac67 100644 --- a/packages/opentelemetry-web/README.md +++ b/packages/opentelemetry-web/README.md @@ -45,16 +45,20 @@ const provider = new WebTracerProvider({ }); provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); +provider.register(); -// Changing default contextManager to use ZoneContextManager - supports asynchronous operations const providerWithZone = new WebTracerProvider({ - contextManager: new ZoneContextManager(), plugins: [ new DocumentLoad() ] }); providerWithZone.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); +// Changing default contextManager to use ZoneContextManager - supports asynchronous operations +providerWithZone.register({ + contextManager: new ZoneContextManager(), +}); + ``` ## Useful links diff --git a/packages/opentelemetry-web/src/WebTracerProvider.ts b/packages/opentelemetry-web/src/WebTracerProvider.ts index 2d4b625d9a..0dd8c1eb18 100644 --- a/packages/opentelemetry-web/src/WebTracerProvider.ts +++ b/packages/opentelemetry-web/src/WebTracerProvider.ts @@ -49,6 +49,14 @@ export class WebTracerProvider extends BasicTracerProvider { for (const plugin of config.plugins) { plugin.enable([], this, this.logger); } + + if ((config as SDKRegistrationConfig).contextManager) { + throw 'contextManager should be defined in register method not in' + + ' constructor'; + } + if ((config as SDKRegistrationConfig).propagator) { + throw 'propagator should be defined in register method not in constructor'; + } } /** @@ -61,6 +69,8 @@ export class WebTracerProvider extends BasicTracerProvider { register(config: SDKRegistrationConfig = {}) { if (config.contextManager === undefined) { config.contextManager = new StackContextManager(); + } + if (config.contextManager) { config.contextManager.enable(); } diff --git a/packages/opentelemetry-web/test/WebTracerProvider.test.ts b/packages/opentelemetry-web/test/WebTracerProvider.test.ts index 1443238f2e..e22d4cef86 100644 --- a/packages/opentelemetry-web/test/WebTracerProvider.test.ts +++ b/packages/opentelemetry-web/test/WebTracerProvider.test.ts @@ -15,7 +15,7 @@ */ import { context } from '@opentelemetry/api'; -import { BasePlugin, NoopLogger } from '@opentelemetry/core'; +import { B3Propagator, BasePlugin, NoopLogger } from '@opentelemetry/core'; import { ContextManager } from '@opentelemetry/context-base'; import { ZoneContextManager } from '@opentelemetry/context-zone'; import { Tracer, Span } from '@opentelemetry/tracing'; @@ -79,8 +79,40 @@ describe('WebTracerProvider', () => { }); }); + it('should throw error when context manager is passed in constructor', () => { + let error = ''; + try { + new WebTracerProvider({ + contextManager: new ZoneContextManager(), + } as any); + } catch (e) { + error = e; + } + assert.strictEqual( + error, + 'contextManager should be defined in' + + ' register method not in constructor' + ); + }); + + it('should throw error when propagator is passed in constructor', () => { + let error = ''; + try { + new WebTracerProvider({ + propagator: new B3Propagator(), + } as any); + } catch (e) { + error = e; + } + assert.strictEqual( + error, + 'propagator should be defined in register' + + ' method not in constructor' + ); + }); + describe('when contextManager is "ZoneContextManager"', () => { - it('should correctly return the contexts for 2 parallel actions', () => { + it('should correctly return the contexts for 2 parallel actions', done => { const webTracerWithZone = new WebTracerProvider().getTracer('default'); const rootSpan = webTracerWithZone.startSpan('rootSpan'); @@ -112,6 +144,7 @@ describe('WebTracerProvider', () => { webTracerWithZone.getCurrentSpan() === concurrentSpan2, 'Current span is concurrentSpan2' ); + done(); }, 20); }); }); From e104cf1c6d1580fcf7ff9e20751302c11b38ae75 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Fri, 27 Mar 2020 01:28:40 +0100 Subject: [PATCH 2/3] chore: fixing span extraction from zone after context updates --- .../opentelemetry-core/src/context/context.ts | 5 +- .../src/userInteraction.ts | 46 +++++++++++++------ 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/packages/opentelemetry-core/src/context/context.ts b/packages/opentelemetry-core/src/context/context.ts index e0a33a415c..c1890915ce 100644 --- a/packages/opentelemetry-core/src/context/context.ts +++ b/packages/opentelemetry-core/src/context/context.ts @@ -17,7 +17,10 @@ import { Span, SpanContext } from '@opentelemetry/api'; import { Context } from '@opentelemetry/context-base'; -const ACTIVE_SPAN_KEY = Context.createKey( +/** + * Active span key + */ +export const ACTIVE_SPAN_KEY = Context.createKey( 'OpenTelemetry Context Key ACTIVE_SPAN' ); const EXTRACTED_SPAN_CONTEXT_KEY = Context.createKey( diff --git a/packages/opentelemetry-plugin-user-interaction/src/userInteraction.ts b/packages/opentelemetry-plugin-user-interaction/src/userInteraction.ts index 3be533279a..6e2a134988 100644 --- a/packages/opentelemetry-plugin-user-interaction/src/userInteraction.ts +++ b/packages/opentelemetry-plugin-user-interaction/src/userInteraction.ts @@ -15,8 +15,13 @@ */ import * as shimmer from 'shimmer'; -import { BasePlugin, hrTime, isWrapped } from '@opentelemetry/core'; -import * as types from '@opentelemetry/api'; +import { + ACTIVE_SPAN_KEY, + BasePlugin, + hrTime, + isWrapped, +} from '@opentelemetry/core'; +import * as api from '@opentelemetry/api'; import { getElementXPath } from '@opentelemetry/web'; import { AsyncTask, @@ -41,7 +46,7 @@ export class UserInteractionPlugin extends BasePlugin { readonly component: string = 'user-interaction'; readonly version = VERSION; moduleName = this.component; - private _spansData = new WeakMap(); + private _spansData = new WeakMap(); private _zonePatched = false; constructor() { @@ -56,7 +61,7 @@ export class UserInteractionPlugin extends BasePlugin { * @param task * @param span */ - private _checkForTimeout(task: AsyncTask, span: types.Span) { + private _checkForTimeout(task: AsyncTask, span: api.Span) { const spanData = this._spansData.get(span); if (spanData) { if (task.source === 'setTimeout') { @@ -78,7 +83,7 @@ export class UserInteractionPlugin extends BasePlugin { private _createSpan( element: HTMLElement, eventName: string - ): types.Span | undefined { + ): api.Span | undefined { if (!element.getAttribute) { return undefined; } @@ -114,7 +119,7 @@ export class UserInteractionPlugin extends BasePlugin { * This is needed to be able to end span when no more tasks left * @param span */ - private _decrementTask(span: types.Span) { + private _decrementTask(span: api.Span) { const spanData = this._spansData.get(span); if (spanData) { spanData.taskCount--; @@ -124,6 +129,19 @@ export class UserInteractionPlugin extends BasePlugin { } } + /** + * Return the current span + * @param zone + * @private + */ + private _getCurrentSpan(zone: Zone): api.Span | undefined { + const context: api.Context | undefined = zone.get(ZONE_CONTEXT_KEY); + if (context) { + return context.getValue(ACTIVE_SPAN_KEY) as api.Span; + } + return context; + } + /** * It gets the element that has been clicked when zone tries to run a new task * @param task @@ -140,7 +158,7 @@ export class UserInteractionPlugin extends BasePlugin { * This is needed to be able to end span when no more tasks left * @param span */ - private _incrementTask(span: types.Span) { + private _incrementTask(span: api.Span) { const spanData = this._spansData.get(span); if (spanData) { spanData.taskCount++; @@ -228,7 +246,7 @@ export class UserInteractionPlugin extends BasePlugin { * @param url */ _updateInteractionName(url: string) { - const span: types.Span | undefined = this._tracer.getCurrentSpan(); + const span: api.Span | undefined = this._tracer.getCurrentSpan(); if (span && typeof span.updateName === 'function') { span.updateName(`${EVENT_NAVIGATION_NAME} ${url}`); } @@ -246,7 +264,7 @@ export class UserInteractionPlugin extends BasePlugin { task: AsyncTask ) { const currentZone = Zone.current; - const currentSpan = currentZone.get(ZONE_CONTEXT_KEY); + const currentSpan = plugin._getCurrentSpan(currentZone); if (currentSpan && plugin._shouldCountTask(task, currentZone)) { plugin._decrementTask(currentSpan); } @@ -269,7 +287,7 @@ export class UserInteractionPlugin extends BasePlugin { task: AsyncTask ) { const currentZone = Zone.current; - const currentSpan: types.Span = currentZone.get(ZONE_CONTEXT_KEY); + const currentSpan = plugin._getCurrentSpan(currentZone); if (currentSpan && plugin._shouldCountTask(task, currentZone)) { plugin._incrementTask(currentSpan); plugin._checkForTimeout(task, currentSpan); @@ -294,7 +312,7 @@ export class UserInteractionPlugin extends BasePlugin { applyArgs?: any ): Zone { const target: HTMLElement | undefined = plugin._getClickedElement(task); - let span: types.Span | undefined; + let span: api.Span | undefined; if (target) { span = plugin._createSpan(target, 'click'); if (span) { @@ -310,7 +328,7 @@ export class UserInteractionPlugin extends BasePlugin { } } } else { - span = this.get(ZONE_CONTEXT_KEY); + span = plugin._getCurrentSpan(this); } try { @@ -337,7 +355,7 @@ export class UserInteractionPlugin extends BasePlugin { if (!currentZone || !task.data || task.data.isPeriodic) { return false; } - const currentSpan = currentZone.get(ZONE_CONTEXT_KEY); + const currentSpan = this._getCurrentSpan(currentZone); if (!currentSpan) { return false; } @@ -353,7 +371,7 @@ export class UserInteractionPlugin extends BasePlugin { * @param endTime * @private */ - private _tryToEndSpan(span: types.Span, endTime?: types.HrTime) { + private _tryToEndSpan(span: api.Span, endTime?: api.HrTime) { if (span) { const spanData = this._spansData.get(span); if (spanData) { From 666f71e26f014174bb27b396af4f8a8039be6370 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Fri, 27 Mar 2020 01:38:54 +0100 Subject: [PATCH 3/3] chore: bump --- packages/opentelemetry-plugin-user-interaction/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opentelemetry-plugin-user-interaction/README.md b/packages/opentelemetry-plugin-user-interaction/README.md index 64e994eb9c..ba8dacf37f 100644 --- a/packages/opentelemetry-plugin-user-interaction/README.md +++ b/packages/opentelemetry-plugin-user-interaction/README.md @@ -102,3 +102,4 @@ Apache 2.0 - See [LICENSE][license-url] for more information. [npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fplugin-user-interaction.svg [zone-js]: https://www.npmjs.com/package/zone.js [@opentelemetry/context-zone]: https://www.npmjs.com/package/@opentelemetry/context-zone +