From 280dfc383c9fa5e05ca5e4540e069c22a848d709 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Fri, 25 Oct 2019 14:43:36 +0200 Subject: [PATCH 01/17] feat(scope-zone): new scope manager to support async operations in web --- README.md | 4 + examples/tracer-web/index.html | 5 + examples/tracer-web/index.js | 82 ++- examples/tracer-web/package.json | 1 + examples/tracer-web/webpack.config.js | 10 - .../opentelemetry-scope-zone-peer-dep/LICENSE | 201 +++++++ .../README.md | 69 +++ .../karma.conf.js | 24 + .../package.json | 79 +++ .../src/ZoneScopeManager.ts | 511 ++++++++++++++++++ .../src/index.ts | 19 + .../src/types.ts | 37 ++ .../src/util.ts | 24 + .../test/ZoneScopeManager.test.ts | 271 ++++++++++ .../test/index-webpack.ts | 21 + .../tsconfig.json | 12 + .../tslint.json | 4 + .../webpack/test.config.js | 34 ++ packages/opentelemetry-scope-zone/LICENSE | 201 +++++++ packages/opentelemetry-scope-zone/README.md | 69 +++ .../opentelemetry-scope-zone/package.json | 73 +++ .../opentelemetry-scope-zone/src/index.ts | 18 + .../opentelemetry-scope-zone/tsconfig.json | 12 + packages/opentelemetry-scope-zone/tslint.json | 4 + packages/opentelemetry-web/README.md | 49 +- 25 files changed, 1804 insertions(+), 30 deletions(-) create mode 100644 packages/opentelemetry-scope-zone-peer-dep/LICENSE create mode 100644 packages/opentelemetry-scope-zone-peer-dep/README.md create mode 100644 packages/opentelemetry-scope-zone-peer-dep/karma.conf.js create mode 100644 packages/opentelemetry-scope-zone-peer-dep/package.json create mode 100644 packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts create mode 100644 packages/opentelemetry-scope-zone-peer-dep/src/index.ts create mode 100644 packages/opentelemetry-scope-zone-peer-dep/src/types.ts create mode 100644 packages/opentelemetry-scope-zone-peer-dep/src/util.ts create mode 100644 packages/opentelemetry-scope-zone-peer-dep/test/ZoneScopeManager.test.ts create mode 100644 packages/opentelemetry-scope-zone-peer-dep/test/index-webpack.ts create mode 100644 packages/opentelemetry-scope-zone-peer-dep/tsconfig.json create mode 100644 packages/opentelemetry-scope-zone-peer-dep/tslint.json create mode 100644 packages/opentelemetry-scope-zone-peer-dep/webpack/test.config.js create mode 100644 packages/opentelemetry-scope-zone/LICENSE create mode 100644 packages/opentelemetry-scope-zone/README.md create mode 100644 packages/opentelemetry-scope-zone/package.json create mode 100644 packages/opentelemetry-scope-zone/src/index.ts create mode 100644 packages/opentelemetry-scope-zone/tsconfig.json create mode 100644 packages/opentelemetry-scope-zone/tslint.json diff --git a/README.md b/README.md index e798d11f37f..7b28ab60b60 100644 --- a/README.md +++ b/README.md @@ -95,11 +95,15 @@ OpenTelemetry is vendor-agnostic and can upload data to any backend with various OpenTelemetry can collect tracing data automatically using plugins. Vendors/Users can also create and use their own. Currently, OpenTelemetry supports automatic tracing for: +#### Node Plugins - [@opentelemetry/plugin-http](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-http) - [@opentelemetry/plugin-grpc](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-grpc) - [@opentelemetry/plugin-https](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-https) - [@opentelemetry/plugin-postgres](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-postgres) - WIP +#### Web Plugins +- [@opentelemetry/plugin-document-load](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-document-load) + To request automatic tracing support for a module not on this list, please [file an issue](https://github.com/open-telemetry/opentelemetry-js/issues). Alternatively, you can [write a plugin yourself](https://github.com/open-telemetry/opentelemetry-js/blob/master/doc/plugin-guide.md). ### Shims diff --git a/examples/tracer-web/index.html b/examples/tracer-web/index.html index 08bab8e1b0c..522a8c2443b 100644 --- a/examples/tracer-web/index.html +++ b/examples/tracer-web/index.html @@ -12,6 +12,11 @@ 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 a8e3e34b413..d3c99e7d07a 100644 --- a/examples/tracer-web/index.js +++ b/examples/tracer-web/index.js @@ -1,11 +1,91 @@ import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/tracing'; import { WebTracer } from '@opentelemetry/web'; import { DocumentLoad } from '@opentelemetry/plugin-document-load'; +import { ZoneScopeManager } from '@opentelemetry/scope-zone'; const webTracer = new WebTracer({ plugins: [ new DocumentLoad() ] }); - webTracer.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); + +const webTracerWithZone = new WebTracer({ + scopeManager: new ZoneScopeManager(), + plugins: [ + new DocumentLoad() + ] +}); +webTracerWithZone.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); + +console.log('Current span is window', webTracerWithZone.getCurrentSpan() === window); + +const span1 = webTracerWithZone.startSpan('foo1'); +webTracerWithZone.withSpan(span1, () => { + console.log('Current span is span1', webTracerWithZone.getCurrentSpan() === span1); + + setTimeout(() => { + const span2 = webTracerWithZone.startSpan('foo2'); + console.log('Current span is span1', webTracerWithZone.getCurrentSpan() === span1); + webTracerWithZone.withSpan(span2, () => { + console.log('Current span is span2', webTracerWithZone.getCurrentSpan() === span2); + setTimeout(() => { + console.log('Current span is span2', webTracerWithZone.getCurrentSpan() === span2); + }, 500); + }); + // there is a timeout which still keeps span2 active + console.log('Current span is span2', webTracerWithZone.getCurrentSpan() === span2); + }, 500); + console.log('Current span is span1', webTracerWithZone.getCurrentSpan() === span1); +}); + +const span3 = webTracerWithZone.startSpan('foo1'); +webTracerWithZone.withSpan(span3, () => { + console.log('Current span is span3', webTracerWithZone.getCurrentSpan() === span3); + setTimeout(() => { + const span4 = webTracerWithZone.startSpan('foo2'); + console.log('Current span is span3', webTracerWithZone.getCurrentSpan() === span3); + webTracerWithZone.withSpan(span4, () => { + console.log('Current span is span4', webTracerWithZone.getCurrentSpan() === span4); + setTimeout(() => { + console.log('Current span is span4', webTracerWithZone.getCurrentSpan() === span4); + }, 500); + }); + // there is a timeout which still keeps span4 active + console.log('Current span is span4', webTracerWithZone.getCurrentSpan() === span4); + }, 490); + console.log('Current span is span3', webTracerWithZone.getCurrentSpan() === span3); +}); + +console.log('Current span is window', webTracerWithZone.getCurrentSpan() === window); + +// example of keeping track of scope between async operations +const prepareClickEvent = () => { + const element = document.getElementById('button2'); + let mainSpan = webTracerWithZone.startSpan('main-span'); + webTracerWithZone.bind(element, mainSpan); + + const onClick = () => { + element.removeEventListener('click', onClick); + console.log('starting timeout, scope is', webTracerWithZone.getCurrentSpan().name); + const spanClickAsync = webTracerWithZone.startSpan('span-click-timeout', { + parent: webTracerWithZone.getCurrentSpan() + }); + spanClickAsync.addEvent('starting timeout'); + + window.setTimeout(() => { + console.log('timeout finished, scope is', webTracerWithZone.getCurrentSpan().name); + spanClickAsync.addEvent('timeout finished'); + console.log('ending spans'); + spanClickAsync.end(); + mainSpan.end(); + }, 500); + }; + element.addEventListener('click', onClick); + +}; + +window.addEventListener('load', () => { + const element = document.getElementById('button1'); + element.addEventListener('click', prepareClickEvent); +}); diff --git a/examples/tracer-web/package.json b/examples/tracer-web/package.json index 4ee8da499e7..1753a79fcc3 100644 --- a/examples/tracer-web/package.json +++ b/examples/tracer-web/package.json @@ -35,6 +35,7 @@ }, "dependencies": { "@opentelemetry/plugin-document-load": "^0.1.1", + "@opentelemetry/scope-zone": "^0.1.1", "@opentelemetry/tracing": "^0.1.1", "@opentelemetry/web": "^0.1.1" }, diff --git a/examples/tracer-web/webpack.config.js b/examples/tracer-web/webpack.config.js index 69ad1b74f29..0c4efe02b24 100644 --- a/examples/tracer-web/webpack.config.js +++ b/examples/tracer-web/webpack.config.js @@ -26,14 +26,6 @@ const common = { } ] }, - plugins: [ - new webpack.ProvidePlugin({ - jQuery: 'jquery', - $: 'jquery', - jquery: 'jquery', - 'window.jQuery': 'jquery' - }) - ], resolve: { modules: [ path.resolve(mainPath, 'src'), @@ -52,8 +44,6 @@ module.exports = webpackMerge(common, { }, devServer: { contentBase: path.resolve(__dirname), - // contentBase: path.resolve('.'), - // historyApiFallback: true }, plugins: [ new webpack.DefinePlugin({ diff --git a/packages/opentelemetry-scope-zone-peer-dep/LICENSE b/packages/opentelemetry-scope-zone-peer-dep/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/packages/opentelemetry-scope-zone-peer-dep/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-scope-zone-peer-dep/README.md b/packages/opentelemetry-scope-zone-peer-dep/README.md new file mode 100644 index 00000000000..9ecd898e013 --- /dev/null +++ b/packages/opentelemetry-scope-zone-peer-dep/README.md @@ -0,0 +1,69 @@ +# OpenTelemetry Scope Zone Peer Dependency +[![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 *Zone Scope Manager with a peer dependency for [zone-js]* for Web applications. +If you don't have your own [zone-js] please use *@opentelemetry/scope-zone* + + +## Installation + +```bash +npm install --save @opentelemetry/scope-zone-peer-dep +``` + +## Usage +```js +import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/tracing'; +import { WebTracer } from '@opentelemetry/web'; +import { ZoneScopeManager } from '@opentelemetry/scope-zone-peer-dep'; + +const webTracerWithZone = new WebTracer({ + scopeManager: new ZoneScopeManager() +}); +webTracerWithZone.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); + +// Example how the ZoneScopeManager keeps the reference to the correct scope during async operations +const span1 = webTracerWithZone.startSpan('foo1'); +webTracerWithZone.withSpan(span1, () => { + console.log('Current span is span1', webTracerWithZone.getCurrentSpan() === span1); + setTimeout(() => { + const span2 = webTracerWithZone.startSpan('foo2'); + console.log('Current span is span1', webTracerWithZone.getCurrentSpan() === span1); + webTracerWithZone.withSpan(span2, () => { + console.log('Current span is span2', webTracerWithZone.getCurrentSpan() === span2); + setTimeout(() => { + console.log('Current span is span2', webTracerWithZone.getCurrentSpan() === span2); + }, 500); + }); + // there is a timeout which still keeps span2 active + console.log('Current span is span2', webTracerWithZone.getCurrentSpan() === span2); + }, 500); + console.log('Current span is span1', webTracerWithZone.getCurrentSpan() === span1); +}); + +``` + +## 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-scope-zone-peer-dep +[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-web +[devDependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js/dev-status.svg?path=packages/opentelemetry-scope-zone-peer-dep +[devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-web&type=dev +[npm-url]: https://www.npmjs.com/package/@opentelemetry/scope-zone-peer-dep +[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fscope-zone-peer-dep.svg +[zone-js]: https://www.npmjs.com/package/zone.js diff --git a/packages/opentelemetry-scope-zone-peer-dep/karma.conf.js b/packages/opentelemetry-scope-zone-peer-dep/karma.conf.js new file mode 100644 index 00000000000..66529c7d920 --- /dev/null +++ b/packages/opentelemetry-scope-zone-peer-dep/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-scope-zone-peer-dep/package.json b/packages/opentelemetry-scope-zone-peer-dep/package.json new file mode 100644 index 00000000000..800b15b6548 --- /dev/null +++ b/packages/opentelemetry-scope-zone-peer-dep/package.json @@ -0,0 +1,79 @@ +{ + "name": "@opentelemetry/scope-zone-peer-dep", + "version": "0.1.1", + "description": "OpenTelemetry Scope Zone with peer dependency for zone.js", + "main": "build/src/index.js", + "types": "build/src/index.d.ts", + "repository": "open-telemetry/opentelemetry-js", + "scripts": { + "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", + "tdd": "karma start", + "test:browser": "nyc karma start --single-run", + "watch": "tsc -w" + }, + "keywords": [ + "opentelemetry", + "web", + "tracing", + "profiling", + "metrics", + "stats" + ], + "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/zone.js": "^0.5.12", + "@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", + "zone.js": "^0.10.2", + "@opentelemetry/tracing": "^0.1.1" + }, + "dependencies": { + "@opentelemetry/scope-base": "^0.1.1" + }, + "peerDependencies": { + "zone.js": "^0.10.2" + }, + "sideEffects": false +} diff --git a/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts b/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts new file mode 100644 index 00000000000..23db8069515 --- /dev/null +++ b/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts @@ -0,0 +1,511 @@ +/*! + * 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 { ScopeManager } from '@opentelemetry/scope-base'; +import { Func, ScopeWithEventTargets, TargetWithEvents } from './types'; +import { isListenerObject } from './util'; + +/** + * Zone Scope Manager for managing the state in web + * it fully supports the async calls + */ +export class ZoneScopeManager implements ScopeManager { + /** + * whether the scope manager is enabled or not + */ + private _enabled = false; + + /** + * Keeps the the information whether to prevent restoring the scope after zone onInvoke + */ + private _preventScopeRestoring: { [key: string]: boolean } = {}; + + /** + * Keeps the reference to the scopes per zone + */ + private _scopes: { [key: string]: unknown[] } = {}; + + /** + * Keeps the reference to the scopes with original event targets + */ + private _scopesWithEventTargets: ScopeWithEventTargets[] = []; + + /** + * Helps to create a unique name for the zones - part of zone name + */ + private _zoneCounter = 0; + + constructor() {} + + /** + * Returns the active scope from certain zone name + * @param activeZoneName + */ + private _activeScopeFromZoneName( + activeZoneName: string | undefined + ): unknown | undefined { + if (activeZoneName) { + const scopes = this._scopes[activeZoneName]; + if (scopes) { + return scopes[scopes.length - 1]; + } + } + return undefined; + } + + /** + * @param target Function to be executed within the scope + * @param scope + */ + private _bindFunction(target: T, scope?: unknown): T { + const manager = this; + const contextWrapper = function(this: any, ...args: unknown[]) { + return manager.with(scope, () => target.apply(this || scope, args)); + }; + Object.defineProperty(contextWrapper, 'length', { + enumerable: false, + configurable: true, + writable: false, + value: target.length, + }); + return (contextWrapper as unknown) as T; + } + + /** + * @param obj target object on which the listeners will be patched + * @param scope + */ + private _bindListener(obj: T, scope?: unknown): T { + const target = (obj as unknown) as TargetWithEvents; + if (target.__ot_listeners !== undefined) { + return obj; + } + target.__ot_listeners = {}; + + if (typeof target.addEventListener === 'function') { + target.addEventListener = this._patchAddEventListener( + target, + target.addEventListener, + scope + ); + } + + if (typeof target.removeEventListener === 'function') { + target.removeEventListener = this._patchRemoveEventListener( + target, + target.removeEventListener, + scope + ); + } + + return obj; + } + + /** + * Removes references to the targets that were bind to certain scope / span + * @param scope + */ + private _cleanEventTargetsFromScope(scope: any, target?: TargetWithEvents) { + // console.log('_cleanEventTargetsFromScope1', this._scopesWithEventTargets.length, scope && scope.name); + for (let i = this._scopesWithEventTargets.length - 1; i >= 0; i--) { + const scopeWithEventTargets = this._scopesWithEventTargets[i]; + if (scopeWithEventTargets.scope === scope) { + if ( + typeof target !== 'undefined' && + scopeWithEventTargets.target !== target + ) { + continue; + } + + if (scopeWithEventTargets.target) { + try { + scopeWithEventTargets.target.addEventListener = + scopeWithEventTargets.eventTargets.addEventListener; + + scopeWithEventTargets.target.removeEventListener = + scopeWithEventTargets.eventTargets.removeEventListener; + + delete scopeWithEventTargets.target.__ot_listeners; + } catch (e) { + console.log(e); + } + } + + this._scopesWithEventTargets.splice(i, 1); + + if ( + typeof target !== 'undefined' && + scopeWithEventTargets.target === target + ) { + break; + } + } + } + // console.log('_cleanEventTargetsFromScope2', this._scopesWithEventTargets.length); + } + + /** + * Removes all the associated scopes with zone after the zone has no more tasks + * @param zoneName + */ + private _cleanScopesFromZone(zoneName: string) { + const scopes = this._scopes[zoneName]; + for (let i = scopes.length - 1; i >= 0; i--) { + this._cleanEventTargetsFromScope(scopes[i]); + scopes.splice(i, 1); + } + delete this._scopes[zoneName]; + } + + /** + * Creates a new unique zone name + */ + private _createZoneName() { + this._zoneCounter++; + const random = Math.random(); + const zoneName = `${this._zoneCounter}-${random}`; + return zoneName; + } + + /** + * Creates a new zone + * @param zoneName zone name + */ + private _createZone(zoneName: string): Zone { + return Zone.root.fork({ + name: zoneName, + onInvoke: this._onZoneInvoke.bind(this), + onHasTask: this._onHasTask.bind(this), + }); + } + + /** + * Ensure that appropriate {@link ScopeWithEventTargets} exists if not then create a new one + * @param scope + * @param target + */ + private _ensureScopeWithEventTargetsExists( + scope: unknown, + target: TargetWithEvents + ): ScopeWithEventTargets { + let foundScope; + for (const scopeWithEventTargets of this._scopesWithEventTargets) { + if ( + scopeWithEventTargets.scope === scope && + scopeWithEventTargets.target === target + ) { + foundScope = scopeWithEventTargets; + break; + } + } + if (!foundScope) { + this._scopesWithEventTargets.push({ + scope, + target, + eventTargets: {}, + }); + foundScope = this._scopesWithEventTargets[ + this._scopesWithEventTargets.length - 1 + ]; + } + return foundScope; + } + + /** + * Returns the active zone + */ + private _getActiveZone(): Zone | undefined { + const currentZone = Zone.current; + if (currentZone) { + if ( + this._scopes[currentZone.name] && + this._scopes[currentZone.name].length + ) { + return currentZone; + } + } + return undefined; + } + + /** + * Returns the active zone name if such zone has any scope + */ + private _getActiveZoneName(): string | undefined { + const activeZone = this._getActiveZone(); + if (activeZone) { + return activeZone.name; + } + return undefined; + } + + /** + * Called when zone executes any async task or after the last async task was executed + * @param parentZoneDelegate + * @param currentZone + * @param targetZone + * @param hasTaskState + */ + private _onHasTask( + parentZoneDelegate: ZoneDelegate, + currentZone: Zone, + targetZone: Zone, + hasTaskState: HasTaskState + ) { + // console.log('onHasTask', hasTaskState, this._preventScopeRestoring[currentZone.name]); + if ( + !hasTaskState.eventTask && + !hasTaskState.microTask && + !hasTaskState.macroTask + ) { + // no more tasks can clean up zone data + this._cleanScopesFromZone(currentZone.name); + } else { + this._preventScopeRestoring[currentZone.name] = true; + } + } + + /** + * Called when zone invokes a delegate (callback) function + * @param parentZoneDelegate + * @param currentZone + * @param targetZone + * @param delegate + * @param applyThis + * @param applyArgs + * @param source + */ + private _onZoneInvoke( + parentZoneDelegate: ZoneDelegate, + currentZone: Zone, + targetZone: Zone, + delegate: Function, + applyThis: any, + applyArgs?: any[], + source?: string + ) { + // console.log('onInvoke before', typeof applyThis === 'string' ? applyThis : applyThis && applyThis.name || 'window'); + const result = parentZoneDelegate.invoke( + targetZone, + delegate, + applyThis, + applyArgs, + source + ); + // console.log('onInvoke after', typeof applyThis === 'string' ? applyThis : applyThis && applyThis.name || 'window', this._preventScopeRestoring[currentZone.name]); + // if zone has asynchronous task then restoring scope cannot be done immediately + // scopes will be cleaned later in _onHasTask - when there are no more tasks + if (!this._preventScopeRestoring[currentZone.name]) { + this._restorePreviousScope(currentZone); + } + return result; + } + + /** + * Patches addEventListener method + * @param target any target that has "addEventListener" method + * @param original reference to the patched method + * @param [scope] scope to be bind to the listener + */ + private _patchAddEventListener( + target: TargetWithEvents, + original: Function, + scope?: unknown + ) { + const scopeManager = this; + this._saveOriginalAddEventWithScope(scope, target, original); + + return function(this: {}, event: string, listener: Func) { + if (target.__ot_listeners === undefined) { + target.__ot_listeners = {}; + } + let listeners = target.__ot_listeners[event]; + if (listeners === undefined) { + listeners = new WeakMap(); + target.__ot_listeners[event] = listeners; + } + const patchedListener = scopeManager.bind(listener, scope); + // store a weak reference of the user listener to ours + listeners.set(listener, patchedListener); + return original.call(this, event, patchedListener); + }; + } + + /** + * Patches removeEventListener method + * @param target any target that has "removeEventListener" method + * @param original reference to the patched method + * @param [scope] scope to be bind to the listener + */ + private _patchRemoveEventListener( + target: TargetWithEvents, + original: Function, + scope?: unknown + ) { + const scopeManager = this; + this._saveOriginalRemoveEventWithScope(scope, target, original); + return function(this: {}, event: string, listener: Func) { + if ( + target.__ot_listeners === undefined || + target.__ot_listeners[event] === undefined + ) { + return original.call(this, event, listener); + } + const events = target.__ot_listeners[event]; + const patchedListener = events.get(listener); + scopeManager._cleanEventTargetsFromScope(scope, target); + return original.call(this, event, patchedListener || listener); + }; + } + + /** + * Restores previous scope for certain zone + * @param zone + */ + private _restorePreviousScope(zone: Zone) { + const zoneName = zone.name; + const scopes = this._scopes[zoneName]; + if (scopes && scopes.length > 0) { + this._cleanEventTargetsFromScope(scopes[scopes.length - 1]); + scopes.splice(scopes.length - 1, 1); + } + } + + /** + * Saves original addEventListener method so it can be restored later + * @param scope + * @param target + * @param original original addEventListener method + */ + private _saveOriginalAddEventWithScope( + scope: unknown, + target: TargetWithEvents, + original: any + ) { + const foundScope = this._ensureScopeWithEventTargetsExists(scope, target); + if (foundScope) { + foundScope.eventTargets.addEventListener = original; + } + } + + /** + * Saves original removeEventListener method so it can be restored later + * @param scope + * @param target + * @param original original removeEventListener method + */ + private _saveOriginalRemoveEventWithScope( + scope: unknown, + target: TargetWithEvents, + original: any + ) { + const foundScope = this._ensureScopeWithEventTargetsExists(scope, target); + if (foundScope) { + foundScope.eventTargets.removeEventListener = original; + } + } + + /** + * Returns the active scope + */ + active(): unknown | undefined { + const activeZoneName = this._getActiveZoneName(); + + const active = this._activeScopeFromZoneName(activeZoneName); + if (active) { + return active; + } + if (this._enabled) { + return window; + } + return undefined; + } + + /** + * Binds a the certain scope or the active one to the target function and then returns the target + * @param target + * @param scope + */ + bind(target: T | TargetWithEvents, scope?: unknown): T { + // if no specific scope to propagate is given, we use the current one + if (scope === undefined) { + scope = this.active(); + } + if (typeof target === 'function') { + return this._bindFunction(target, scope); + } else if (isListenerObject(target)) { + this._bindListener(target, scope); + } + return (target as unknown) as T; + } + + /** + * Disable the scope manager (clears all the scopes) + */ + disable(): this { + const zoneNames = Object.keys(this._scopes); + for (const zoneName of zoneNames) { + this._cleanScopesFromZone(zoneName); + } + this._scopes = {}; + this._enabled = false; + return this; + } + + /** + * Enables the scope manager and creates a default(root) scope + */ + enable(): this { + if (this._enabled) { + return this; + } + this._enabled = true; + return this; + } + + /** + * Calls the callback function [fn] with the provided [scope]. + * If [scope] is undefined then it will use the active scope. + * The scope will be set as active + * @param scope + * @param fn Callback function + */ + with ReturnType>( + scope: unknown, + fn: () => ReturnType + ): ReturnType { + // if no scope use active from active zone + if (typeof scope === 'undefined' || scope === null) { + scope = this.active(); + } + + let zoneName: string; + let activeZone = this._getActiveZone(); + if (!activeZone) { + zoneName = this._createZoneName(); + } else { + zoneName = activeZone.name; + } + + this._scopes[zoneName] = this._scopes[zoneName] || []; + this._scopes[zoneName].push(scope); + + if (!activeZone) { + activeZone = this._createZone(zoneName); + } + + return activeZone.run(fn, scope); + } +} diff --git a/packages/opentelemetry-scope-zone-peer-dep/src/index.ts b/packages/opentelemetry-scope-zone-peer-dep/src/index.ts new file mode 100644 index 00000000000..9673589ce51 --- /dev/null +++ b/packages/opentelemetry-scope-zone-peer-dep/src/index.ts @@ -0,0 +1,19 @@ +/*! + * 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 './ZoneScopeManager'; +export * from './types'; +export * from './util'; diff --git a/packages/opentelemetry-scope-zone-peer-dep/src/types.ts b/packages/opentelemetry-scope-zone-peer-dep/src/types.ts new file mode 100644 index 00000000000..79dad4f3fe8 --- /dev/null +++ b/packages/opentelemetry-scope-zone-peer-dep/src/types.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 type Func = (...args: unknown[]) => T; + +export interface ScopeWithEventTargets { + scope: unknown; + target: TargetWithEvents; + eventTargets: TargetWithEvents; +} + +export interface TargetWithEvents { + addEventListener?( + event: string, + listener: (...args: any[]) => void, + opts?: { once: boolean } + ): any; + removeEventListener?( + event: string, + listener: (...args: any[]) => void, + opts?: { once: boolean } + ): any; + __ot_listeners?: { [name: string]: WeakMap, Func> }; +} diff --git a/packages/opentelemetry-scope-zone-peer-dep/src/util.ts b/packages/opentelemetry-scope-zone-peer-dep/src/util.ts new file mode 100644 index 00000000000..0f7aa835612 --- /dev/null +++ b/packages/opentelemetry-scope-zone-peer-dep/src/util.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. + */ + +import { TargetWithEvents } from './types'; + +export function isListenerObject(obj: TargetWithEvents = {}): boolean { + return ( + typeof obj.addEventListener === 'function' && + typeof obj.removeEventListener === 'function' + ); +} diff --git a/packages/opentelemetry-scope-zone-peer-dep/test/ZoneScopeManager.test.ts b/packages/opentelemetry-scope-zone-peer-dep/test/ZoneScopeManager.test.ts new file mode 100644 index 00000000000..5028fd392c9 --- /dev/null +++ b/packages/opentelemetry-scope-zone-peer-dep/test/ZoneScopeManager.test.ts @@ -0,0 +1,271 @@ +/*! + * 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 'zone.js'; +import * as sinon from 'sinon'; +import * as assert from 'assert'; +import { ZoneScopeManager } from '../src'; + +let clock: any; + +class MockSpan { + name = 'test'; +} + +describe('ZoneScopeManager', () => { + let scopeManager: ZoneScopeManager; + + beforeEach(() => { + clock = sinon.useFakeTimers(); + scopeManager = new ZoneScopeManager(); + scopeManager.enable(); + }); + + afterEach(() => { + clock.restore(); + scopeManager.disable(); + }); + + describe('.enable()', () => { + it('should work', () => { + assert.doesNotThrow(() => { + assert(scopeManager.enable() === scopeManager, 'should return this'); + assert(scopeManager.active() === window, 'should has root scope'); + }); + }); + }); + + describe('.disable()', () => { + it('should work', () => { + assert.doesNotThrow(() => { + assert(scopeManager.disable() === scopeManager, 'should return this'); + assert(scopeManager.active() === undefined, 'should has no scope'); + }); + }); + }); + + describe('.with()', () => { + it('should run the callback (null as target)', done => { + scopeManager.with(null, done); + }); + + it('should run the callback (object as target)', done => { + const test = { a: 1 }; + scopeManager.with(test, () => { + assert.strictEqual(scopeManager.active(), test, 'should have scope'); + return done(); + }); + }); + + it('should run the callback (when disabled)', done => { + scopeManager.disable(); + scopeManager.with(null, () => { + scopeManager.enable(); + return done(); + }); + }); + + it('should rethrow errors', done => { + assert.throws(() => { + scopeManager.with(null, () => { + throw new Error('This should be rethrown'); + }); + }); + return done(); + }); + + it('should finally restore an old scope, including the async task', done => { + const scope1 = 'scope1'; + const scope2 = 'scope2'; + const scope3 = 'scope3'; + + scopeManager.with(scope1, () => { + assert.strictEqual(scopeManager.active(), 'scope1'); + scopeManager.with(scope2, () => { + assert.strictEqual(scopeManager.active(), 'scope2'); + scopeManager.with(scope3, () => { + assert.strictEqual(scopeManager.active(), 'scope3'); + }); + assert.strictEqual(scopeManager.active(), 'scope2'); + }); + assert.strictEqual(scopeManager.active(), 'scope1'); + setTimeout(() => { + assert.strictEqual(scopeManager.active(), 'scope1'); + done(); + }, 500); + clock.tick(500); + }); + assert.strictEqual(scopeManager.active(), window); + }); + + it('should finally restore an old scope when scope is an object, including the async task', done => { + const scope1 = { a: 1 }; + const scope2 = { a: 2 }; + const scope3 = { a: 3 }; + scopeManager.with(scope1, () => { + assert.strictEqual(scopeManager.active(), scope1); + scopeManager.with(scope2, () => { + assert.strictEqual(scopeManager.active(), scope2); + scopeManager.with(scope3, () => { + assert.strictEqual(scopeManager.active(), scope3); + }); + assert.strictEqual(scopeManager.active(), scope2); + }); + assert.strictEqual(scopeManager.active(), scope1); + setTimeout(() => { + assert.strictEqual(scopeManager.active(), scope1); + done(); + }, 500); + clock.tick(500); + }); + assert.strictEqual(scopeManager.active(), window); + }); + }); + + describe('.bind(function)', () => { + it('should call the function with previously assigned scope', () => { + class Obj { + title: string; + + constructor(title: string) { + this.title = title; + } + + getTitle() { + return this.title; + } + } + + const obj1 = new Obj('a1'); + obj1.title = 'a2'; + const obj2 = new Obj('b1'); + const wrapper: any = scopeManager.bind(obj2.getTitle, obj1); + assert.ok(wrapper(), 'a2'); + }); + + it('should return the same target (when enabled)', () => { + const test = { a: 1 }; + assert.deepStrictEqual(scopeManager.bind(test), test); + }); + + it('should return the same target (when disabled)', () => { + scopeManager.disable(); + const test = { a: 1 }; + assert.deepStrictEqual(scopeManager.bind(test), test); + scopeManager.enable(); + }); + + it('should return current scope (when enabled)', done => { + const scope = { a: 1 }; + const fn: any = scopeManager.bind(() => { + assert.strictEqual(scopeManager.active(), scope, 'should have scope'); + return done(); + }, scope); + fn(); + }); + + it('should return current scope (when disabled)', done => { + scopeManager.disable(); + const scope = { a: 1 }; + const fn: any = scopeManager.bind(() => { + assert.strictEqual(scopeManager.active(), scope, 'should have scope'); + return done(); + }, scope); + fn(); + }); + + it('should bind the target for click event', done => { + const scope1 = { a: 1 }; + const element = document.createElement('div'); + + scopeManager.bind(element, scope1); + + element.addEventListener('click', () => { + assert.strictEqual(scopeManager.active(), scope1); + setTimeout(() => { + assert.strictEqual(scopeManager.active(), scope1); + done(); + }, 500); + clock.tick(500); + }); + + element.dispatchEvent( + new CustomEvent('click', { + bubbles: true, + cancelable: false, + composed: true, + }) + ); + }); + + it('should restore original events when user removes the click event', done => { + // for this test block sinon as it doesn't work correctly in this particular example with zone + + clock.restore(); + const scope1 = new MockSpan(); + + const element = document.createElement('div'); + const elementAddEventListener = element.addEventListener; + const elementRemoveEventListener = element.removeEventListener; + + scopeManager.bind(element, scope1); + + function onClick(this: any) { + assert.strictEqual( + this, + element, + 'event callback should have a proper scope' + ); + assert.strictEqual(scopeManager.active(), scope1); + setTimeout(() => { + done(); + }, 50); + } + + element.addEventListener('click', onClick); + + setTimeout(() => { + assert.ok( + element.addEventListener !== elementAddEventListener, + 'element should have patched version of addEventListener' + ); + assert.ok( + element.removeEventListener !== elementRemoveEventListener, + 'element should have patched version of removeEventListener' + ); + + element.removeEventListener('click', onClick); + + assert.ok( + element.addEventListener === elementAddEventListener, + 'element should have original version of of addEventListener' + ); + assert.ok( + element.removeEventListener === elementRemoveEventListener, + 'element should have original version of removeEventListener' + ); + }, 10); + + element.dispatchEvent( + new CustomEvent('click', { + bubbles: true, + cancelable: false, + composed: true, + }) + ); + }); + }); +}); diff --git a/packages/opentelemetry-scope-zone-peer-dep/test/index-webpack.ts b/packages/opentelemetry-scope-zone-peer-dep/test/index-webpack.ts new file mode 100644 index 00000000000..6bfbf5a20ae --- /dev/null +++ b/packages/opentelemetry-scope-zone-peer-dep/test/index-webpack.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. + */ + +// 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-scope-zone-peer-dep/tsconfig.json b/packages/opentelemetry-scope-zone-peer-dep/tsconfig.json new file mode 100644 index 00000000000..ab49dd3fbd6 --- /dev/null +++ b/packages/opentelemetry-scope-zone-peer-dep/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.base", + "compilerOptions": { + "rootDir": ".", + "outDir": "build" + }, + "files": [ "node_modules/zone.js/dist/zone.js.d.ts"], + "include": [ + "src/**/*.ts", + "test/**/*.ts" + ] +} diff --git a/packages/opentelemetry-scope-zone-peer-dep/tslint.json b/packages/opentelemetry-scope-zone-peer-dep/tslint.json new file mode 100644 index 00000000000..0710b135d07 --- /dev/null +++ b/packages/opentelemetry-scope-zone-peer-dep/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-scope-zone-peer-dep/webpack/test.config.js b/packages/opentelemetry-scope-zone-peer-dep/webpack/test.config.js new file mode 100644 index 00000000000..997cce71822 --- /dev/null +++ b/packages/opentelemetry-scope-zone-peer-dep/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-scope-zone/LICENSE b/packages/opentelemetry-scope-zone/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/packages/opentelemetry-scope-zone/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-scope-zone/README.md b/packages/opentelemetry-scope-zone/README.md new file mode 100644 index 00000000000..02b9c681a36 --- /dev/null +++ b/packages/opentelemetry-scope-zone/README.md @@ -0,0 +1,69 @@ +# OpenTelemetry Scope Zone +[![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 *Zone Scope Manager with bundled [zone-js]* for Web applications. +If you have your own [zone-js] please use *@opentelemetry/scope-zone-peer-dep* + + +## Installation + +```bash +npm install --save @opentelemetry/scope-zone +``` + +## Usage +```js +import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/tracing'; +import { WebTracer } from '@opentelemetry/web'; +import { ZoneScopeManager } from '@opentelemetry/scope-zone'; + +const webTracerWithZone = new WebTracer({ + scopeManager: new ZoneScopeManager() +}); +webTracerWithZone.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); + +// Example how the ZoneScopeManager keeps the reference to the correct scope during async operations +const span1 = webTracerWithZone.startSpan('foo1'); +webTracerWithZone.withSpan(span1, () => { + console.log('Current span is span1', webTracerWithZone.getCurrentSpan() === span1); + setTimeout(() => { + const span2 = webTracerWithZone.startSpan('foo2'); + console.log('Current span is span1', webTracerWithZone.getCurrentSpan() === span1); + webTracerWithZone.withSpan(span2, () => { + console.log('Current span is span2', webTracerWithZone.getCurrentSpan() === span2); + setTimeout(() => { + console.log('Current span is span2', webTracerWithZone.getCurrentSpan() === span2); + }, 500); + }); + // there is a timeout which still keeps span2 active + console.log('Current span is span2', webTracerWithZone.getCurrentSpan() === span2); + }, 500); + console.log('Current span is span1', webTracerWithZone.getCurrentSpan() === span1); +}); + +``` + +## 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-scope-zone +[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-web +[devDependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js/dev-status.svg?path=packages/opentelemetry-scope-zone +[devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-web&type=dev +[npm-url]: https://www.npmjs.com/package/@opentelemetry/scope-zone +[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fscope-zone.svg +[zone-js]: https://www.npmjs.com/package/zone.js diff --git a/packages/opentelemetry-scope-zone/package.json b/packages/opentelemetry-scope-zone/package.json new file mode 100644 index 00000000000..bbf88fa7fae --- /dev/null +++ b/packages/opentelemetry-scope-zone/package.json @@ -0,0 +1,73 @@ +{ + "name": "@opentelemetry/scope-zone", + "version": "0.1.1", + "description": "OpenTelemetry Scope Zone", + "main": "build/src/index.js", + "types": "build/src/index.d.ts", + "repository": "open-telemetry/opentelemetry-js", + "scripts": { + "check": "gts check", + "clean": "rimraf build/*", + "compile": "tsc -p .", + "fix": "gts fix", + "prepare": "npm run compile", + "watch": "tsc -w" + }, + "keywords": [ + "opentelemetry", + "web", + "tracing", + "profiling", + "metrics", + "stats" + ], + "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/scope-zone-peer-dep": "^0.1.1", + "zone.js": "^0.10.2" + }, + + "sideEffects": true +} diff --git a/packages/opentelemetry-scope-zone/src/index.ts b/packages/opentelemetry-scope-zone/src/index.ts new file mode 100644 index 00000000000..425ddebfda3 --- /dev/null +++ b/packages/opentelemetry-scope-zone/src/index.ts @@ -0,0 +1,18 @@ +/*! + * 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 '@opentelemetry/scope-zone-peer-dep'; +import 'zone.js'; diff --git a/packages/opentelemetry-scope-zone/tsconfig.json b/packages/opentelemetry-scope-zone/tsconfig.json new file mode 100644 index 00000000000..ab49dd3fbd6 --- /dev/null +++ b/packages/opentelemetry-scope-zone/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.base", + "compilerOptions": { + "rootDir": ".", + "outDir": "build" + }, + "files": [ "node_modules/zone.js/dist/zone.js.d.ts"], + "include": [ + "src/**/*.ts", + "test/**/*.ts" + ] +} diff --git a/packages/opentelemetry-scope-zone/tslint.json b/packages/opentelemetry-scope-zone/tslint.json new file mode 100644 index 00000000000..0710b135d07 --- /dev/null +++ b/packages/opentelemetry-scope-zone/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-web/README.md b/packages/opentelemetry-web/README.md index 20117c68fce..54e6a8d94cc 100644 --- a/packages/opentelemetry-web/README.md +++ b/packages/opentelemetry-web/README.md @@ -8,14 +8,36 @@ This module provides *automated instrumentation and tracing* for Web applications. For manual instrumentation see the -[@opentelemetry/web](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-web) package. +[@opentelemetry/tracing](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-tracing) package. ## How does automatic tracing work? +This package exposes a class `WebTracer` that will be able to automatically trace things in Browser only. + +See the example how to use it. + +OpenTelemetry comes with a growing number of instrumentation plugins for well know modules (see [supported modules](https://github.com/open-telemetry/opentelemetry-js#plugins)) and an API to create custom plugins (see [the plugin developer guide](https://github.com/open-telemetry/opentelemetry-js/blob/master/doc/plugin-guide.md)). + +Web Tracer currently supports one plugin for document load. +Unlike Node Tracer, the plugins needs to be initialised and passed in configuration. +The reason is to give user full control over which plugin will be bundled into web page. + +You can choose to use the ZoneScopeManager if you want to trace asynchronous operations. + +## Installation + +```bash +npm install --save @opentelemetry/web +``` + +## Usage + ```js import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/tracing'; import { WebTracer } from '@opentelemetry/web'; import { DocumentLoad } from '@opentelemetry/plugin-document-load'; +import { ZoneScopeManager } from '@opentelemetry/scope-zone'; +// Minimum required setup - supports only synchronous operations const webTracer = new WebTracer({ plugins: [ new DocumentLoad() @@ -23,26 +45,15 @@ const webTracer = new WebTracer({ }); webTracer.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); -``` - -## Installation - -```bash -npm install --save @opentelemetry/web -``` -## Usage - -```js -// Manual -const { WebTracer } = require('@opentelemetry/web'); -const webTracer = new WebTracer(); -const span = webTracer.startSpan('span1'); -webTracer.withSpan(span, function () { - this.addEvent('start'); +// Changing default scopeManager to use ZoneScopeManager - supports asynchronous operations +const webTracerWithZone = new WebTracer({ + scopeManager: new ZoneScopeManager(), + plugins: [ + new DocumentLoad() + ] }); -span.addEvent('middle'); -span.end(); +webTracerWithZone.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); ``` From 67ca355f5a3752928e02d4924326a388367dadb0 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Fri, 25 Oct 2019 15:15:58 +0200 Subject: [PATCH 02/17] chore: removing not needed dependency --- packages/opentelemetry-scope-zone-peer-dep/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/opentelemetry-scope-zone-peer-dep/package.json b/packages/opentelemetry-scope-zone-peer-dep/package.json index 800b15b6548..6ce2518146f 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/package.json +++ b/packages/opentelemetry-scope-zone-peer-dep/package.json @@ -66,8 +66,7 @@ "typescript": "^3.6.3", "webpack": "^4.35.2", "webpack-cli": "^3.3.9", - "zone.js": "^0.10.2", - "@opentelemetry/tracing": "^0.1.1" + "zone.js": "^0.10.2" }, "dependencies": { "@opentelemetry/scope-base": "^0.1.1" From 9707516e15d73b1d445ebb22964bf103322298f2 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Mon, 28 Oct 2019 16:33:31 +0100 Subject: [PATCH 03/17] chore: updating readme --- packages/opentelemetry-scope-zone-peer-dep/README.md | 1 + packages/opentelemetry-scope-zone/README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/opentelemetry-scope-zone-peer-dep/README.md b/packages/opentelemetry-scope-zone-peer-dep/README.md index 9ecd898e013..1459932cb27 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/README.md +++ b/packages/opentelemetry-scope-zone-peer-dep/README.md @@ -6,6 +6,7 @@ [![Apache License][license-image]][license-image] This module provides *Zone Scope Manager with a peer dependency for [zone-js]* for Web applications. +If you use Angular you already have the zone.js and you should use this package. If you don't have your own [zone-js] please use *@opentelemetry/scope-zone* diff --git a/packages/opentelemetry-scope-zone/README.md b/packages/opentelemetry-scope-zone/README.md index 02b9c681a36..b5507ec2d26 100644 --- a/packages/opentelemetry-scope-zone/README.md +++ b/packages/opentelemetry-scope-zone/README.md @@ -7,6 +7,7 @@ This module provides *Zone Scope Manager with bundled [zone-js]* for Web applications. If you have your own [zone-js] please use *@opentelemetry/scope-zone-peer-dep* +If you use Angular it means you already have the zone.js adn you should use *@opentelemetry/scope-zone-peer-dep* ## Installation From b2b79b8202e8f46c0a93e5588d998bb2a696a72c Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Mon, 28 Oct 2019 16:34:36 +0100 Subject: [PATCH 04/17] chore: clean up --- .../src/ZoneScopeManager.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts b/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts index 23db8069515..9c156aaa063 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts +++ b/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts @@ -48,8 +48,6 @@ export class ZoneScopeManager implements ScopeManager { */ private _zoneCounter = 0; - constructor() {} - /** * Returns the active scope from certain zone name * @param activeZoneName @@ -176,8 +174,7 @@ export class ZoneScopeManager implements ScopeManager { private _createZoneName() { this._zoneCounter++; const random = Math.random(); - const zoneName = `${this._zoneCounter}-${random}`; - return zoneName; + return `${this._zoneCounter}-${random}`; } /** From bfa8d7c288717b3c504c35b6c4b2f0c452140f18 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Mon, 28 Oct 2019 21:29:23 +0100 Subject: [PATCH 05/17] chore: refactored the bind method --- .../src/ZoneScopeManager.ts | 149 ++---------------- .../src/index.ts | 1 - .../src/types.ts | 10 +- .../test/ZoneScopeManager.test.ts | 70 +++----- 4 files changed, 46 insertions(+), 184 deletions(-) diff --git a/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts b/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts index 9c156aaa063..752ab3fc850 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts +++ b/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts @@ -14,8 +14,19 @@ * limitations under the License. */ +/** + * ZoneScopeManager + * This module provides an easy functionality for tracing action between asynchronous operations in web. + * It was not possible with standard [StackScopeManager]{@link https://github.com/open-telemetry/opentelemetry-js/blob/master/packages/opentelemetry-web/src/StackScopeManager.ts}. + * It heavily depends on [zone.js]{@link https://www.npmjs.com/package/zone.js}. + * It stores the information about scopes per zone. If there is not active zone, a new zone will be forked. + * It also supports binding a certain Span to a target with "addEventListener". + * When this happens a new zone is being created and the provided Span is being assigned to this zone. + * + */ + import { ScopeManager } from '@opentelemetry/scope-base'; -import { Func, ScopeWithEventTargets, TargetWithEvents } from './types'; +import { Func, TargetWithEvents } from './types'; import { isListenerObject } from './util'; /** @@ -38,11 +49,6 @@ export class ZoneScopeManager implements ScopeManager { */ private _scopes: { [key: string]: unknown[] } = {}; - /** - * Keeps the reference to the scopes with original event targets - */ - private _scopesWithEventTargets: ScopeWithEventTargets[] = []; - /** * Helps to create a unique name for the zones - part of zone name */ @@ -104,67 +110,18 @@ export class ZoneScopeManager implements ScopeManager { if (typeof target.removeEventListener === 'function') { target.removeEventListener = this._patchRemoveEventListener( target, - target.removeEventListener, - scope + target.removeEventListener ); } return obj; } - /** - * Removes references to the targets that were bind to certain scope / span - * @param scope - */ - private _cleanEventTargetsFromScope(scope: any, target?: TargetWithEvents) { - // console.log('_cleanEventTargetsFromScope1', this._scopesWithEventTargets.length, scope && scope.name); - for (let i = this._scopesWithEventTargets.length - 1; i >= 0; i--) { - const scopeWithEventTargets = this._scopesWithEventTargets[i]; - if (scopeWithEventTargets.scope === scope) { - if ( - typeof target !== 'undefined' && - scopeWithEventTargets.target !== target - ) { - continue; - } - - if (scopeWithEventTargets.target) { - try { - scopeWithEventTargets.target.addEventListener = - scopeWithEventTargets.eventTargets.addEventListener; - - scopeWithEventTargets.target.removeEventListener = - scopeWithEventTargets.eventTargets.removeEventListener; - - delete scopeWithEventTargets.target.__ot_listeners; - } catch (e) { - console.log(e); - } - } - - this._scopesWithEventTargets.splice(i, 1); - - if ( - typeof target !== 'undefined' && - scopeWithEventTargets.target === target - ) { - break; - } - } - } - // console.log('_cleanEventTargetsFromScope2', this._scopesWithEventTargets.length); - } - /** * Removes all the associated scopes with zone after the zone has no more tasks * @param zoneName */ private _cleanScopesFromZone(zoneName: string) { - const scopes = this._scopes[zoneName]; - for (let i = scopes.length - 1; i >= 0; i--) { - this._cleanEventTargetsFromScope(scopes[i]); - scopes.splice(i, 1); - } delete this._scopes[zoneName]; } @@ -189,38 +146,6 @@ export class ZoneScopeManager implements ScopeManager { }); } - /** - * Ensure that appropriate {@link ScopeWithEventTargets} exists if not then create a new one - * @param scope - * @param target - */ - private _ensureScopeWithEventTargetsExists( - scope: unknown, - target: TargetWithEvents - ): ScopeWithEventTargets { - let foundScope; - for (const scopeWithEventTargets of this._scopesWithEventTargets) { - if ( - scopeWithEventTargets.scope === scope && - scopeWithEventTargets.target === target - ) { - foundScope = scopeWithEventTargets; - break; - } - } - if (!foundScope) { - this._scopesWithEventTargets.push({ - scope, - target, - eventTargets: {}, - }); - foundScope = this._scopesWithEventTargets[ - this._scopesWithEventTargets.length - 1 - ]; - } - return foundScope; - } - /** * Returns the active zone */ @@ -322,9 +247,8 @@ export class ZoneScopeManager implements ScopeManager { scope?: unknown ) { const scopeManager = this; - this._saveOriginalAddEventWithScope(scope, target, original); - return function(this: {}, event: string, listener: Func) { + return function(this: {}, event: string, listener: Func, opts?: any) { if (target.__ot_listeners === undefined) { target.__ot_listeners = {}; } @@ -336,7 +260,7 @@ export class ZoneScopeManager implements ScopeManager { const patchedListener = scopeManager.bind(listener, scope); // store a weak reference of the user listener to ours listeners.set(listener, patchedListener); - return original.call(this, event, patchedListener); + return original.call(this, event, patchedListener, opts); }; } @@ -344,15 +268,11 @@ export class ZoneScopeManager implements ScopeManager { * Patches removeEventListener method * @param target any target that has "removeEventListener" method * @param original reference to the patched method - * @param [scope] scope to be bind to the listener */ private _patchRemoveEventListener( target: TargetWithEvents, - original: Function, - scope?: unknown + original: Function ) { - const scopeManager = this; - this._saveOriginalRemoveEventWithScope(scope, target, original); return function(this: {}, event: string, listener: Func) { if ( target.__ot_listeners === undefined || @@ -362,7 +282,7 @@ export class ZoneScopeManager implements ScopeManager { } const events = target.__ot_listeners[event]; const patchedListener = events.get(listener); - scopeManager._cleanEventTargetsFromScope(scope, target); + events.delete(listener); return original.call(this, event, patchedListener || listener); }; } @@ -375,45 +295,10 @@ export class ZoneScopeManager implements ScopeManager { const zoneName = zone.name; const scopes = this._scopes[zoneName]; if (scopes && scopes.length > 0) { - this._cleanEventTargetsFromScope(scopes[scopes.length - 1]); scopes.splice(scopes.length - 1, 1); } } - /** - * Saves original addEventListener method so it can be restored later - * @param scope - * @param target - * @param original original addEventListener method - */ - private _saveOriginalAddEventWithScope( - scope: unknown, - target: TargetWithEvents, - original: any - ) { - const foundScope = this._ensureScopeWithEventTargetsExists(scope, target); - if (foundScope) { - foundScope.eventTargets.addEventListener = original; - } - } - - /** - * Saves original removeEventListener method so it can be restored later - * @param scope - * @param target - * @param original original removeEventListener method - */ - private _saveOriginalRemoveEventWithScope( - scope: unknown, - target: TargetWithEvents, - original: any - ) { - const foundScope = this._ensureScopeWithEventTargetsExists(scope, target); - if (foundScope) { - foundScope.eventTargets.removeEventListener = original; - } - } - /** * Returns the active scope */ diff --git a/packages/opentelemetry-scope-zone-peer-dep/src/index.ts b/packages/opentelemetry-scope-zone-peer-dep/src/index.ts index 9673589ce51..3360b42ee33 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/src/index.ts +++ b/packages/opentelemetry-scope-zone-peer-dep/src/index.ts @@ -16,4 +16,3 @@ export * from './ZoneScopeManager'; export * from './types'; -export * from './util'; diff --git a/packages/opentelemetry-scope-zone-peer-dep/src/types.ts b/packages/opentelemetry-scope-zone-peer-dep/src/types.ts index 79dad4f3fe8..e67614be708 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/src/types.ts +++ b/packages/opentelemetry-scope-zone-peer-dep/src/types.ts @@ -16,12 +16,10 @@ export type Func = (...args: unknown[]) => T; -export interface ScopeWithEventTargets { - scope: unknown; - target: TargetWithEvents; - eventTargets: TargetWithEvents; -} - +/** + * Minimum requirements that the object needs to have so that it can bind to the events instead of function + * this is "addEventListener" and "removeEventListener" - see {@link isListenerObject} + */ export interface TargetWithEvents { addEventListener?( event: string, diff --git a/packages/opentelemetry-scope-zone-peer-dep/test/ZoneScopeManager.test.ts b/packages/opentelemetry-scope-zone-peer-dep/test/ZoneScopeManager.test.ts index 5028fd392c9..5bfa1666e9c 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/test/ZoneScopeManager.test.ts +++ b/packages/opentelemetry-scope-zone-peer-dep/test/ZoneScopeManager.test.ts @@ -21,10 +21,6 @@ import { ZoneScopeManager } from '../src'; let clock: any; -class MockSpan { - name = 'test'; -} - describe('ZoneScopeManager', () => { let scopeManager: ZoneScopeManager; @@ -187,7 +183,7 @@ describe('ZoneScopeManager', () => { fn(); }); - it('should bind the target for click event', done => { + it('should bind the the certain scope to the target "addEventListener" function', done => { const scope1 = { a: 1 }; const element = document.createElement('div'); @@ -211,53 +207,37 @@ describe('ZoneScopeManager', () => { ); }); - it('should restore original events when user removes the click event', done => { - // for this test block sinon as it doesn't work correctly in this particular example with zone - - clock.restore(); - const scope1 = new MockSpan(); - + it('should preserve zone when creating new click event inside zone', done => { + const scope1 = { a: 1 }; const element = document.createElement('div'); - const elementAddEventListener = element.addEventListener; - const elementRemoveEventListener = element.removeEventListener; scopeManager.bind(element, scope1); - function onClick(this: any) { - assert.strictEqual( - this, - element, - 'event callback should have a proper scope' - ); + element.addEventListener('click', () => { assert.strictEqual(scopeManager.active(), scope1); setTimeout(() => { - done(); - }, 50); - } + assert.strictEqual(scopeManager.active(), scope1); + const element2 = document.createElement('div'); + + element2.addEventListener('click', () => { + assert.strictEqual(scopeManager.active(), scope1); + setTimeout(() => { + assert.strictEqual(scopeManager.active(), scope1); + done(); + }, 500); + clock.tick(500); + }); - element.addEventListener('click', onClick); - - setTimeout(() => { - assert.ok( - element.addEventListener !== elementAddEventListener, - 'element should have patched version of addEventListener' - ); - assert.ok( - element.removeEventListener !== elementRemoveEventListener, - 'element should have patched version of removeEventListener' - ); - - element.removeEventListener('click', onClick); - - assert.ok( - element.addEventListener === elementAddEventListener, - 'element should have original version of of addEventListener' - ); - assert.ok( - element.removeEventListener === elementRemoveEventListener, - 'element should have original version of removeEventListener' - ); - }, 10); + element2.dispatchEvent( + new CustomEvent('click', { + bubbles: true, + cancelable: false, + composed: true, + }) + ); + }, 500); + clock.tick(500); + }); element.dispatchEvent( new CustomEvent('click', { From 21785a37dba70b88d996b641a82f30fbc9c6d18e Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Mon, 28 Oct 2019 21:29:58 +0100 Subject: [PATCH 06/17] chore: updated example for web tracer --- examples/tracer-web/index.html | 4 +- examples/tracer-web/index.js | 84 ++++++++++++++++++++-------------- 2 files changed, 51 insertions(+), 37 deletions(-) diff --git a/examples/tracer-web/index.html b/examples/tracer-web/index.html index 522a8c2443b..afb28c949e0 100644 --- a/examples/tracer-web/index.html +++ b/examples/tracer-web/index.html @@ -13,9 +13,7 @@ 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 d3c99e7d07a..003eb1c384e 100644 --- a/examples/tracer-web/index.js +++ b/examples/tracer-web/index.js @@ -39,53 +39,69 @@ webTracerWithZone.withSpan(span1, () => { console.log('Current span is span1', webTracerWithZone.getCurrentSpan() === span1); }); -const span3 = webTracerWithZone.startSpan('foo1'); -webTracerWithZone.withSpan(span3, () => { - console.log('Current span is span3', webTracerWithZone.getCurrentSpan() === span3); - setTimeout(() => { - const span4 = webTracerWithZone.startSpan('foo2'); - console.log('Current span is span3', webTracerWithZone.getCurrentSpan() === span3); - webTracerWithZone.withSpan(span4, () => { - console.log('Current span is span4', webTracerWithZone.getCurrentSpan() === span4); - setTimeout(() => { - console.log('Current span is span4', webTracerWithZone.getCurrentSpan() === span4); - }, 500); - }); - // there is a timeout which still keeps span4 active - console.log('Current span is span4', webTracerWithZone.getCurrentSpan() === span4); - }, 490); - console.log('Current span is span3', webTracerWithZone.getCurrentSpan() === span3); -}); - -console.log('Current span is window', webTracerWithZone.getCurrentSpan() === window); - // example of keeping track of scope between async operations +let counter = 0; const prepareClickEvent = () => { - const element = document.getElementById('button2'); + const url = 'https://raw.githubusercontent.com/open-telemetry/opentelemetry-js/master/package.json'; + + const element = document.getElementById('button1'); let mainSpan = webTracerWithZone.startSpan('main-span'); webTracerWithZone.bind(element, mainSpan); const onClick = () => { - element.removeEventListener('click', onClick); - console.log('starting timeout, scope is', webTracerWithZone.getCurrentSpan().name); - const spanClickAsync = webTracerWithZone.startSpan('span-click-timeout', { + counter++; + const span1 = webTracerWithZone.startSpan(`files-series-info-${counter}`, { parent: webTracerWithZone.getCurrentSpan() }); - spanClickAsync.addEvent('starting timeout'); - window.setTimeout(() => { - console.log('timeout finished, scope is', webTracerWithZone.getCurrentSpan().name); - spanClickAsync.addEvent('timeout finished'); - console.log('ending spans'); - spanClickAsync.end(); - mainSpan.end(); - }, 500); + webTracerWithZone.withSpan(span1, ()=> { + span1.addEvent('fetching-data-1'); + getData(url).then((data)=> { + console.log(webTracerWithZone.getCurrentSpan().name, data.description, data.version); + webTracerWithZone.getCurrentSpan().addEvent('fetching-data-1-completed'); + webTracerWithZone.getCurrentSpan().addEvent('fetching-data-2'); + + getData(url).then((data)=> { + console.log(webTracerWithZone.getCurrentSpan().name, data.description, data.version); + + webTracerWithZone.getCurrentSpan().addEvent('fetching-data-2-completed'); + webTracerWithZone.getCurrentSpan().end(); + counter++ + ;const span2 = webTracerWithZone.startSpan(`files-series-info-${counter}`, { + parent: webTracerWithZone.getCurrentSpan() + }); + webTracerWithZone.withSpan(span2, ()=> { + webTracerWithZone.getCurrentSpan().addEvent('fetching-data-3'); + getData(url).then((data)=> { + console.log(webTracerWithZone.getCurrentSpan().name, data.description, data.version); + webTracerWithZone.getCurrentSpan().addEvent('fetching-data-3-completed'); + webTracerWithZone.getCurrentSpan().end(); + }); + }); + }); + }); + }); }; element.addEventListener('click', onClick); +}; +const getData = (url) => { + return new Promise(async (resolve, reject) => { + const req = new XMLHttpRequest(); + req.open('GET', url, true); + req.send(); + req.onload = function () { + let json; + try { + json = JSON.parse(req.responseText); + } catch (e) { + reject(e); + } + resolve(json); + }; + }); }; window.addEventListener('load', () => { - const element = document.getElementById('button1'); - element.addEventListener('click', prepareClickEvent); + prepareClickEvent(); }); From d835b9e8695d81f190390f63551288566d2fedcf Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Mon, 28 Oct 2019 21:31:17 +0100 Subject: [PATCH 07/17] chore: updated readme --- packages/opentelemetry-scope-zone-peer-dep/README.md | 7 ++++--- packages/opentelemetry-scope-zone/README.md | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/opentelemetry-scope-zone-peer-dep/README.md b/packages/opentelemetry-scope-zone-peer-dep/README.md index 1459932cb27..72d83ed591d 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/README.md +++ b/packages/opentelemetry-scope-zone-peer-dep/README.md @@ -6,8 +6,8 @@ [![Apache License][license-image]][license-image] This module provides *Zone Scope Manager with a peer dependency for [zone-js]* for Web applications. -If you use Angular you already have the zone.js and you should use this package. -If you don't have your own [zone-js] please use *@opentelemetry/scope-zone* +If you use Angular you already have the [zone-js] and you should use this package. +If you don't have your own [zone-js] please use [@opentelemetry/scope-zone] ## Installation @@ -62,9 +62,10 @@ Apache 2.0 - See [LICENSE][license-url] for more information. [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-scope-zone-peer-dep -[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-web +[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-scope-zone-peer-dep [devDependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js/dev-status.svg?path=packages/opentelemetry-scope-zone-peer-dep [devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-web&type=dev [npm-url]: https://www.npmjs.com/package/@opentelemetry/scope-zone-peer-dep [npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fscope-zone-peer-dep.svg [zone-js]: https://www.npmjs.com/package/zone.js +[@opentelemetry/scope-zone]: https://www.npmjs.com/package/@opentelemetry/scope-zone diff --git a/packages/opentelemetry-scope-zone/README.md b/packages/opentelemetry-scope-zone/README.md index b5507ec2d26..c6a768191d5 100644 --- a/packages/opentelemetry-scope-zone/README.md +++ b/packages/opentelemetry-scope-zone/README.md @@ -7,7 +7,7 @@ This module provides *Zone Scope Manager with bundled [zone-js]* for Web applications. If you have your own [zone-js] please use *@opentelemetry/scope-zone-peer-dep* -If you use Angular it means you already have the zone.js adn you should use *@opentelemetry/scope-zone-peer-dep* +If you use Angular it means you already have the [zone-js] and you should use [@opentelemetry/scope-zone-peer-dep] ## Installation @@ -62,9 +62,10 @@ Apache 2.0 - See [LICENSE][license-url] for more information. [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-scope-zone -[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-web +[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-scope-zone [devDependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js/dev-status.svg?path=packages/opentelemetry-scope-zone [devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-web&type=dev [npm-url]: https://www.npmjs.com/package/@opentelemetry/scope-zone [npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fscope-zone.svg [zone-js]: https://www.npmjs.com/package/zone.js +[@opentelemetry/scope-zone-peer-dep]: https://www.npmjs.com/package/@opentelemetry/scope-zone-peer-dep From dc043704e6eaa06bb5737092b6a0910dd5318754 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Mon, 28 Oct 2019 22:00:36 +0100 Subject: [PATCH 08/17] chore: updating jsdoc --- .../src/ZoneScopeManager.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts b/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts index 752ab3fc850..e7ffc073452 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts +++ b/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts @@ -72,7 +72,7 @@ export class ZoneScopeManager implements ScopeManager { /** * @param target Function to be executed within the scope - * @param scope + * @param scope A scope (span) to be executed within target function */ private _bindFunction(target: T, scope?: unknown): T { const manager = this; @@ -90,7 +90,7 @@ export class ZoneScopeManager implements ScopeManager { /** * @param obj target object on which the listeners will be patched - * @param scope + * @param scope A scope (span) to be bind to target */ private _bindListener(obj: T, scope?: unknown): T { const target = (obj as unknown) as TargetWithEvents; @@ -318,7 +318,7 @@ export class ZoneScopeManager implements ScopeManager { /** * Binds a the certain scope or the active one to the target function and then returns the target * @param target - * @param scope + * @param scope A scope (span) to be bind to target */ bind(target: T | TargetWithEvents, scope?: unknown): T { // if no specific scope to propagate is given, we use the current one @@ -361,7 +361,7 @@ export class ZoneScopeManager implements ScopeManager { * Calls the callback function [fn] with the provided [scope]. * If [scope] is undefined then it will use the active scope. * The scope will be set as active - * @param scope + * @param scope A scope (span) to be called with provided callback * @param fn Callback function */ with ReturnType>( From 290d4af6c1d2cd3415e95d756dd5df1c2eab78aa Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Mon, 28 Oct 2019 23:51:50 +0100 Subject: [PATCH 09/17] fix: fixing flaky test for BatchSpanProcessor with timeout --- .../test/export/BatchSpanProcessor.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts b/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts index e4ef7f55ef5..04805a0a2db 100644 --- a/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts +++ b/packages/opentelemetry-tracing/test/export/BatchSpanProcessor.test.ts @@ -15,6 +15,7 @@ */ import * as assert from 'assert'; +import * as sinon from 'sinon'; import { Span, BasicTracer, @@ -107,6 +108,7 @@ describe('BatchSpanProcessor', () => { }); it('should force flush when timeout exceeded', done => { + const clock = sinon.useFakeTimers(); const processor = new BatchSpanProcessor(exporter, defaultBufferConfig); for (let i = 0; i < defaultBufferConfig.bufferSize; i++) { const span = createSampledSpan(`${name}_${i}`); @@ -118,6 +120,10 @@ describe('BatchSpanProcessor', () => { assert.strictEqual(exporter.getFinishedSpans().length, 5); done(); }, defaultBufferConfig.bufferTimeout + 1000); - }).timeout(defaultBufferConfig.bufferTimeout * 2); + + clock.tick(defaultBufferConfig.bufferTimeout + 1000); + + clock.restore(); + }); }); }); From 4050623ff192d0b423b43d6804130f8cc2aa6044 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Tue, 29 Oct 2019 02:44:52 +0100 Subject: [PATCH 10/17] chore: missing link --- packages/opentelemetry-scope-zone/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-scope-zone/README.md b/packages/opentelemetry-scope-zone/README.md index c6a768191d5..3ab2fd1f1c4 100644 --- a/packages/opentelemetry-scope-zone/README.md +++ b/packages/opentelemetry-scope-zone/README.md @@ -6,7 +6,7 @@ [![Apache License][license-image]][license-image] This module provides *Zone Scope Manager with bundled [zone-js]* for Web applications. -If you have your own [zone-js] please use *@opentelemetry/scope-zone-peer-dep* +If you have your own [zone-js] please use [@opentelemetry/scope-zone-peer-dep] If you use Angular it means you already have the [zone-js] and you should use [@opentelemetry/scope-zone-peer-dep] From 2e203510c48bd8bb00785d58f24e604a0780acc4 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Fri, 1 Nov 2019 21:56:28 +0100 Subject: [PATCH 11/17] chore: updating karma files --- .../karma.conf.js | 4 +-- .../package.json | 12 ++++--- .../test/index-webpack.ts | 4 ++- .../webpack/test.config.js | 34 ------------------- 4 files changed, 12 insertions(+), 42 deletions(-) delete mode 100644 packages/opentelemetry-scope-zone-peer-dep/webpack/test.config.js diff --git a/packages/opentelemetry-scope-zone-peer-dep/karma.conf.js b/packages/opentelemetry-scope-zone-peer-dep/karma.conf.js index 66529c7d920..7183aab0336 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/karma.conf.js +++ b/packages/opentelemetry-scope-zone-peer-dep/karma.conf.js @@ -14,11 +14,11 @@ * limitations under the License. */ -const webpackConfig = require('./webpack/test.config.js'); +const karmaWebpackConfig = require('../../karma.webpack'); const karmaBaseConfig = require('../../karma.base'); module.exports = (config) => { config.set(Object.assign({}, karmaBaseConfig, { - webpack: webpackConfig + webpack: karmaWebpackConfig })) }; diff --git a/packages/opentelemetry-scope-zone-peer-dep/package.json b/packages/opentelemetry-scope-zone-peer-dep/package.json index 6ce2518146f..7ea6251ed9b 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/package.json +++ b/packages/opentelemetry-scope-zone-peer-dep/package.json @@ -40,17 +40,19 @@ "access": "public" }, "devDependencies": { + "@babel/core": "^7.6.0", "@types/mocha": "^5.2.5", "@types/node": "^12.6.8", - "@types/zone.js": "^0.5.12", - "@types/webpack-env": "1.13.9", "@types/sinon": "^7.0.13", - "@babel/core": "^7.6.0", + "@types/webpack-env": "1.13.9", + "@types/zone.js": "^0.5.12", "babel-loader": "^8.0.6", "codecov": "^3.1.0", "gts": "^1.0.0", - "karma": "^4.1.0", - "karma-chrome-launcher": "^2.2.0", + "istanbul-instrumenter-loader": "^3.0.1", + "karma": "^4.4.1", + "karma-chrome-launcher": "^3.1.0", + "karma-coverage-istanbul-reporter": "^2.1.0", "karma-mocha": "^1.3.0", "karma-spec-reporter": "^0.0.32", "karma-webpack": "^4.0.2", diff --git a/packages/opentelemetry-scope-zone-peer-dep/test/index-webpack.ts b/packages/opentelemetry-scope-zone-peer-dep/test/index-webpack.ts index 6bfbf5a20ae..7731f090914 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/test/index-webpack.ts +++ b/packages/opentelemetry-scope-zone-peer-dep/test/index-webpack.ts @@ -16,6 +16,8 @@ // 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); + +const srcContext = require.context('.', true, /src$/); +srcContext.keys().forEach(srcContext); diff --git a/packages/opentelemetry-scope-zone-peer-dep/webpack/test.config.js b/packages/opentelemetry-scope-zone-peer-dep/webpack/test.config.js deleted file mode 100644 index 997cce71822..00000000000 --- a/packages/opentelemetry-scope-zone-peer-dep/webpack/test.config.js +++ /dev/null @@ -1,34 +0,0 @@ -/*! - * 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 } } - ] - } -}; From 02e50e693367fe9ff6de40b3a907e96766fc5497 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Fri, 1 Nov 2019 21:57:47 +0100 Subject: [PATCH 12/17] chore: adding docs and tests for utils --- .../src/util.ts | 4 ++ .../test/utils.test.ts | 54 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 packages/opentelemetry-scope-zone-peer-dep/test/utils.test.ts diff --git a/packages/opentelemetry-scope-zone-peer-dep/src/util.ts b/packages/opentelemetry-scope-zone-peer-dep/src/util.ts index 0f7aa835612..98022f92195 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/src/util.ts +++ b/packages/opentelemetry-scope-zone-peer-dep/src/util.ts @@ -16,6 +16,10 @@ import { TargetWithEvents } from './types'; +/** + * check if an object has addEventListener and removeEventListener functions then it will return true + * @param obj + */ export function isListenerObject(obj: TargetWithEvents = {}): boolean { return ( typeof obj.addEventListener === 'function' && diff --git a/packages/opentelemetry-scope-zone-peer-dep/test/utils.test.ts b/packages/opentelemetry-scope-zone-peer-dep/test/utils.test.ts new file mode 100644 index 00000000000..790bbb28fbe --- /dev/null +++ b/packages/opentelemetry-scope-zone-peer-dep/test/utils.test.ts @@ -0,0 +1,54 @@ +/*! + * 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 utils from '../src/util'; + +describe('ZoneScopeManager utils', () => { + describe('isListenerObject', () => { + describe('when object contains "addEventListener" and "removeEventListener"', () => { + it('should return true', () => { + const obj = { + addEventListener: function() {}, + removeEventListener: function() {}, + }; + assert.strictEqual(utils.isListenerObject(obj), true); + }); + }); + describe('when object doesn\'t contain "addEventListener" and "removeEventListener"', () => { + it('should return true', () => { + const obj = {}; + assert.strictEqual(utils.isListenerObject(obj), false); + }); + }); + describe('when object contains "addEventListener" only', () => { + it('should return false', () => { + const obj = { + addEventListener: function() {}, + }; + assert.strictEqual(utils.isListenerObject(obj), false); + }); + }); + describe('when object contains "removeEventListener" only', () => { + it('should return false', () => { + const obj = { + removeEventListener: function() {}, + }; + assert.strictEqual(utils.isListenerObject(obj), false); + }); + }); + }); +}); From 687048e7d5fe44047f52d22e980f2794189495d8 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Fri, 1 Nov 2019 22:27:18 +0100 Subject: [PATCH 13/17] chore: refactoring zone scope manager --- .../src/ZoneScopeManager.ts | 183 +++--------------- 1 file changed, 24 insertions(+), 159 deletions(-) diff --git a/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts b/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts index e7ffc073452..d735e73c7d5 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts +++ b/packages/opentelemetry-scope-zone-peer-dep/src/ZoneScopeManager.ts @@ -14,24 +14,21 @@ * limitations under the License. */ +import { ScopeManager } from '@opentelemetry/scope-base'; +import { Func, TargetWithEvents } from './types'; +import { isListenerObject } from './util'; + +/* Key name to be used to save a scope reference in Zone */ +const ZONE_SCOPE_KEY = 'OT_ZONE_SCOPE'; + /** * ZoneScopeManager * This module provides an easy functionality for tracing action between asynchronous operations in web. * It was not possible with standard [StackScopeManager]{@link https://github.com/open-telemetry/opentelemetry-js/blob/master/packages/opentelemetry-web/src/StackScopeManager.ts}. * It heavily depends on [zone.js]{@link https://www.npmjs.com/package/zone.js}. - * It stores the information about scopes per zone. If there is not active zone, a new zone will be forked. - * It also supports binding a certain Span to a target with "addEventListener". + * It stores the information about scope in zone. Each Scope will have always new Zone; + * It also supports binding a certain Span to a target that has "addEventListener" and "removeEventListener". * When this happens a new zone is being created and the provided Span is being assigned to this zone. - * - */ - -import { ScopeManager } from '@opentelemetry/scope-base'; -import { Func, TargetWithEvents } from './types'; -import { isListenerObject } from './util'; - -/** - * Zone Scope Manager for managing the state in web - * it fully supports the async calls */ export class ZoneScopeManager implements ScopeManager { /** @@ -39,16 +36,6 @@ export class ZoneScopeManager implements ScopeManager { */ private _enabled = false; - /** - * Keeps the the information whether to prevent restoring the scope after zone onInvoke - */ - private _preventScopeRestoring: { [key: string]: boolean } = {}; - - /** - * Keeps the reference to the scopes per zone - */ - private _scopes: { [key: string]: unknown[] } = {}; - /** * Helps to create a unique name for the zones - part of zone name */ @@ -56,18 +43,12 @@ export class ZoneScopeManager implements ScopeManager { /** * Returns the active scope from certain zone name - * @param activeZoneName + * @param activeZone */ - private _activeScopeFromZoneName( - activeZoneName: string | undefined + private _activeScopeFromZone( + activeZone: Zone | undefined ): unknown | undefined { - if (activeZoneName) { - const scopes = this._scopes[activeZoneName]; - if (scopes) { - return scopes[scopes.length - 1]; - } - } - return undefined; + return activeZone && activeZone.get(ZONE_SCOPE_KEY); } /** @@ -117,14 +98,6 @@ export class ZoneScopeManager implements ScopeManager { return obj; } - /** - * Removes all the associated scopes with zone after the zone has no more tasks - * @param zoneName - */ - private _cleanScopesFromZone(zoneName: string) { - delete this._scopes[zoneName]; - } - /** * Creates a new unique zone name */ @@ -137,12 +110,14 @@ export class ZoneScopeManager implements ScopeManager { /** * Creates a new zone * @param zoneName zone name + * @param scope A scope (span) to be bind with Zone */ - private _createZone(zoneName: string): Zone { + private _createZone(zoneName: string, scope: unknown): Zone { return Zone.root.fork({ name: zoneName, - onInvoke: this._onZoneInvoke.bind(this), - onHasTask: this._onHasTask.bind(this), + properties: { + [ZONE_SCOPE_KEY]: scope, + }, }); } @@ -150,89 +125,7 @@ export class ZoneScopeManager implements ScopeManager { * Returns the active zone */ private _getActiveZone(): Zone | undefined { - const currentZone = Zone.current; - if (currentZone) { - if ( - this._scopes[currentZone.name] && - this._scopes[currentZone.name].length - ) { - return currentZone; - } - } - return undefined; - } - - /** - * Returns the active zone name if such zone has any scope - */ - private _getActiveZoneName(): string | undefined { - const activeZone = this._getActiveZone(); - if (activeZone) { - return activeZone.name; - } - return undefined; - } - - /** - * Called when zone executes any async task or after the last async task was executed - * @param parentZoneDelegate - * @param currentZone - * @param targetZone - * @param hasTaskState - */ - private _onHasTask( - parentZoneDelegate: ZoneDelegate, - currentZone: Zone, - targetZone: Zone, - hasTaskState: HasTaskState - ) { - // console.log('onHasTask', hasTaskState, this._preventScopeRestoring[currentZone.name]); - if ( - !hasTaskState.eventTask && - !hasTaskState.microTask && - !hasTaskState.macroTask - ) { - // no more tasks can clean up zone data - this._cleanScopesFromZone(currentZone.name); - } else { - this._preventScopeRestoring[currentZone.name] = true; - } - } - - /** - * Called when zone invokes a delegate (callback) function - * @param parentZoneDelegate - * @param currentZone - * @param targetZone - * @param delegate - * @param applyThis - * @param applyArgs - * @param source - */ - private _onZoneInvoke( - parentZoneDelegate: ZoneDelegate, - currentZone: Zone, - targetZone: Zone, - delegate: Function, - applyThis: any, - applyArgs?: any[], - source?: string - ) { - // console.log('onInvoke before', typeof applyThis === 'string' ? applyThis : applyThis && applyThis.name || 'window'); - const result = parentZoneDelegate.invoke( - targetZone, - delegate, - applyThis, - applyArgs, - source - ); - // console.log('onInvoke after', typeof applyThis === 'string' ? applyThis : applyThis && applyThis.name || 'window', this._preventScopeRestoring[currentZone.name]); - // if zone has asynchronous task then restoring scope cannot be done immediately - // scopes will be cleaned later in _onHasTask - when there are no more tasks - if (!this._preventScopeRestoring[currentZone.name]) { - this._restorePreviousScope(currentZone); - } - return result; + return Zone.current; } /** @@ -287,25 +180,13 @@ export class ZoneScopeManager implements ScopeManager { }; } - /** - * Restores previous scope for certain zone - * @param zone - */ - private _restorePreviousScope(zone: Zone) { - const zoneName = zone.name; - const scopes = this._scopes[zoneName]; - if (scopes && scopes.length > 0) { - scopes.splice(scopes.length - 1, 1); - } - } - /** * Returns the active scope */ active(): unknown | undefined { - const activeZoneName = this._getActiveZoneName(); + const activeZone = this._getActiveZone(); - const active = this._activeScopeFromZoneName(activeZoneName); + const active = this._activeScopeFromZone(activeZone); if (active) { return active; } @@ -337,11 +218,6 @@ export class ZoneScopeManager implements ScopeManager { * Disable the scope manager (clears all the scopes) */ disable(): this { - const zoneNames = Object.keys(this._scopes); - for (const zoneName of zoneNames) { - this._cleanScopesFromZone(zoneName); - } - this._scopes = {}; this._enabled = false; return this; } @@ -373,21 +249,10 @@ export class ZoneScopeManager implements ScopeManager { scope = this.active(); } - let zoneName: string; - let activeZone = this._getActiveZone(); - if (!activeZone) { - zoneName = this._createZoneName(); - } else { - zoneName = activeZone.name; - } - - this._scopes[zoneName] = this._scopes[zoneName] || []; - this._scopes[zoneName].push(scope); + const zoneName = this._createZoneName(); - if (!activeZone) { - activeZone = this._createZone(zoneName); - } + const newZone = this._createZone(zoneName, scope); - return activeZone.run(fn, scope); + return newZone.run(fn, scope); } } From 9e2e9950e9126f8cca4a86f7a518c8f8bd4955ef Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Fri, 1 Nov 2019 22:46:51 +0100 Subject: [PATCH 14/17] chore: adding test for web and zone scope manager for parallel actions --- packages/opentelemetry-web/package.json | 3 +- .../opentelemetry-web/test/WebTracer.test.ts | 55 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/packages/opentelemetry-web/package.json b/packages/opentelemetry-web/package.json index f4f438c8628..ef48c26da18 100644 --- a/packages/opentelemetry-web/package.json +++ b/packages/opentelemetry-web/package.json @@ -67,7 +67,8 @@ "typescript": "^3.6.3", "webpack": "^4.35.2", "webpack-cli": "^3.3.9", - "webpack-merge": "^4.2.2" + "webpack-merge": "^4.2.2", + "@opentelemetry/scope-zone": "^0.1.1" }, "dependencies": { "@opentelemetry/core": "^0.1.1", diff --git a/packages/opentelemetry-web/test/WebTracer.test.ts b/packages/opentelemetry-web/test/WebTracer.test.ts index e1474508ea7..ddbb2eda8a9 100644 --- a/packages/opentelemetry-web/test/WebTracer.test.ts +++ b/packages/opentelemetry-web/test/WebTracer.test.ts @@ -21,6 +21,7 @@ import { BasicTracerConfig } from '@opentelemetry/tracing'; import { WebTracerConfig } from '../src'; import { StackScopeManager } from '../src/StackScopeManager'; import { WebTracer } from '../src/WebTracer'; +import { ZoneScopeManager } from '@opentelemetry/scope-zone'; class DummyPlugin extends BasePlugin { patch() {} @@ -78,5 +79,59 @@ describe('WebTracer', () => { tracer = new WebTracer({}); }); }); + + describe('when scopeManager is "ZoneScopeManager"', () => { + it('should correctly return the scopes for 3 parallel actions', () => { + const webTracerWithZone = new WebTracer({ + scopeManager: new ZoneScopeManager(), + }); + + const rootSpan = webTracerWithZone.startSpan('rootSpan'); + + webTracerWithZone.withSpan(rootSpan, () => { + assert.ok( + webTracerWithZone.getCurrentSpan() === rootSpan, + 'Current span is rootSpan' + ); + const concurrentSpan1 = webTracerWithZone.startSpan( + 'concurrentSpan1' + ); + const concurrentSpan2 = webTracerWithZone.startSpan( + 'concurrentSpan2' + ); + + const concurrentSpan3 = webTracerWithZone.startSpan( + 'concurrentSpan3' + ); + + webTracerWithZone.withSpan(concurrentSpan1, () => { + setTimeout(() => { + assert.ok( + webTracerWithZone.getCurrentSpan() === concurrentSpan1, + 'Current span is concurrentSpan1' + ); + }, 10); + }); + + webTracerWithZone.withSpan(concurrentSpan2, () => { + setTimeout(() => { + assert.ok( + webTracerWithZone.getCurrentSpan() === concurrentSpan2, + 'Current span is concurrentSpan2' + ); + }, 20); + }); + + webTracerWithZone.withSpan(concurrentSpan3, () => { + setTimeout(() => { + assert.ok( + webTracerWithZone.getCurrentSpan() === concurrentSpan3, + 'Current span is concurrentSpan3' + ); + }, 30); + }); + }); + }); + }); }); }); From 380a2e7bf6fe9ba1582c0d2ccbbd576d644025f8 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Fri, 1 Nov 2019 23:07:52 +0100 Subject: [PATCH 15/17] chore: updating example for web tracer with zone scope manager --- examples/tracer-web/index.js | 66 ++++++++++++------------------------ 1 file changed, 22 insertions(+), 44 deletions(-) diff --git a/examples/tracer-web/index.js b/examples/tracer-web/index.js index 003eb1c384e..6fb24930602 100644 --- a/examples/tracer-web/index.js +++ b/examples/tracer-web/index.js @@ -20,29 +20,11 @@ webTracerWithZone.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExport console.log('Current span is window', webTracerWithZone.getCurrentSpan() === window); -const span1 = webTracerWithZone.startSpan('foo1'); -webTracerWithZone.withSpan(span1, () => { - console.log('Current span is span1', webTracerWithZone.getCurrentSpan() === span1); - - setTimeout(() => { - const span2 = webTracerWithZone.startSpan('foo2'); - console.log('Current span is span1', webTracerWithZone.getCurrentSpan() === span1); - webTracerWithZone.withSpan(span2, () => { - console.log('Current span is span2', webTracerWithZone.getCurrentSpan() === span2); - setTimeout(() => { - console.log('Current span is span2', webTracerWithZone.getCurrentSpan() === span2); - }, 500); - }); - // there is a timeout which still keeps span2 active - console.log('Current span is span2', webTracerWithZone.getCurrentSpan() === span2); - }, 500); - console.log('Current span is span1', webTracerWithZone.getCurrentSpan() === span1); -}); - // example of keeping track of scope between async operations let counter = 0; const prepareClickEvent = () => { - const url = 'https://raw.githubusercontent.com/open-telemetry/opentelemetry-js/master/package.json'; + 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'; const element = document.getElementById('button1'); let mainSpan = webTracerWithZone.startSpan('main-span'); @@ -53,32 +35,28 @@ const prepareClickEvent = () => { const span1 = webTracerWithZone.startSpan(`files-series-info-${counter}`, { parent: webTracerWithZone.getCurrentSpan() }); + counter++; + const span2 = webTracerWithZone.startSpan(`files-series-info-${counter}`, { + parent: webTracerWithZone.getCurrentSpan() + }); - webTracerWithZone.withSpan(span1, ()=> { - span1.addEvent('fetching-data-1'); - getData(url).then((data)=> { - console.log(webTracerWithZone.getCurrentSpan().name, data.description, data.version); - webTracerWithZone.getCurrentSpan().addEvent('fetching-data-1-completed'); - webTracerWithZone.getCurrentSpan().addEvent('fetching-data-2'); - - getData(url).then((data)=> { - console.log(webTracerWithZone.getCurrentSpan().name, data.description, data.version); + webTracerWithZone.withSpan(span1, () => { + getData(url1).then((data) => { + console.log('current span is span1', webTracerWithZone.getCurrentSpan() === span1); + console.log('info from package.json', data.description, data.version); + webTracerWithZone.getCurrentSpan().addEvent('fetching-span1-completed'); + span1.end(); + }); + }); - webTracerWithZone.getCurrentSpan().addEvent('fetching-data-2-completed'); - webTracerWithZone.getCurrentSpan().end(); - counter++ - ;const span2 = webTracerWithZone.startSpan(`files-series-info-${counter}`, { - parent: webTracerWithZone.getCurrentSpan() - }); - webTracerWithZone.withSpan(span2, ()=> { - webTracerWithZone.getCurrentSpan().addEvent('fetching-data-3'); - getData(url).then((data)=> { - console.log(webTracerWithZone.getCurrentSpan().name, data.description, data.version); - webTracerWithZone.getCurrentSpan().addEvent('fetching-data-3-completed'); - webTracerWithZone.getCurrentSpan().end(); - }); - }); - }); + webTracerWithZone.withSpan(span2, () => { + getData(url2).then((data) => { + setTimeout(() => { + console.log('current span is span2', webTracerWithZone.getCurrentSpan() === span2); + console.log('info from package.json', data.description, data.version); + webTracerWithZone.getCurrentSpan().addEvent('fetching-span2-completed'); + span2.end(); + }, 100); }); }); }; From 21bf8341042bee8a660629077be0331d451ecbce Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Fri, 1 Nov 2019 23:08:34 +0100 Subject: [PATCH 16/17] chore: updating example for web tracer with zone scope manager --- examples/tracer-web/index.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/tracer-web/index.js b/examples/tracer-web/index.js index 6fb24930602..0e5e9956e33 100644 --- a/examples/tracer-web/index.js +++ b/examples/tracer-web/index.js @@ -21,7 +21,6 @@ webTracerWithZone.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExport console.log('Current span is window', webTracerWithZone.getCurrentSpan() === window); // example of keeping track of scope between async operations -let counter = 0; 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'; @@ -31,12 +30,11 @@ const prepareClickEvent = () => { webTracerWithZone.bind(element, mainSpan); const onClick = () => { - counter++; - const span1 = webTracerWithZone.startSpan(`files-series-info-${counter}`, { + const span1 = webTracerWithZone.startSpan(`files-series-info-1`, { parent: webTracerWithZone.getCurrentSpan() }); - counter++; - const span2 = webTracerWithZone.startSpan(`files-series-info-${counter}`, { + + const span2 = webTracerWithZone.startSpan(`files-series-info-2`, { parent: webTracerWithZone.getCurrentSpan() }); From 9782a033c34a594f24169100fb57b8aa3df7d34f Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Tue, 5 Nov 2019 13:26:56 +0100 Subject: [PATCH 17/17] chore: adding test for parallel run --- .../test/ZoneScopeManager.test.ts | 39 +++++++++++++++++++ .../opentelemetry-web/test/WebTracer.test.ts | 14 +------ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/packages/opentelemetry-scope-zone-peer-dep/test/ZoneScopeManager.test.ts b/packages/opentelemetry-scope-zone-peer-dep/test/ZoneScopeManager.test.ts index 5bfa1666e9c..490d5bfe455 100644 --- a/packages/opentelemetry-scope-zone-peer-dep/test/ZoneScopeManager.test.ts +++ b/packages/opentelemetry-scope-zone-peer-dep/test/ZoneScopeManager.test.ts @@ -129,6 +129,45 @@ describe('ZoneScopeManager', () => { }); assert.strictEqual(scopeManager.active(), window); }); + it('should correctly return the scopes for 3 parallel actions', () => { + const rootSpan = { name: 'rootSpan' }; + scopeManager.with(rootSpan, () => { + assert.ok( + scopeManager.active() === rootSpan, + 'Current span is rootSpan' + ); + const concurrentSpan1 = { name: 'concurrentSpan1' }; + const concurrentSpan2 = { name: 'concurrentSpan2' }; + const concurrentSpan3 = { name: 'concurrentSpan3' }; + + scopeManager.with(concurrentSpan1, () => { + setTimeout(() => { + assert.ok( + scopeManager.active() === concurrentSpan1, + 'Current span is concurrentSpan1' + ); + }, 10); + }); + + scopeManager.with(concurrentSpan2, () => { + setTimeout(() => { + assert.ok( + scopeManager.active() === concurrentSpan2, + 'Current span is concurrentSpan2' + ); + }, 20); + }); + + scopeManager.with(concurrentSpan3, () => { + setTimeout(() => { + assert.ok( + scopeManager.active() === concurrentSpan3, + 'Current span is concurrentSpan3' + ); + }, 30); + }); + }); + }); }); describe('.bind(function)', () => { diff --git a/packages/opentelemetry-web/test/WebTracer.test.ts b/packages/opentelemetry-web/test/WebTracer.test.ts index ddbb2eda8a9..75580b2515f 100644 --- a/packages/opentelemetry-web/test/WebTracer.test.ts +++ b/packages/opentelemetry-web/test/WebTracer.test.ts @@ -81,7 +81,7 @@ describe('WebTracer', () => { }); describe('when scopeManager is "ZoneScopeManager"', () => { - it('should correctly return the scopes for 3 parallel actions', () => { + it('should correctly return the scopes for 2 parallel actions', () => { const webTracerWithZone = new WebTracer({ scopeManager: new ZoneScopeManager(), }); @@ -100,10 +100,6 @@ describe('WebTracer', () => { 'concurrentSpan2' ); - const concurrentSpan3 = webTracerWithZone.startSpan( - 'concurrentSpan3' - ); - webTracerWithZone.withSpan(concurrentSpan1, () => { setTimeout(() => { assert.ok( @@ -122,14 +118,6 @@ describe('WebTracer', () => { }, 20); }); - webTracerWithZone.withSpan(concurrentSpan3, () => { - setTimeout(() => { - assert.ok( - webTracerWithZone.getCurrentSpan() === concurrentSpan3, - 'Current span is concurrentSpan3' - ); - }, 30); - }); }); }); });