diff --git a/.circleci/config.yml b/.circleci/config.yml
index 208c5f711d..8138c5e300 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -48,7 +48,7 @@ browsers_unit_tests: &browsers_unit_tests
jobs:
lint:
docker:
- - image: node
+ - image: node:12
steps:
- checkout
- run:
@@ -59,7 +59,7 @@ jobs:
command: yarn run check
docs:
docker:
- - image: node
+ - image: node:12
steps:
- checkout
- run:
diff --git a/examples/tracer-web/index.html b/examples/tracer-web/index.html
index cf9634a00a..08bab8e1b0 100644
--- a/examples/tracer-web/index.html
+++ b/examples/tracer-web/index.html
@@ -3,14 +3,14 @@
- JS Example
+ Web Tracer Example
- Testing, debugging in development
+ Example of using Web Tracer with document load plugin and console exporter
diff --git a/examples/tracer-web/index.js b/examples/tracer-web/index.js
index 1d149f8af3..a8e3e34b41 100644
--- a/examples/tracer-web/index.js
+++ b/examples/tracer-web/index.js
@@ -1,38 +1,11 @@
+import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/tracing';
import { WebTracer } from '@opentelemetry/web';
+import { DocumentLoad } from '@opentelemetry/plugin-document-load';
-import * as shimmer from 'shimmer';
-
-class Tester {
- constructor() {
- }
- add(name) {
- console.log('calling add', name);
- }
-}
-
-const tester = new Tester();
-
-const webTracer = new WebTracer();
-const span = webTracer.startSpan('span1');
-
-shimmer.wrap(Tester.prototype, 'add', (originalFunction) => {
- return function patchedFunction() {
- try {
- span.addEvent('start');
- } catch (e) {
- console.log('error', e);
- } finally {
- const result = originalFunction.apply(this, arguments);
- span.addEvent('after call');
- span.end();
- return result;
- }
- };
-});
-
-webTracer.withSpan(span, function () {
- console.log(this === span);
+const webTracer = new WebTracer({
+ plugins: [
+ new DocumentLoad()
+ ]
});
-tester.add('foo');
-console.log(span);
+webTracer.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
diff --git a/examples/tracer-web/package.json b/examples/tracer-web/package.json
index f811d97eae..4ee8da499e 100644
--- a/examples/tracer-web/package.json
+++ b/examples/tracer-web/package.json
@@ -26,16 +26,17 @@
},
"devDependencies": {
"@babel/core": "^7.6.0",
- "@types/shimmer": "^1.0.1",
"babel-loader": "^8.0.6",
- "shimmer": "^1.2.0",
+ "ts-loader": "^6.0.4",
"webpack": "^4.35.2",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.1",
"webpack-merge": "^4.2.2"
},
"dependencies": {
+ "@opentelemetry/plugin-document-load": "^0.1.1",
+ "@opentelemetry/tracing": "^0.1.1",
"@opentelemetry/web": "^0.1.1"
},
"homepage": "https://github.com/open-telemetry/opentelemetry-js#readme"
-}
\ No newline at end of file
+}
diff --git a/packages/opentelemetry-core/src/common/time.ts b/packages/opentelemetry-core/src/common/time.ts
index 9a9d0e22b7..fcc564692e 100644
--- a/packages/opentelemetry-core/src/common/time.ts
+++ b/packages/opentelemetry-core/src/common/time.ts
@@ -16,6 +16,7 @@
import * as types from '@opentelemetry/types';
import { otperformance as performance } from '../platform';
+import { TimeOriginLegacy } from './types';
const NANOSECOND_DIGITS = 9;
const SECOND_TO_NANOSECONDS = Math.pow(10, NANOSECOND_DIGITS);
@@ -32,10 +33,21 @@ function numberToHrtime(epochMillis: number): types.HrTime {
return [seconds, nanos];
}
+function getTimeOrigin(): number {
+ let timeOrigin = performance.timeOrigin;
+ if (typeof timeOrigin !== 'number') {
+ const perf: TimeOriginLegacy = (performance as unknown) as TimeOriginLegacy;
+ timeOrigin = perf.timing && perf.timing.fetchStart;
+ }
+ return timeOrigin;
+}
+
// Returns an hrtime calculated via performance component.
export function hrTime(performanceNow?: number): types.HrTime {
- const timeOrigin = numberToHrtime(performance.timeOrigin);
- const now = numberToHrtime(performanceNow || performance.now());
+ const timeOrigin = numberToHrtime(getTimeOrigin());
+ const now = numberToHrtime(
+ typeof performanceNow === 'number' ? performanceNow : performance.now()
+ );
let seconds = timeOrigin[0] + now[0];
let nanos = timeOrigin[1] + now[1];
@@ -52,15 +64,14 @@ export function hrTime(performanceNow?: number): types.HrTime {
// Converts a TimeInput to an HrTime, defaults to _hrtime().
export function timeInputToHrTime(time: types.TimeInput): types.HrTime {
// process.hrtime
- if (Array.isArray(time)) {
- return time;
+ if (isTimeInputHrTime(time)) {
+ return time as types.HrTime;
} else if (typeof time === 'number') {
// Must be a performance.now() if it's smaller than process start time.
- if (time < performance.timeOrigin) {
+ if (time < getTimeOrigin()) {
return hrTime(time);
- }
- // epoch milliseconds or performance.timeOrigin
- else {
+ } else {
+ // epoch milliseconds or performance.timeOrigin
return numberToHrtime(time);
}
} else if (time instanceof Date) {
@@ -102,3 +113,21 @@ export function hrTimeToMilliseconds(hrTime: types.HrTime): number {
export function hrTimeToMicroseconds(hrTime: types.HrTime): number {
return Math.round(hrTime[0] * 1e6 + hrTime[1] / 1e3);
}
+
+export function isTimeInputHrTime(value: unknown) {
+ return (
+ Array.isArray(value) &&
+ value.length === 2 &&
+ typeof value[0] === 'number' &&
+ typeof value[1] === 'number'
+ );
+}
+
+// check if input value is a correct types.TimeInput
+export function isTimeInput(value: unknown) {
+ return (
+ isTimeInputHrTime(value) ||
+ typeof value === 'number' ||
+ value instanceof Date
+ );
+}
diff --git a/packages/opentelemetry-core/src/common/types.ts b/packages/opentelemetry-core/src/common/types.ts
index 7c549e3a12..d765535031 100644
--- a/packages/opentelemetry-core/src/common/types.ts
+++ b/packages/opentelemetry-core/src/common/types.ts
@@ -21,3 +21,14 @@ export enum LogLevel {
INFO,
DEBUG,
}
+
+/**
+ * This interface defines a fallback to read a timeOrigin when it is not available on performance.timeOrigin,
+ * this happens for example on Safari Mac
+ * then the timeOrigin is taken from fetchStart - which is the closest to timeOrigin
+ */
+export interface TimeOriginLegacy {
+ timing: {
+ fetchStart: number;
+ };
+}
diff --git a/packages/opentelemetry-core/test/common/time.test.ts b/packages/opentelemetry-core/test/common/time.test.ts
index a974dda198..4eb82a9b78 100644
--- a/packages/opentelemetry-core/test/common/time.test.ts
+++ b/packages/opentelemetry-core/test/common/time.test.ts
@@ -25,6 +25,7 @@ import {
hrTimeToNanoseconds,
hrTimeToMilliseconds,
hrTimeToMicroseconds,
+ isTimeInput,
} from '../../src/common/time';
describe('time', () => {
@@ -62,6 +63,47 @@ describe('time', () => {
const output = hrTime(performanceNow);
assert.deepStrictEqual(output, [0, 23100000]);
});
+
+ it('should allow passed "performanceNow" equal to 0', () => {
+ sandbox.stub(performance, 'timeOrigin').value(11.5);
+ sandbox.stub(performance, 'now').callsFake(() => 11.3);
+
+ const output = hrTime(0);
+ assert.deepStrictEqual(output, [0, 11500000]);
+ });
+
+ it('should use performance.now() when "performanceNow" is equal to undefined', () => {
+ sandbox.stub(performance, 'timeOrigin').value(11.5);
+ sandbox.stub(performance, 'now').callsFake(() => 11.3);
+
+ const output = hrTime(undefined);
+ assert.deepStrictEqual(output, [0, 22800000]);
+ });
+
+ it('should use performance.now() when "performanceNow" is equal to null', () => {
+ sandbox.stub(performance, 'timeOrigin').value(11.5);
+ sandbox.stub(performance, 'now').callsFake(() => 11.3);
+
+ const output = hrTime(null as any);
+ assert.deepStrictEqual(output, [0, 22800000]);
+ });
+
+ describe('when timeOrigin is not available', () => {
+ it('should use the performance.timing.fetchStart as a fallback', () => {
+ Object.defineProperty(performance, 'timing', {
+ writable: true,
+ value: {
+ fetchStart: 11.5,
+ },
+ });
+
+ sandbox.stub(performance, 'timeOrigin').value(undefined);
+ sandbox.stub(performance, 'now').callsFake(() => 11.3);
+
+ const output = hrTime();
+ assert.deepStrictEqual(output, [0, 22800000]);
+ });
+ });
});
describe('#timeInputToHrTime', () => {
@@ -134,4 +176,32 @@ describe('time', () => {
assert.deepStrictEqual(output, 1200000);
});
});
+ describe('#isTimeInput', () => {
+ it('should return true for a number', () => {
+ assert.strictEqual(isTimeInput(12), true);
+ });
+ it('should return true for a date', () => {
+ assert.strictEqual(isTimeInput(new Date()), true);
+ });
+ it('should return true for an array with 2 elements type number', () => {
+ assert.strictEqual(isTimeInput([1, 1]), true);
+ });
+ it('should return FALSE for different cases for an array ', () => {
+ assert.strictEqual(isTimeInput([1, 1, 1]), false);
+ assert.strictEqual(isTimeInput([1]), false);
+ assert.strictEqual(isTimeInput([1, 'a']), false);
+ });
+ it('should return FALSE for a string', () => {
+ assert.strictEqual(isTimeInput('a'), false);
+ });
+ it('should return FALSE for an object', () => {
+ assert.strictEqual(isTimeInput({}), false);
+ });
+ it('should return FALSE for a null', () => {
+ assert.strictEqual(isTimeInput(null), false);
+ });
+ it('should return FALSE for undefined', () => {
+ assert.strictEqual(isTimeInput(undefined), false);
+ });
+ });
});
diff --git a/packages/opentelemetry-plugin-document-load/LICENSE b/packages/opentelemetry-plugin-document-load/LICENSE
new file mode 100644
index 0000000000..261eeb9e9f
--- /dev/null
+++ b/packages/opentelemetry-plugin-document-load/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/packages/opentelemetry-plugin-document-load/README.md b/packages/opentelemetry-plugin-document-load/README.md
new file mode 100644
index 0000000000..cfc8346618
--- /dev/null
+++ b/packages/opentelemetry-plugin-document-load/README.md
@@ -0,0 +1,50 @@
+# OpenTelemetry Plugin Document Load
+[![Gitter chat][gitter-image]][gitter-url]
+[![NPM Published Version][npm-img]][npm-url]
+[![dependencies][dependencies-image]][dependencies-url]
+[![devDependencies][devDependencies-image]][devDependencies-url]
+[![Apache License][license-image]][license-image]
+
+This module provides *automated instrumentation for document load* for Web applications.
+
+## Installation
+
+```bash
+npm install --save @opentelemetry/plugin-document-load
+```
+
+## Usage
+
+```js
+import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/tracing';
+import { WebTracer } from '@opentelemetry/web';
+import { DocumentLoad } from '@opentelemetry/plugin-document-load';
+
+const webTracer = new WebTracer({
+ plugins: [
+ new DocumentLoad()
+ ]
+});
+
+webTracer.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
+```
+
+## Useful links
+- For more information on OpenTelemetry, visit:
+- For more about OpenTelemetry JavaScript:
+- For help or feedback on this project, join us on [gitter][gitter-url]
+
+## License
+
+Apache 2.0 - See [LICENSE][license-url] for more information.
+
+[gitter-image]: https://badges.gitter.im/open-telemetry/opentelemetry-js.svg
+[gitter-url]: https://gitter.im/open-telemetry/opentelemetry-node?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
+[license-url]: https://github.com/open-telemetry/opentelemetry-js/blob/master/LICENSE
+[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat
+[dependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js/status.svg?path=packages/opentelemetry-plugin-document-load
+[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-plugin-document-load
+[devDependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js/dev-status.svg?path=packages/opentelemetry-plugin-document-load
+[devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-plugin-document-load&type=dev
+[npm-url]: https://www.npmjs.com/package/@opentelemetry/plugin-document-load
+[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fplugin-document-load.svg
diff --git a/packages/opentelemetry-plugin-document-load/karma.conf.js b/packages/opentelemetry-plugin-document-load/karma.conf.js
new file mode 100644
index 0000000000..66529c7d92
--- /dev/null
+++ b/packages/opentelemetry-plugin-document-load/karma.conf.js
@@ -0,0 +1,24 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const webpackConfig = require('./webpack/test.config.js');
+const karmaBaseConfig = require('../../karma.base');
+
+module.exports = (config) => {
+ config.set(Object.assign({}, karmaBaseConfig, {
+ webpack: webpackConfig
+ }))
+};
diff --git a/packages/opentelemetry-plugin-document-load/package.json b/packages/opentelemetry-plugin-document-load/package.json
new file mode 100644
index 0000000000..9597ca16fc
--- /dev/null
+++ b/packages/opentelemetry-plugin-document-load/package.json
@@ -0,0 +1,76 @@
+{
+ "name": "@opentelemetry/plugin-document-load",
+ "version": "0.1.1",
+ "description": "OpenTelemetry document-load automatic instrumentation package.",
+ "main": "build/src/index.js",
+ "types": "build/src/index.d.ts",
+ "repository": "open-telemetry/opentelemetry-js",
+ "scripts": {
+ "clean": "rimraf build/*",
+ "check": "gts check",
+ "codecov:browser": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
+ "compile": "tsc -p .",
+ "fix": "gts fix",
+ "prepare": "npm run compile",
+ "tdd": "karma start",
+ "test:browser": "nyc karma start --single-run",
+ "watch": "tsc -w"
+ },
+ "keywords": [
+ "opentelemetry",
+ "document-load",
+ "web",
+ "tracing",
+ "profiling",
+ "plugin"
+ ],
+ "author": "OpenTelemetry Authors",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "files": [
+ "build/src/**/*.js",
+ "build/src/**/*.d.ts",
+ "doc",
+ "LICENSE",
+ "README.md"
+ ],
+ "publishConfig": {
+ "access": "public"
+ },
+ "devDependencies": {
+ "@types/mocha": "^5.2.5",
+ "@types/node": "^12.6.8",
+ "@types/webpack-env": "1.13.9",
+ "@types/sinon": "^7.0.13",
+ "@babel/core": "^7.6.0",
+ "babel-loader": "^8.0.6",
+ "codecov": "^3.1.0",
+ "gts": "^1.0.0",
+ "karma": "^4.1.0",
+ "karma-chrome-launcher": "^2.2.0",
+ "karma-mocha": "^1.3.0",
+ "karma-spec-reporter": "^0.0.32",
+ "karma-webpack": "^4.0.2",
+ "mocha": "^6.1.0",
+ "nyc": "^14.1.1",
+ "rimraf": "^3.0.0",
+ "sinon": "^7.5.0",
+ "tslint-consistent-codestyle" : "^1.16.0",
+ "tslint-microsoft-contrib": "^6.2.0",
+ "ts-loader": "^6.0.4",
+ "ts-mocha": "^6.0.0",
+ "ts-node": "^8.0.0",
+ "typescript": "^3.6.3",
+ "webpack": "^4.35.2",
+ "webpack-cli": "^3.3.9",
+ "webpack-merge": "^4.2.2"
+ },
+ "dependencies": {
+ "@opentelemetry/core": "^0.1.1",
+ "@opentelemetry/types": "^0.1.1",
+ "@opentelemetry/tracing": "^0.1.1",
+ "@opentelemetry/web": "^0.1.1"
+ }
+}
diff --git a/packages/opentelemetry-plugin-document-load/src/documentLoad.ts b/packages/opentelemetry-plugin-document-load/src/documentLoad.ts
new file mode 100644
index 0000000000..6b8255cc88
--- /dev/null
+++ b/packages/opentelemetry-plugin-document-load/src/documentLoad.ts
@@ -0,0 +1,231 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { BasePlugin, otperformance } from '@opentelemetry/core';
+import { PluginConfig, Span, SpanOptions } from '@opentelemetry/types';
+import { AttributeNames } from './enums/AttributeNames';
+import { PerformanceTimingNames as PTN } from './enums/PerformanceTimingNames';
+import { PerformanceEntries, PerformanceLegacy } from './types';
+import { hasKey } from './utils';
+
+/**
+ * This class represents a document load plugin
+ */
+export class DocumentLoad extends BasePlugin {
+ readonly component: string = 'document-load';
+ readonly version: string = '1';
+ moduleName = this.component;
+ protected _config!: PluginConfig;
+
+ /**
+ *
+ * @param config
+ */
+ constructor(config: PluginConfig = {}) {
+ super();
+ this._onDocumentLoaded = this._onDocumentLoaded.bind(this);
+ this._config = config;
+ }
+
+ /**
+ * callback to be executed when page is loaded
+ */
+ private _onDocumentLoaded() {
+ this._collectPerformance();
+ }
+
+ /**
+ * Helper function for starting an event
+ * @param span
+ * @param performanceName name of performance entry for time start
+ * @param entries
+ */
+ private _addSpanEvent(
+ span: Span,
+ performanceName: string,
+ entries: PerformanceEntries
+ ): Span | undefined {
+ if (
+ hasKey(entries, performanceName) &&
+ typeof entries[performanceName] === 'number'
+ ) {
+ span.addEvent(performanceName, undefined, entries[performanceName]);
+ return span;
+ }
+ return undefined;
+ }
+
+ /**
+ * Collects information about performance and creates appropriate spans
+ */
+ private _collectPerformance() {
+ const entries = this._getEntries();
+
+ const rootSpan = this._startSpan(
+ AttributeNames.DOCUMENT_LOAD,
+ PTN.FETCH_START,
+ entries
+ );
+ if (!rootSpan) {
+ return;
+ }
+ const fetchSpan = this._startSpan(
+ AttributeNames.DOCUMENT_FETCH,
+ PTN.FETCH_START,
+ entries,
+ {
+ parent: rootSpan,
+ }
+ );
+ if (fetchSpan) {
+ this._addSpanEvent(fetchSpan, PTN.DOMAIN_LOOKUP_START, entries);
+ this._addSpanEvent(fetchSpan, PTN.DOMAIN_LOOKUP_END, entries);
+ this._addSpanEvent(fetchSpan, PTN.CONNECT_START, entries);
+ this._addSpanEvent(fetchSpan, PTN.SECURE_CONNECTION_START, entries);
+ this._addSpanEvent(fetchSpan, PTN.CONNECT_END, entries);
+ this._addSpanEvent(fetchSpan, PTN.REQUEST_START, entries);
+ this._addSpanEvent(fetchSpan, PTN.RESPONSE_START, entries);
+
+ this._endSpan(fetchSpan, PTN.RESPONSE_END, entries);
+ }
+
+ this._addSpanEvent(rootSpan, PTN.UNLOAD_EVENT_START, entries);
+ this._addSpanEvent(rootSpan, PTN.UNLOAD_EVENT_END, entries);
+ this._addSpanEvent(rootSpan, PTN.DOM_INTERACTIVE, entries);
+ this._addSpanEvent(rootSpan, PTN.DOM_CONTENT_LOADED_EVENT_START, entries);
+ this._addSpanEvent(rootSpan, PTN.DOM_CONTENT_LOADED_EVENT_END, entries);
+ this._addSpanEvent(rootSpan, PTN.DOM_COMPLETE, entries);
+ this._addSpanEvent(rootSpan, PTN.LOAD_EVENT_START, entries);
+
+ this._endSpan(rootSpan, PTN.LOAD_EVENT_END, entries);
+ }
+
+ /**
+ * Helper function for ending span
+ * @param span
+ * @param performanceName name of performance entry for time end
+ * @param entries
+ */
+ private _endSpan(
+ span: Span | undefined,
+ performanceName: string,
+ entries: PerformanceEntries
+ ) {
+ // span can be undefined when entries are missing the certain performance - the span will not be created
+ if (typeof span !== 'undefined' && hasKey(entries, performanceName)) {
+ this._addSpanEvent(span, performanceName, entries);
+ span.end(entries[performanceName]);
+ }
+ }
+
+ /**
+ * gets performance entries of navigation
+ */
+ private _getEntries() {
+ const entries: PerformanceEntries = {};
+ const performanceNavigationTiming = (otperformance.getEntriesByType(
+ 'navigation'
+ )[0] as unknown) as PerformanceEntries;
+
+ if (performanceNavigationTiming) {
+ const keys = Object.values(PTN);
+ keys.forEach((key: string) => {
+ if (hasKey(performanceNavigationTiming, key)) {
+ const value = performanceNavigationTiming[key];
+ if (typeof value === 'number' && value > 0) {
+ entries[key] = value;
+ }
+ }
+ });
+ } else {
+ // // fallback to previous version
+ const perf: (typeof otperformance) & PerformanceLegacy = otperformance;
+ const performanceTiming = perf.timing;
+ if (performanceTiming) {
+ const keys = Object.values(PTN);
+ keys.forEach((key: string) => {
+ if (hasKey(performanceTiming, key)) {
+ const value = performanceTiming[key];
+ if (typeof value === 'number' && value > 0) {
+ entries[key] = value;
+ }
+ }
+ });
+ }
+ }
+ return entries;
+ }
+
+ /**
+ * Helper function for starting a span
+ * @param spanName name of span
+ * @param performanceName name of performance entry for time start
+ * @param entries
+ * @param spanOptions
+ */
+ private _startSpan(
+ spanName: string,
+ performanceName: string,
+ entries: PerformanceEntries,
+ spanOptions: SpanOptions = {}
+ ): Span | undefined {
+ if (
+ hasKey(entries, performanceName) &&
+ typeof entries[performanceName] === 'number'
+ ) {
+ const span = this._tracer.startSpan(
+ spanName,
+ Object.assign(
+ {},
+ {
+ startTime: entries[performanceName],
+ },
+ spanOptions
+ )
+ );
+ span.setAttribute(AttributeNames.COMPONENT, this.component);
+ this._addSpanEvent(span, performanceName, entries);
+ return span;
+ }
+ return undefined;
+ }
+
+ /**
+ * executes callback {_onDocumentLoaded} when the page is loaded
+ */
+ private _waitForPageLoad() {
+ if (window.document.readyState === 'complete') {
+ this._onDocumentLoaded();
+ } else {
+ window.addEventListener('load', this._onDocumentLoaded);
+ }
+ }
+
+ /**
+ * implements patch function
+ */
+ protected patch() {
+ this._waitForPageLoad();
+ return this._moduleExports;
+ }
+
+ /**
+ * implements unpatch function
+ */
+ protected unpatch() {
+ window.removeEventListener('load', this._onDocumentLoaded);
+ }
+}
diff --git a/packages/opentelemetry-plugin-document-load/src/enums/AttributeNames.ts b/packages/opentelemetry-plugin-document-load/src/enums/AttributeNames.ts
new file mode 100644
index 0000000000..2e4a7d9ddf
--- /dev/null
+++ b/packages/opentelemetry-plugin-document-load/src/enums/AttributeNames.ts
@@ -0,0 +1,21 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export enum AttributeNames {
+ COMPONENT = 'component',
+ DOCUMENT_LOAD = 'documentLoad',
+ DOCUMENT_FETCH = 'documentFetch',
+}
diff --git a/packages/opentelemetry-plugin-document-load/src/enums/PerformanceTimingNames.ts b/packages/opentelemetry-plugin-document-load/src/enums/PerformanceTimingNames.ts
new file mode 100644
index 0000000000..12d5a44fa8
--- /dev/null
+++ b/packages/opentelemetry-plugin-document-load/src/enums/PerformanceTimingNames.ts
@@ -0,0 +1,37 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export enum PerformanceTimingNames {
+ CONNECT_END = 'connectEnd',
+ CONNECT_START = 'connectStart',
+ DOM_COMPLETE = 'domComplete',
+ DOM_CONTENT_LOADED_EVENT_END = 'domContentLoadedEventEnd',
+ DOM_CONTENT_LOADED_EVENT_START = 'domContentLoadedEventStart',
+ DOM_INTERACTIVE = 'domInteractive',
+ DOMAIN_LOOKUP_END = 'domainLookupEnd',
+ DOMAIN_LOOKUP_START = 'domainLookupStart',
+ FETCH_START = 'fetchStart',
+ LOAD_EVENT_END = 'loadEventEnd',
+ LOAD_EVENT_START = 'loadEventStart',
+ REDIRECT_END = 'redirectEnd',
+ REDIRECT_START = 'redirectStart',
+ REQUEST_START = 'requestStart',
+ RESPONSE_END = 'responseEnd',
+ RESPONSE_START = 'responseStart',
+ SECURE_CONNECTION_START = 'secureConnectionStart',
+ UNLOAD_EVENT_END = 'unloadEventEnd',
+ UNLOAD_EVENT_START = 'unloadEventStart',
+}
diff --git a/packages/opentelemetry-plugin-document-load/src/index.ts b/packages/opentelemetry-plugin-document-load/src/index.ts
new file mode 100644
index 0000000000..865a66fe1a
--- /dev/null
+++ b/packages/opentelemetry-plugin-document-load/src/index.ts
@@ -0,0 +1,17 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './documentLoad';
diff --git a/packages/opentelemetry-plugin-document-load/src/types.ts b/packages/opentelemetry-plugin-document-load/src/types.ts
new file mode 100644
index 0000000000..cd99cb6e9c
--- /dev/null
+++ b/packages/opentelemetry-plugin-document-load/src/types.ts
@@ -0,0 +1,50 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { PerformanceTimingNames } from './enums/PerformanceTimingNames';
+
+/**
+ * Performance metrics
+ */
+export type PerformanceEntries = {
+ [PerformanceTimingNames.CONNECT_END]?: number;
+ [PerformanceTimingNames.CONNECT_START]?: number;
+ [PerformanceTimingNames.DOM_COMPLETE]?: number;
+ [PerformanceTimingNames.DOM_CONTENT_LOADED_EVENT_END]?: number;
+ [PerformanceTimingNames.DOM_CONTENT_LOADED_EVENT_START]?: number;
+ [PerformanceTimingNames.DOM_INTERACTIVE]?: number;
+ [PerformanceTimingNames.DOMAIN_LOOKUP_END]?: number;
+ [PerformanceTimingNames.DOMAIN_LOOKUP_START]?: number;
+ [PerformanceTimingNames.FETCH_START]?: number;
+ [PerformanceTimingNames.LOAD_EVENT_END]?: number;
+ [PerformanceTimingNames.LOAD_EVENT_START]?: number;
+ [PerformanceTimingNames.REDIRECT_END]?: number;
+ [PerformanceTimingNames.REDIRECT_START]?: number;
+ [PerformanceTimingNames.REQUEST_START]?: number;
+ [PerformanceTimingNames.RESPONSE_END]?: number;
+ [PerformanceTimingNames.RESPONSE_START]?: number;
+ [PerformanceTimingNames.SECURE_CONNECTION_START]?: number;
+ [PerformanceTimingNames.UNLOAD_EVENT_END]?: number;
+ [PerformanceTimingNames.UNLOAD_EVENT_START]?: number;
+};
+
+/**
+ * This interface defines a fallback to read performance metrics,
+ * this happens for example on Safari Mac
+ */
+export interface PerformanceLegacy {
+ timing?: PerformanceEntries;
+}
diff --git a/packages/opentelemetry-plugin-document-load/src/utils.ts b/packages/opentelemetry-plugin-document-load/src/utils.ts
new file mode 100644
index 0000000000..cb585de753
--- /dev/null
+++ b/packages/opentelemetry-plugin-document-load/src/utils.ts
@@ -0,0 +1,24 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Helper function to be able to use enum as typed key in type and in interface when using forEach
+ * @param obj
+ * @param key
+ */
+export function hasKey(obj: O, key: keyof any): key is keyof O {
+ return key in obj;
+}
diff --git a/packages/opentelemetry-plugin-document-load/test/documentLoad.test.ts b/packages/opentelemetry-plugin-document-load/test/documentLoad.test.ts
new file mode 100644
index 0000000000..ecd99ce0c3
--- /dev/null
+++ b/packages/opentelemetry-plugin-document-load/test/documentLoad.test.ts
@@ -0,0 +1,305 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as assert from 'assert';
+import * as sinon from 'sinon';
+
+import { ConsoleLogger } from '@opentelemetry/core';
+import {
+ BasicTracer,
+ ReadableSpan,
+ SimpleSpanProcessor,
+ SpanExporter,
+} from '@opentelemetry/tracing';
+import { Logger, PluginConfig } from '@opentelemetry/types';
+
+import { ExportResult } from '../../opentelemetry-base/build/src';
+import { DocumentLoad } from '../src';
+import { PerformanceTimingNames as PTN } from '../src/enums/PerformanceTimingNames';
+
+export class DummyExporter implements SpanExporter {
+ export(
+ spans: ReadableSpan[],
+ resultCallback: (result: ExportResult) => void
+ ) {}
+
+ shutdown() {}
+}
+
+describe('DocumentLoad Plugin', () => {
+ let plugin: DocumentLoad;
+ let moduleExports: any;
+ let tracer: BasicTracer;
+ let logger: Logger;
+ let config: PluginConfig;
+ let spanProcessor: SimpleSpanProcessor;
+ let dummyExporter: DummyExporter;
+
+ beforeEach(() => {
+ Object.defineProperty(window.document, 'readyState', {
+ writable: true,
+ value: 'complete',
+ });
+ moduleExports = {};
+ tracer = new BasicTracer();
+ logger = new ConsoleLogger();
+ config = {};
+ plugin = new DocumentLoad();
+ dummyExporter = new DummyExporter();
+ spanProcessor = new SimpleSpanProcessor(dummyExporter);
+ tracer.addSpanProcessor(spanProcessor);
+ });
+
+ afterEach(() => {
+ Object.defineProperty(window.document, 'readyState', {
+ writable: true,
+ value: 'complete',
+ });
+ });
+
+ describe('constructor', () => {
+ it('should construct an instance', () => {
+ plugin = new DocumentLoad();
+ assert.ok(plugin instanceof DocumentLoad);
+ });
+ });
+
+ describe('when document readyState is complete', () => {
+ it('should start collecting the performance immediately', () => {
+ const spyOnEnd = sinon.spy(dummyExporter, 'export');
+ plugin.enable(moduleExports, tracer, logger, config);
+ assert.strictEqual(window.document.readyState, 'complete');
+ assert.strictEqual(spyOnEnd.callCount, 2);
+ });
+ });
+
+ describe('when document readyState is not complete', () => {
+ beforeEach(() => {
+ Object.defineProperty(window.document, 'readyState', {
+ writable: true,
+ value: 'loading',
+ });
+ });
+
+ it('should collect performance after document load event', () => {
+ const spy = sinon.spy(window, 'addEventListener');
+ const spyOnEnd = sinon.spy(dummyExporter, 'export');
+
+ plugin.enable(moduleExports, tracer, logger, config);
+ const args = spy.args[0];
+ const name = args[0];
+ assert.strictEqual(name, 'load');
+ assert.ok(spy.calledOnce);
+ assert.ok(spyOnEnd.callCount === 0);
+
+ window.dispatchEvent(
+ new CustomEvent('load', {
+ bubbles: true,
+ cancelable: false,
+ composed: true,
+ detail: {},
+ })
+ );
+ assert.strictEqual(spyOnEnd.callCount, 2);
+ });
+ });
+
+ describe('when navigation entries types are available', () => {
+ let spyExport: any;
+
+ beforeEach(() => {
+ const entries = {
+ name: 'http://localhost:8090/',
+ entryType: 'navigation',
+ startTime: 0,
+ duration: 374.0100000286475,
+ initiatorType: 'navigation',
+ nextHopProtocol: 'http/1.1',
+ workerStart: 0,
+ redirectStart: 0,
+ redirectEnd: 0,
+ fetchStart: 0.7999999215826392,
+ domainLookupStart: 0.7999999215826392,
+ domainLookupEnd: 0.7999999215826392,
+ connectStart: 0.7999999215826392,
+ connectEnd: 0.7999999215826393,
+ secureConnectionStart: 0.7999999215826392,
+ requestStart: 4.480000003241003,
+ responseStart: 5.729999975301325,
+ responseEnd: 6.154999951831996,
+ transferSize: 655,
+ encodedBodySize: 362,
+ decodedBodySize: 362,
+ serverTiming: [],
+ unloadEventStart: 12.63499993365258,
+ unloadEventEnd: 13.514999998733401,
+ domInteractive: 200.12499997392297,
+ domContentLoadedEventStart: 200.13999997172505,
+ domContentLoadedEventEnd: 201.6000000294298,
+ domComplete: 370.62499998137355,
+ loadEventStart: 370.64999993890524,
+ loadEventEnd: 374.0100000286475,
+ type: 'reload',
+ redirectCount: 0,
+ } as any;
+ spyExport = sinon
+ .stub(window.performance, 'getEntriesByType')
+ .returns([entries]);
+ });
+
+ it('should export correct span with events', () => {
+ const spyOnEnd = sinon.spy(dummyExporter, 'export');
+ plugin.enable(moduleExports, tracer, logger, config);
+
+ const span1 = spyOnEnd.args[0][0][0] as ReadableSpan;
+ const span2 = spyOnEnd.args[1][0][0] as ReadableSpan;
+ const events1 = span1.events;
+ const events2 = span2.events;
+
+ assert.strictEqual(span1.name, 'documentFetch');
+ assert.strictEqual(span2.name, 'documentLoad');
+
+ assert.strictEqual(events1[0].name, PTN.FETCH_START);
+ assert.strictEqual(events1[1].name, PTN.DOMAIN_LOOKUP_START);
+ assert.strictEqual(events1[2].name, PTN.DOMAIN_LOOKUP_END);
+ assert.strictEqual(events1[3].name, PTN.CONNECT_START);
+ assert.strictEqual(events1[4].name, PTN.SECURE_CONNECTION_START);
+ assert.strictEqual(events1[5].name, PTN.CONNECT_END);
+ assert.strictEqual(events1[6].name, PTN.REQUEST_START);
+ assert.strictEqual(events1[7].name, PTN.RESPONSE_START);
+ assert.strictEqual(events1[8].name, PTN.RESPONSE_END);
+
+ assert.strictEqual(events2[0].name, PTN.FETCH_START);
+ assert.strictEqual(events2[1].name, PTN.UNLOAD_EVENT_START);
+ assert.strictEqual(events2[2].name, PTN.UNLOAD_EVENT_END);
+ assert.strictEqual(events2[3].name, PTN.DOM_INTERACTIVE);
+ assert.strictEqual(events2[4].name, PTN.DOM_CONTENT_LOADED_EVENT_START);
+ assert.strictEqual(events2[5].name, PTN.DOM_CONTENT_LOADED_EVENT_END);
+ assert.strictEqual(events2[6].name, PTN.DOM_COMPLETE);
+ assert.strictEqual(events2[7].name, PTN.LOAD_EVENT_START);
+ assert.strictEqual(events2[8].name, PTN.LOAD_EVENT_END);
+
+ assert.strictEqual(events1.length, 9);
+ assert.strictEqual(events2.length, 9);
+ assert.strictEqual(spyOnEnd.callCount, 2);
+ });
+ afterEach(() => {
+ spyExport.restore();
+ });
+ });
+
+ describe('when navigation entries types are NOT available then fallback to "performance.timing"', () => {
+ let spyExport: any;
+
+ beforeEach(() => {
+ const entries = {
+ navigationStart: 1571078170305,
+ unloadEventStart: 0,
+ unloadEventEnd: 0,
+ redirectStart: 0,
+ redirectEnd: 0,
+ fetchStart: 1571078170305,
+ domainLookupStart: 1571078170307,
+ domainLookupEnd: 1571078170308,
+ connectStart: 1571078170309,
+ connectEnd: 1571078170310,
+ secureConnectionStart: 1571078170310,
+ requestStart: 1571078170310,
+ responseStart: 1571078170313,
+ responseEnd: 1571078170330,
+ domLoading: 1571078170331,
+ domInteractive: 1571078170392,
+ domContentLoadedEventStart: 1571078170392,
+ domContentLoadedEventEnd: 1571078170392,
+ domComplete: 1571078170393,
+ loadEventStart: 1571078170393,
+ loadEventEnd: 1571078170394,
+ } as any;
+
+ spyExport = sinon
+ .stub(window.performance, 'getEntriesByType')
+ .returns([]);
+ Object.defineProperty(window.performance, 'timing', {
+ writable: true,
+ value: entries,
+ });
+ });
+
+ it('should export correct span with events', () => {
+ const spyOnEnd = sinon.spy(dummyExporter, 'export');
+ plugin.enable(moduleExports, tracer, logger, config);
+
+ const span1 = spyOnEnd.args[0][0][0] as ReadableSpan;
+ const span2 = spyOnEnd.args[1][0][0] as ReadableSpan;
+ const events1 = span1.events;
+ const events2 = span2.events;
+
+ assert.strictEqual(span1.name, 'documentFetch');
+ assert.strictEqual(span2.name, 'documentLoad');
+
+ assert.strictEqual(events1[0].name, PTN.FETCH_START);
+ assert.strictEqual(events1[1].name, PTN.DOMAIN_LOOKUP_START);
+ assert.strictEqual(events1[2].name, PTN.DOMAIN_LOOKUP_END);
+ assert.strictEqual(events1[3].name, PTN.CONNECT_START);
+ assert.strictEqual(events1[4].name, PTN.SECURE_CONNECTION_START);
+ assert.strictEqual(events1[5].name, PTN.CONNECT_END);
+ assert.strictEqual(events1[6].name, PTN.REQUEST_START);
+ assert.strictEqual(events1[7].name, PTN.RESPONSE_START);
+ assert.strictEqual(events1[8].name, PTN.RESPONSE_END);
+
+ assert.strictEqual(events2[0].name, PTN.FETCH_START);
+ assert.strictEqual(events2[1].name, PTN.DOM_INTERACTIVE);
+ assert.strictEqual(events2[2].name, PTN.DOM_CONTENT_LOADED_EVENT_START);
+ assert.strictEqual(events2[3].name, PTN.DOM_CONTENT_LOADED_EVENT_END);
+ assert.strictEqual(events2[4].name, PTN.DOM_COMPLETE);
+ assert.strictEqual(events2[5].name, PTN.LOAD_EVENT_START);
+ assert.strictEqual(events2[6].name, PTN.LOAD_EVENT_END);
+
+ assert.strictEqual(events1.length, 9);
+ assert.strictEqual(events2.length, 7);
+ assert.strictEqual(spyOnEnd.callCount, 2);
+ });
+
+ afterEach(() => {
+ spyExport.restore();
+ });
+ });
+
+ describe('when navigation entries types and "performance.timing" are NOT available', () => {
+ let spyExport: any;
+
+ beforeEach(() => {
+ spyExport = sinon
+ .stub(window.performance, 'getEntriesByType')
+ .returns([]);
+ Object.defineProperty(window.performance, 'timing', {
+ writable: true,
+ value: undefined,
+ });
+ });
+
+ it('should not create any span', () => {
+ const spyOnEnd = sinon.spy(dummyExporter, 'export');
+ plugin.enable(moduleExports, tracer, logger, config);
+
+ assert.ok(spyOnEnd.callCount === 0);
+ });
+
+ afterEach(() => {
+ spyExport.restore();
+ });
+ });
+});
diff --git a/packages/opentelemetry-plugin-document-load/test/index-webpack.ts b/packages/opentelemetry-plugin-document-load/test/index-webpack.ts
new file mode 100644
index 0000000000..3899f0edc9
--- /dev/null
+++ b/packages/opentelemetry-plugin-document-load/test/index-webpack.ts
@@ -0,0 +1,20 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file is the webpack entry point for the browser Karma tests. It requires
+// all modules ending in "test" from the current folder and all its subfolders.
+const testsContext = require.context('.', true, /test$/);
+testsContext.keys().forEach(testsContext);
diff --git a/packages/opentelemetry-plugin-document-load/tsconfig.json b/packages/opentelemetry-plugin-document-load/tsconfig.json
new file mode 100644
index 0000000000..a2042cd68b
--- /dev/null
+++ b/packages/opentelemetry-plugin-document-load/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../tsconfig.base",
+ "compilerOptions": {
+ "rootDir": ".",
+ "outDir": "build"
+ },
+ "include": [
+ "src/**/*.ts",
+ "test/**/*.ts"
+ ]
+}
diff --git a/packages/opentelemetry-plugin-document-load/tslint.json b/packages/opentelemetry-plugin-document-load/tslint.json
new file mode 100644
index 0000000000..0710b135d0
--- /dev/null
+++ b/packages/opentelemetry-plugin-document-load/tslint.json
@@ -0,0 +1,4 @@
+{
+ "rulesDirectory": ["node_modules/tslint-microsoft-contrib"],
+ "extends": ["../../tslint.base.js", "./node_modules/tslint-consistent-codestyle"]
+}
diff --git a/packages/opentelemetry-plugin-document-load/webpack/test.config.js b/packages/opentelemetry-plugin-document-load/webpack/test.config.js
new file mode 100644
index 0000000000..997cce7182
--- /dev/null
+++ b/packages/opentelemetry-plugin-document-load/webpack/test.config.js
@@ -0,0 +1,34 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const webpackNodePolyfills = require('../../../webpack.node-polyfills.js');
+
+// This is the webpack configuration for browser Karma tests.
+module.exports = {
+ mode: 'development',
+ target: 'web',
+ output: { filename: 'bundle.js' },
+ resolve: { extensions: ['.ts', '.js'] },
+ devtool: 'inline-source-map',
+ module: {
+ rules: [
+ { test: /\.ts$/, use: 'ts-loader' },
+ // This setting configures Node polyfills for the browser that will be
+ // added to the webpack bundle for Karma tests.
+ { parser: { node: webpackNodePolyfills } }
+ ]
+ }
+};
diff --git a/packages/opentelemetry-tracing/package.json b/packages/opentelemetry-tracing/package.json
index 291a5fac62..8ee6dcedfa 100644
--- a/packages/opentelemetry-tracing/package.json
+++ b/packages/opentelemetry-tracing/package.json
@@ -10,15 +10,16 @@
"types": "build/src/index.d.ts",
"repository": "open-telemetry/opentelemetry-js",
"scripts": {
- "test": "nyc ts-mocha -p tsconfig.json 'test/**/*.ts' --exclude 'test/index-webpack.ts'",
- "test:browser": "karma start --single-run",
- "tdd": "yarn test -- --watch-extensions ts --watch",
- "codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
- "clean": "rimraf build/*",
"check": "gts check",
+ "clean": "rimraf build/*",
+ "codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
"compile": "tsc -p .",
"fix": "gts fix",
- "prepare": "npm run compile"
+ "prepare": "npm run compile",
+ "tdd": "yarn test -- --watch-extensions ts --watch",
+ "test": "nyc ts-mocha -p tsconfig.json 'test/**/*.ts' --exclude 'test/index-webpack.ts'",
+ "test:browser": "karma start --single-run",
+ "watch": "tsc -w"
},
"keywords": [
"opentelemetry",
@@ -46,6 +47,7 @@
"devDependencies": {
"@types/mocha": "^5.2.5",
"@types/node": "^12.6.8",
+ "@types/sinon": "^7.0.13",
"@types/webpack-env": "1.13.9",
"codecov": "^3.1.0",
"gts": "^1.0.0",
@@ -57,6 +59,7 @@
"mocha": "^6.1.0",
"nyc": "^14.1.1",
"rimraf": "^3.0.0",
+ "sinon": "^7.5.0",
"tslint-consistent-codestyle": "^1.15.1",
"tslint-microsoft-contrib": "^6.2.0",
"ts-loader": "^6.0.4",
diff --git a/packages/opentelemetry-tracing/src/Span.ts b/packages/opentelemetry-tracing/src/Span.ts
index c59b96b460..c9e9152654 100644
--- a/packages/opentelemetry-tracing/src/Span.ts
+++ b/packages/opentelemetry-tracing/src/Span.ts
@@ -15,7 +15,12 @@
*/
import * as types from '@opentelemetry/types';
-import { hrTime, hrTimeDuration, timeInputToHrTime } from '@opentelemetry/core';
+import {
+ hrTime,
+ hrTimeDuration,
+ isTimeInput,
+ timeInputToHrTime,
+} from '@opentelemetry/core';
import { ReadableSpan } from './export/ReadableSpan';
import { BasicTracer } from './BasicTracer';
import { SpanProcessor } from './SpanProcessor';
@@ -95,13 +100,37 @@ export class Span implements types.Span, ReadableSpan {
return this;
}
- addEvent(name: string, attributes?: types.Attributes): this {
+ /**
+ *
+ * @param name Span Name
+ * @param [attributesOrStartTime] Span attributes or start time
+ * if type is {@type TimeInput} and 3rd param is undefined
+ * @param [startTime] Specified start time for the event
+ */
+ addEvent(
+ name: string,
+ attributesOrStartTime?: types.Attributes | types.TimeInput,
+ startTime?: types.TimeInput
+ ): this {
if (this._isSpanEnded()) return this;
if (this.events.length >= this._traceParams.numberOfEventsPerSpan!) {
this._logger.warn('Dropping extra events.');
this.events.shift();
}
- this.events.push({ name, attributes, time: hrTime() });
+ if (isTimeInput(attributesOrStartTime)) {
+ if (typeof startTime === 'undefined') {
+ startTime = attributesOrStartTime as types.TimeInput;
+ }
+ attributesOrStartTime = undefined;
+ }
+ if (typeof startTime === 'undefined') {
+ startTime = hrTime();
+ }
+ this.events.push({
+ name,
+ attributes: attributesOrStartTime as types.Attributes,
+ time: timeInputToHrTime(startTime),
+ });
return this;
}
diff --git a/packages/opentelemetry-tracing/src/export/ConsoleSpanExporter.ts b/packages/opentelemetry-tracing/src/export/ConsoleSpanExporter.ts
index 0a569a0541..7f5fc96804 100644
--- a/packages/opentelemetry-tracing/src/export/ConsoleSpanExporter.ts
+++ b/packages/opentelemetry-tracing/src/export/ConsoleSpanExporter.ts
@@ -17,28 +17,65 @@
import { SpanExporter } from './SpanExporter';
import { ReadableSpan } from './ReadableSpan';
import { ExportResult } from '@opentelemetry/base';
-import { hrTimeToMilliseconds } from '@opentelemetry/core';
+import { hrTimeToMicroseconds } from '@opentelemetry/core';
/**
* This is implementation of {@link SpanExporter} that prints spans to the
* console. This class can be used for diagnostic purposes.
*/
export class ConsoleSpanExporter implements SpanExporter {
+ /**
+ * Export spans.
+ * @param spans
+ * @param resultCallback
+ */
export(
spans: ReadableSpan[],
resultCallback: (result: ExportResult) => void
+ ): void {
+ return this._sendSpans(spans, resultCallback);
+ }
+
+ /**
+ * Shutdown the exporter.
+ */
+ shutdown(): void {
+ return this._sendSpans([]);
+ }
+
+ /**
+ * converts span info into more readable format
+ * @param span
+ */
+ private _exportInfo(span: ReadableSpan) {
+ return {
+ traceId: span.spanContext.traceId,
+ parentId: span.parentSpanId,
+ name: span.name,
+ id: span.spanContext.spanId,
+ kind: span.kind,
+ timestamp: hrTimeToMicroseconds(span.startTime),
+ duration: hrTimeToMicroseconds(span.duration),
+ attributes: span.attributes,
+ status: span.status,
+ events: span.events,
+ };
+ }
+
+ /**
+ * Showing spans in console
+ * @param spans
+ * @param done
+ */
+ private _sendSpans(
+ spans: ReadableSpan[],
+ done?: (result: ExportResult) => void
): void {
for (const span of spans) {
- console.log(
- `{name=${span.name}, traceId=${span.spanContext.traceId}, spanId=${
- span.spanContext.spanId
- }, kind=${span.kind}, parent=${
- span.parentSpanId
- }, duration=${hrTimeToMilliseconds(span.duration)}}}`
- );
+ console.log(this._exportInfo(span));
+ }
+ if (done) {
+ return done(ExportResult.SUCCESS);
}
- return resultCallback(ExportResult.SUCCESS);
}
-
- shutdown(): void {}
}
diff --git a/packages/opentelemetry-tracing/test/Span.test.ts b/packages/opentelemetry-tracing/test/Span.test.ts
index 45b9650876..11ef1c76ea 100644
--- a/packages/opentelemetry-tracing/test/Span.test.ts
+++ b/packages/opentelemetry-tracing/test/Span.test.ts
@@ -27,6 +27,7 @@ import {
hrTimeToNanoseconds,
hrTimeToMilliseconds,
NoopLogger,
+ hrTimeDuration,
} from '@opentelemetry/core';
const performanceTimeOrigin = hrTime();
@@ -86,6 +87,46 @@ describe('Span', () => {
);
});
+ it('should have an entered time for event', () => {
+ const span = new Span(
+ tracer,
+ name,
+ spanContext,
+ SpanKind.SERVER,
+ undefined,
+ 0
+ );
+ const timeMS = 123;
+ const spanStartTime = hrTimeToMilliseconds(span.startTime);
+ const eventTime = spanStartTime + timeMS;
+
+ span.addEvent('my-event', undefined, eventTime);
+
+ const diff = hrTimeDuration(span.startTime, span.events[0].time);
+ assert.strictEqual(hrTimeToMilliseconds(diff), 123);
+ });
+
+ describe('when 2nd param is "TimeInput" type', () => {
+ it('should have an entered time for event - ', () => {
+ const span = new Span(
+ tracer,
+ name,
+ spanContext,
+ SpanKind.SERVER,
+ undefined,
+ 0
+ );
+ const timeMS = 123;
+ const spanStartTime = hrTimeToMilliseconds(span.startTime);
+ const eventTime = spanStartTime + timeMS;
+
+ span.addEvent('my-event', eventTime);
+
+ const diff = hrTimeDuration(span.startTime, span.events[0].time);
+ assert.strictEqual(hrTimeToMilliseconds(diff), 123);
+ });
+ });
+
it('should get the span context of span', () => {
const span = new Span(tracer, name, spanContext, SpanKind.CLIENT);
const context = span.context();
diff --git a/packages/opentelemetry-tracing/test/export/ConsoleSpanExporter.test.ts b/packages/opentelemetry-tracing/test/export/ConsoleSpanExporter.test.ts
new file mode 100644
index 0000000000..95691ea0e0
--- /dev/null
+++ b/packages/opentelemetry-tracing/test/export/ConsoleSpanExporter.test.ts
@@ -0,0 +1,85 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as assert from 'assert';
+import * as sinon from 'sinon';
+import {
+ BasicTracer,
+ ConsoleSpanExporter,
+ SimpleSpanProcessor,
+} from '../../src';
+
+describe('ConsoleSpanExporter', () => {
+ let consoleExporter: ConsoleSpanExporter;
+ let previousConsoleLog: any;
+
+ beforeEach(() => {
+ previousConsoleLog = console.log;
+ console.log = () => {};
+ consoleExporter = new ConsoleSpanExporter();
+ });
+
+ afterEach(() => {
+ console.log = previousConsoleLog;
+ });
+
+ describe('.export()', () => {
+ it('should export information about span', () => {
+ assert.doesNotThrow(() => {
+ const basicTracer = new BasicTracer();
+ consoleExporter = new ConsoleSpanExporter();
+
+ const spyConsole = sinon.spy(console, 'log');
+ const spyExport = sinon.spy(consoleExporter, 'export');
+
+ basicTracer.addSpanProcessor(new SimpleSpanProcessor(consoleExporter));
+
+ const span = basicTracer.startSpan('foo');
+ span.addEvent('foobar');
+ span.end();
+
+ const spans = spyExport.args[0];
+ const firstSpan = spans[0][0];
+ const firstEvent = firstSpan.events[0];
+ const consoleArgs = spyConsole.args[0];
+ const consoleSpan = consoleArgs[0];
+ const keys = Object.keys(consoleSpan)
+ .sort()
+ .join(',');
+
+ const expectedKeys = [
+ 'attributes',
+ 'duration',
+ 'events',
+ 'id',
+ 'kind',
+ 'name',
+ 'parentId',
+ 'status',
+ 'timestamp',
+ 'traceId',
+ ].join(',');
+
+ assert.ok(firstSpan.name === 'foo');
+ assert.ok(firstEvent.name === 'foobar');
+ assert.ok(consoleSpan.id === firstSpan.spanContext.spanId);
+ assert.ok(keys === expectedKeys);
+
+ assert.ok(spyExport.calledOnce);
+ });
+ });
+ });
+});
diff --git a/packages/opentelemetry-types/src/trace/span.ts b/packages/opentelemetry-types/src/trace/span.ts
index fd0e4cf12a..0a88a97895 100644
--- a/packages/opentelemetry-types/src/trace/span.ts
+++ b/packages/opentelemetry-types/src/trace/span.ts
@@ -53,10 +53,16 @@ export interface Span {
* Adds an event to the Span.
*
* @param name the name of the event.
- * @param [attributes] the attributes that will be added; these are
- * associated with this event.
+ * @param [attributesOrStartTime] the attributes that will be added; these are
+ * associated with this event. Can be also a start time
+ * if type is {@type TimeInput} and 3rd param is undefined
+ * @param [startTime] start time of the event.
*/
- addEvent(name: string, attributes?: Attributes): this;
+ addEvent(
+ name: string,
+ attributesOrStartTime?: Attributes | TimeInput,
+ startTime?: TimeInput
+ ): this;
/**
* Adds a link to the Span.
diff --git a/packages/opentelemetry-web/README.md b/packages/opentelemetry-web/README.md
index 56f7d30332..20117c68fc 100644
--- a/packages/opentelemetry-web/README.md
+++ b/packages/opentelemetry-web/README.md
@@ -11,7 +11,19 @@ For manual instrumentation see the
[@opentelemetry/web](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-web) package.
## How does automatic tracing work?
-> Automatic Instrumentation is in progress, manual instrumentation only supported
+```js
+import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/tracing';
+import { WebTracer } from '@opentelemetry/web';
+import { DocumentLoad } from '@opentelemetry/plugin-document-load';
+
+const webTracer = new WebTracer({
+ plugins: [
+ new DocumentLoad()
+ ]
+});
+
+webTracer.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
+```
## Installation
@@ -27,7 +39,6 @@ const { WebTracer } = require('@opentelemetry/web');
const webTracer = new WebTracer();
const span = webTracer.startSpan('span1');
webTracer.withSpan(span, function () {
- // this === span
this.addEvent('start');
});
span.addEvent('middle');
diff --git a/packages/opentelemetry-web/package.json b/packages/opentelemetry-web/package.json
index e60995d2e2..a93bed101b 100644
--- a/packages/opentelemetry-web/package.json
+++ b/packages/opentelemetry-web/package.json
@@ -6,14 +6,15 @@
"types": "build/src/index.d.ts",
"repository": "open-telemetry/opentelemetry-js",
"scripts": {
- "test:browser": "nyc karma start --single-run",
- "tdd": "karma start",
- "codecov:browser": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
- "clean": "rimraf build/*",
"check": "gts check",
+ "clean": "rimraf build/*",
+ "codecov:browser": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
"compile": "tsc -p .",
"fix": "gts fix",
- "prepare": "npm run compile"
+ "prepare": "npm run compile",
+ "tdd": "karma start",
+ "test:browser": "nyc karma start --single-run",
+ "watch": "tsc -w"
},
"keywords": [
"opentelemetry",
diff --git a/packages/opentelemetry-web/src/StackScopeManager.ts b/packages/opentelemetry-web/src/StackScopeManager.ts
index 9f9e5f9f7a..c6dc866c20 100644
--- a/packages/opentelemetry-web/src/StackScopeManager.ts
+++ b/packages/opentelemetry-web/src/StackScopeManager.ts
@@ -35,7 +35,6 @@ export class StackScopeManager implements ScopeManager {
*
* @param target Function to be executed within the scope
* @param scope
- * @private
*/
private _bindFunction(target: T, scope?: unknown): T {
const manager = this;
diff --git a/packages/opentelemetry-web/src/WebTracer.ts b/packages/opentelemetry-web/src/WebTracer.ts
index ece595195a..5d3fad465e 100644
--- a/packages/opentelemetry-web/src/WebTracer.ts
+++ b/packages/opentelemetry-web/src/WebTracer.ts
@@ -14,25 +14,41 @@
* limitations under the License.
*/
+import { BasePlugin } from '@opentelemetry/core';
import { BasicTracer, BasicTracerConfig } from '@opentelemetry/tracing';
import { StackScopeManager } from './StackScopeManager';
+/**
+ * WebTracerConfig provides an interface for configuring a Web Tracer.
+ */
+export interface WebTracerConfig extends BasicTracerConfig {
+ /**
+ * plugins to be used with tracer, they will be enabled automatically
+ */
+ plugins?: BasePlugin[];
+}
+
/**
* This class represents a web tracer with {@link StackScopeManager}
*/
export class WebTracer extends BasicTracer {
/**
* Constructs a new Tracer instance.
+ * @param config Web Tracer config
*/
- /**
- *
- * @param {BasicTracerConfig} config Web Tracer config
- */
- constructor(config: BasicTracerConfig = {}) {
+ constructor(config: WebTracerConfig = {}) {
if (typeof config.scopeManager === 'undefined') {
config.scopeManager = new StackScopeManager();
}
- config.scopeManager.enable();
+ if (typeof config.plugins === 'undefined') {
+ config.plugins = [];
+ }
super(Object.assign({}, { scopeManager: config.scopeManager }, config));
+
+ config.scopeManager.enable();
+
+ for (const plugin of config.plugins) {
+ plugin.enable([], this, this.logger, {});
+ }
}
}
diff --git a/packages/opentelemetry-web/test/WebTracer.test.ts b/packages/opentelemetry-web/test/WebTracer.test.ts
index 621aae2977..e1474508ea 100644
--- a/packages/opentelemetry-web/test/WebTracer.test.ts
+++ b/packages/opentelemetry-web/test/WebTracer.test.ts
@@ -14,16 +14,24 @@
* limitations under the License.
*/
+import { BasePlugin } from '@opentelemetry/core';
import * as assert from 'assert';
import * as sinon from 'sinon';
import { BasicTracerConfig } from '@opentelemetry/tracing';
+import { WebTracerConfig } from '../src';
import { StackScopeManager } from '../src/StackScopeManager';
import { WebTracer } from '../src/WebTracer';
+class DummyPlugin extends BasePlugin {
+ patch() {}
+
+ unpatch() {}
+}
+
describe('WebTracer', () => {
let tracer: WebTracer;
describe('constructor', () => {
- let defaultOptions: BasicTracerConfig;
+ let defaultOptions: WebTracerConfig;
beforeEach(() => {
defaultOptions = {
@@ -47,6 +55,24 @@ describe('WebTracer', () => {
assert.ok(spy.calledOnce === true);
});
+ it('should enable all plugins', () => {
+ let options: WebTracerConfig;
+ const dummyPlugin1 = new DummyPlugin();
+ const dummyPlugin2 = new DummyPlugin();
+ const spyEnable1 = sinon.spy(dummyPlugin1, 'enable');
+ const spyEnable2 = sinon.spy(dummyPlugin2, 'enable');
+
+ const scopeManager = new StackScopeManager();
+
+ const plugins = [dummyPlugin1, dummyPlugin2];
+
+ options = { plugins, scopeManager };
+ tracer = new WebTracer(options);
+
+ assert.ok(spyEnable1.calledOnce === true);
+ assert.ok(spyEnable2.calledOnce === true);
+ });
+
it('should work without default scope manager', () => {
assert.doesNotThrow(() => {
tracer = new WebTracer({});