├── .asf.yaml ├── .github └── workflows │ ├── build.yml │ └── e2e.yml ├── .gitignore ├── .licenserc.yaml ├── .npmrc ├── .prettierrc ├── CHANGES.md ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── dist └── licenses │ └── LICENSES-js-base64.txt ├── index.js ├── package-lock.json ├── package.json ├── release.md ├── src ├── errors │ ├── ajax.ts │ ├── frames.ts │ ├── index.ts │ ├── js.ts │ ├── promise.ts │ ├── resource.ts │ └── vue.ts ├── index.ts ├── monitor.ts ├── performance │ ├── fmp.ts │ ├── index.ts │ ├── perf.ts │ └── type.ts ├── services │ ├── apiDetectSupported.ts │ ├── base.ts │ ├── bfcache.ts │ ├── constant.ts │ ├── eventsListener.ts │ ├── getEntries.ts │ ├── getVisibilityObserver.ts │ ├── interactions.ts │ ├── observe.ts │ ├── report.ts │ ├── task.ts │ ├── types.ts │ └── uuid.ts ├── trace │ ├── interceptors │ │ ├── fetch.ts │ │ └── xhr.ts │ ├── segment.ts │ └── type.ts └── types.ts ├── test ├── base-compose.yml ├── docker-compose.yml ├── docker │ ├── Dockerfile.generate-traffic │ ├── Dockerfile.provider │ ├── Dockerfile.test-ui │ ├── nginx.conf │ ├── provider.py │ └── test.py ├── e2e.yaml ├── env └── expected │ ├── dependency.yml │ ├── error-log.yml │ ├── metrics-has-value-percentile.yml │ ├── metrics-has-value.yml │ ├── page.yml │ ├── service.yml │ ├── trace-detail.yml │ ├── traces.yml │ └── version.yml ├── tsconfig.json ├── tslint.json └── webpack.config.js /.asf.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | github: 19 | description: Client-side JavaScript exception and tracing library for Apache SkyWalking APM. 20 | homepage: https://skywalking.apache.org/ 21 | labels: 22 | - skywalking 23 | - observability 24 | - apm 25 | - distributed-tracing 26 | - dapper 27 | - javascript 28 | - web-performance 29 | enabled_merge_buttons: 30 | squash: true 31 | merge: false 32 | rebase: false 33 | protected_branches: 34 | master: 35 | required_status_checks: 36 | strict: true 37 | contexts: 38 | - build 39 | required_pull_request_reviews: 40 | dismiss_stale_reviews: true 41 | required_approving_review_count: 1 42 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | name: Node CI 18 | 19 | on: 20 | pull_request: 21 | push: 22 | branches: 23 | - master 24 | 25 | jobs: 26 | check-license: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v2 30 | - name: Check License 31 | uses: apache/skywalking-eyes@9bd5feb86b5817aa6072b008f9866a2c3bbc8587 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | build-and-test: 35 | runs-on: ubuntu-latest 36 | strategy: 37 | matrix: 38 | node-version: [18.x, 20.x] 39 | steps: 40 | - uses: actions/checkout@v1 41 | - name: Use Node.js ${{ matrix.node-version }} 42 | uses: actions/setup-node@v1 43 | with: 44 | node-version: ${{ matrix.node-version }} 45 | - name: npm install and build 46 | run: | 47 | npm ci 48 | npm run build --if-present 49 | env: 50 | CI: true 51 | 52 | build: 53 | runs-on: ubuntu-latest 54 | timeout-minutes: 90 55 | needs: [check-license, build-and-test] 56 | steps: 57 | - name: Placeholder 58 | run: echo "Just to make the GitHub merge button green" 59 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | name: E2E 18 | 19 | on: 20 | pull_request: 21 | push: 22 | branches: 23 | - master 24 | 25 | jobs: 26 | JavaScriptClient: 27 | name: JavaScript Client 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v2 31 | with: 32 | submodules: true 33 | - name: Setup go 34 | uses: actions/setup-go@v3 35 | with: 36 | go-version: '1.18' 37 | - uses: apache/skywalking-infra-e2e@cf589b4a0b9f8e6f436f78e9cfd94a1ee5494180 38 | with: 39 | e2e-file: test/e2e.yaml 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # production 5 | /lib 6 | 7 | # misc 8 | .DS_Store 9 | 10 | .idea 11 | *.iml 12 | 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | yarn.lock* 17 | 18 | # release 19 | release/ -------------------------------------------------------------------------------- /.licenserc.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one 3 | # or more contributor license agreements. See the NOTICE file 4 | # distributed with this work for additional information 5 | # regarding copyright ownership. The ASF licenses this file 6 | # to you under the Apache License, Version 2.0 (the 7 | # "License"); you may not use this file except in compliance 8 | # with the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, 13 | # software distributed under the License is distributed on an 14 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | # KIND, either express or implied. See the License for the 16 | # specific language governing permissions and limitations 17 | # under the License. 18 | # 19 | header: 20 | license: 21 | spdx-id: Apache-2.0 22 | copyright-owner: Apache Software Foundation 23 | 24 | paths-ignore: 25 | - 'lib' 26 | - 'dist' 27 | - 'LICENSE' 28 | - 'NOTICE' 29 | - '.gitignore' 30 | - '.prettierrc' 31 | - '.browserslistrc' 32 | - '**/*.md' 33 | - '**/*.json' 34 | 35 | comment: on-failure 36 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 17 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "printWidth": 120, 4 | "tabWidth": 2, 5 | "semi": true, 6 | "singleQuote": true, 7 | "bracketSpacing": true, 8 | "arrowParens": "always" 9 | } 10 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## 1.0.0 4 | 5 | 1. Monitor Core Web Vitals. 6 | 2. Monitor static resource metrics. 7 | 3. Bump up `infra-e2e`. 8 | 4. Bump dependencies to fix vulnerabilities. 9 | 5. Adjust readme for 1.0 release. 10 | 6. Fix can't catch the resource error. 11 | 7. Fix append http method to tags error. 12 | 8. Bump up `test ui`. 13 | 9. Fix the caught fetch request does not work when it receives a URL. 14 | 15 | ## 0.12.0 16 | 1. Fix native fetch implementation when using Request object. 17 | 2. Fix fetch implementation when using the Header object in http queries. 18 | 3. Bump dependencies. 19 | 20 | ## 0.11.0 21 | 22 | 1. Fixed the bug that navigator.sendBeacon sent json to backend report "No suitable request converter found for a @RequestObject List". 23 | 2. Fix reading property from null. 24 | 3. Pin selenium version and update license CI. 25 | 4. Bump dependencies. 26 | 5. Update README. 27 | 28 | ## 0.10.0 29 | 30 | 1. Fix the ability of Fetch constructure. 31 | 2. Update README. 32 | 3. Bump dependencies. 33 | 34 | ## 0.9.0 35 | 36 | 1. Fix custom configurations when the page router changed for SPA. 37 | 2. Fix reporting data by navigator.sendbeacon when pages is closed. 38 | 3. Bump dependencies. 39 | 4. Add Security Notice. 40 | 5. Support adding custom tags to spans. 41 | 6. Validate custom parameters for register. 42 | 43 | ## 0.8.0 44 | 45 | 1. Fix fmp metric. 46 | 2. Add e2e tese based on skywaling-infra-e2e. 47 | 3. Update metric and events. 48 | 4. Remove ServiceTag by following SkyWalking v9 new layer model. 49 | 50 | ## 0.7.0 51 | 52 | 1. Support setting time interval to report segments. 53 | 2. Fix segments report only send once. 54 | 3. Fix apache/skywalking#7335. 55 | 4. Fix apache/skywalking#7793. 56 | 5. Fix firstReportedError for SPA. 57 | 58 | ## 0.6.0 59 | 60 | 1. Separate production and development environments when building. 61 | 2. Upgrade packages to fix vulnerabilities. 62 | 3. Fix headers could be null . 63 | 4. Fix catching errors for http requests. 64 | 5. Fix the firstReportedError is calculated with more types of errors. 65 | 66 | ## 0.5.1 67 | 68 | 1. Add `noTraceOrigins` option. 69 | 2. Fix wrong URL when using relative path. 70 | 3. Catch frames errors. 71 | 4. Get `response.body` as a stream with the fetch API. 72 | 5. Support reporting multiple logs. 73 | 6. Support typescript project. 74 | 75 | ## 0.4.0 76 | 77 | 1. Update stack and message in logs. 78 | 2. Fix wrong URL when using relative path in xhr. 79 | 80 | ## 0.3.0 81 | 82 | 1. Support tracing starting at the browser. 83 | 2. Add traceSDKInternal SDK for tracing SDK internal RPC. 84 | 3. Add detailMode SDK for tracing http method and url as tags in spans. 85 | 4. Fix conditions of http status. 86 | 87 | ## 0.2.0 88 | 89 | 1. Fix: `secureConnectionStart` is zero bug. 90 | 2. Fix: `response.status` judge bug. 91 | 92 | ## 0.1.0 93 | 94 | 1. Establish the browser exception and tracing core. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | SHELL := /bin/bash -o pipefail 17 | 18 | VERSION ?= latest 19 | RELEASE_SRC = skywalking-client-js-${VERSION}-src 20 | 21 | GPG_UID := 22 | 23 | # set gpg user id 24 | ifdef GPG_UID 25 | GPG_UID_FLAG := -u $(GPG_UID) 26 | endif 27 | 28 | .PHONY: release-src 29 | release-src: 30 | tar -zcvf $(RELEASE_SRC).tgz \ 31 | --exclude .git/ \ 32 | --exclude .idea/ \ 33 | --exclude .gitignore \ 34 | --exclude .DS_Store \ 35 | --exclude .github \ 36 | --exclude lib \ 37 | --exclude node_modules \ 38 | --exclude release \ 39 | --exclude $(RELEASE_SRC).tgz \ 40 | . 41 | 42 | gpg $(GPG_UID_FLAG) --batch --yes --armor --detach-sig $(RELEASE_SRC).tgz 43 | shasum -a 512 $(RELEASE_SRC).tgz > $(RELEASE_SRC).tgz.sha512 44 | 45 | mkdir -p release 46 | mv $(RELEASE_SRC).tgz release/$(RELEASE_SRC).tgz 47 | mv $(RELEASE_SRC).tgz.asc release/$(RELEASE_SRC).tgz.asc 48 | mv $(RELEASE_SRC).tgz.sha512 release/$(RELEASE_SRC).tgz.sha512 49 | 50 | .PHONY: install 51 | install: 52 | npm install 53 | 54 | .PHONY: clean 55 | clean: 56 | rm -rf ./node_modules && \ 57 | rm -rf ./lib 58 | 59 | .PHONY: build 60 | build: 61 | npm run build 62 | 63 | .PHONY: rebuild 64 | rebuild: 65 | npm run rebuild 66 | 67 | .PHONY: publish 68 | publish: rebuild 69 | npm publish 70 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Apache SkyWalking 2 | Copyright 2017-2025 The Apache Software Foundation 3 | 4 | This product includes software developed at 5 | The Apache Software Foundation (http://www.apache.org/). -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Apache SkyWalking Client JS 2 | ========== 3 | 4 | Sky Walking logo 5 | 6 | [Apache SkyWalking](https://github.com/apache/skywalking) Client-side JavaScript exception and tracing library. 7 | - Provide metrics and error collection to SkyWalking backend. 8 | - Lightweight 9 | - Make browser as a start of whole distributed tracing 10 | 11 | NOTICE, SkyWalking Client JS 1.0+ and later versions require SkyWalking 10.2+. 12 | # Usage 13 | 14 | ## Install 15 | The `skywalking-client-js` runtime library is available at [npm](https://www.npmjs.com/package/skywalking-client-js). 16 | 17 | ``` 18 | npm install skywalking-client-js --save 19 | ``` 20 | 21 | ## Quick Start 22 | 23 | **`skywalking-client-js` requires SkyWalking v10, and 10.2+ provides complete features.** 24 | 25 | User could use `register` method to load and report data automatically. 26 | 27 | ```js 28 | import ClientMonitor from 'skywalking-client-js'; 29 | ``` 30 | ```js 31 | // Report collected data to `http:// + window.location.host + /browser/perfData` in default 32 | ClientMonitor.register({ 33 | # Use core/default/restPort in the OAP server. 34 | # If External Communication Channels are activated, `receiver-sharing-server/default/restPort`, 35 | # ref to https://skywalking.apache.org/docs/main/latest/en/setup/backend/backend-expose/ 36 | collector: 'http://127.0.0.1:12800', 37 | service: 'test-ui', 38 | pagePath: '/current/page/name', 39 | serviceVersion: 'v1.0.0', 40 | }); 41 | ``` 42 | 43 | ### Parameters 44 | The register method supports the following parameters. 45 | 46 | |Parameter|Type|Description|Required|Default Value| 47 | |----|----|----|----|----| 48 | |collector|String|In default, the collected data would be reported to current domain(`/browser/perfData`. Then, typically, we recommend you use a Gateway/proxy to redirect the data to the OAP(`resthost:restport`). If you set this, the data could be reported to another domain, NOTE [the Cross-Origin Resource Sharing (CORS) issuse and solution](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). |false|-| 49 | |service|String|project ID.|true|-| 50 | |serviceVersion|String|project verison|true|-| 51 | |pagePath|String|project path|true|-| 52 | |jsErrors|Boolean|Support js errors monitoring|false|true| 53 | |apiErrors|Boolean|Support API errors monitoring|false|true| 54 | |resourceErrors|Boolean|Support resource errors monitoring|false|true| 55 | |useFmp|Boolean|Collect FMP (first meaningful paint) data of the first screen. Deprecated: This is no longer recommended. Please use the `useWebVitals` instead. |false|false| 56 | |enableSPA|Boolean|Monitor the page hashchange event and report PV, which is suitable for [single page application scenarios](https://github.com/apache/skywalking-client-js#spa-page). |false|false| 57 | |autoTracePerf|Boolean|Support sending of performance data automatically.|false|true| 58 | |vue|Vue|Support vue2 errors monitoring. Deprecated: This is no longer recommended. Please use the [Catching errors in frames](https://github.com/apache/skywalking-client-js#catching-errors-in-frames-including-react-angular-vue) scenario instead. |false|undefined| 59 | |traceSDKInternal|Boolean|Support tracing SDK internal RPC.|false|false| 60 | |detailMode|Boolean|Support tracing http method and url as tags in spans.|false|true| 61 | |noTraceOrigins|(string \| RegExp)[]|Origin in the `noTraceOrigins` list will not be traced.|false|[]| 62 | |traceTimeInterval|Number|Support setting time interval to report segments.|false|60000| 63 | |customTags|Array|Custom Tags|false|-| 64 | |useWebVitals|Boolean|Collect three core web vitals. NOTE, Safari does not support all core web vitals, and Firefox does not support `CLS`.|false|false| 65 | 66 | ## Collect Metrics Manually 67 | Use the `setPerformance` method to report metrics at the moment of page loaded or any other moment meaningful. 68 | 69 | 1. Set the SDK configuration item autoTracePerf to false to turn off automatic reporting performance metrics and wait for manual triggering of escalation. 70 | 2. Call `ClientMonitor.setPerformance(object)` method to report 71 | 72 | - Examples 73 | ```js 74 | import ClientMonitor from 'skywalking-client-js'; 75 | 76 | ClientMonitor.setPerformance({ 77 | collector: 'http://127.0.0.1:12800', 78 | service: 'browser-app', 79 | serviceVersion: '1.0.0', 80 | pagePath: location.href, 81 | useWebVitals: true 82 | }); 83 | ``` 84 | 85 | ## Special scene 86 | 87 | ### SPA Page 88 | In spa (single page application) single page application, the page will be refreshed only once. The traditional method only reports PV once after the page loading, but cannot count the PV of each sub-page, and can't make other types of logs aggregate by sub-page. 89 | The SDK provides two processing methods for spa pages: 90 | 91 | 1. Enable spa automatic parsing 92 | This method is suitable for most single page application scenarios with URL hash as the route. 93 | In the initialized configuration item, set enableSPA to true, which will turn on the page's hashchange event listening (trigger re reporting PV), and use URL hash as the page field in other data reporting. 94 | 2. Manual reporting 95 | This method can be used in all single page application scenarios. This method can be used if the first method is invalid. 96 | The SDK provides a set page method to manually update the page name when data is reported. When this method is called, the page PV will be re reported by default. For details, see setPerformance(). 97 | ```js 98 | app.on('routeChange', function (next) { 99 | ClientMonitor.setPerformance({ 100 | collector: 'http://127.0.0.1:12800', 101 | service: 'browser-app', 102 | serviceVersion: '1.0.0', 103 | pagePath: location.href, 104 | useWebVitals: true 105 | }); 106 | }); 107 | ``` 108 | 109 | ## Tracing range of data requests in the browser 110 | 111 | Support tracking these([XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) and [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)) two modes of data requests. At the same time, Support tracking libraries and tools that base on XMLHttpRequest and fetch, such as [Axios](https://github.com/axios/axios), [SuperAgent](https://github.com/visionmedia/superagent), [OpenApi](https://www.openapis.org/) and so on. 112 | 113 | ## Catching errors in frames, including React, Angular, Vue. 114 | 115 | ```js 116 | // Angular 117 | import { ErrorHandler } from '@angular/core'; 118 | import ClientMonitor from 'skywalking-client-js'; 119 | export class AppGlobalErrorhandler implements ErrorHandler { 120 | handleError(error) { 121 | ClientMonitor.reportFrameErrors({ 122 | collector: 'http://127.0.0.1:12800', 123 | service: 'angular-demo', 124 | pagePath: '/app', 125 | serviceVersion: 'v1.0.0', 126 | }, error); 127 | } 128 | } 129 | @NgModule({ 130 | ... 131 | providers: [{provide: ErrorHandler, useClass: AppGlobalErrorhandler}] 132 | }) 133 | class AppModule {} 134 | ``` 135 | 136 | ```js 137 | // React 138 | class ErrorBoundary extends React.Component { 139 | constructor(props) { 140 | super(props); 141 | this.state = { hasError: false }; 142 | } 143 | 144 | static getDerivedStateFromError(error) { 145 | // Update state so the next render will show the fallback UI. 146 | return { hasError: true }; 147 | } 148 | 149 | componentDidCatch(error, errorInfo) { 150 | // You can also log the error to an error reporting service 151 | ClientMonitor.reportFrameErrors({ 152 | collector: 'http://127.0.0.1:12800', 153 | service: 'react-demo', 154 | pagePath: '/app', 155 | serviceVersion: 'v1.0.0', 156 | }, error); 157 | } 158 | 159 | render() { 160 | if (this.state.hasError) { 161 | // You can render any custom fallback UI 162 | return

Something went wrong.

; 163 | } 164 | 165 | return this.props.children; 166 | } 167 | } 168 | 169 | 170 | 171 | ``` 172 | 173 | ```js 174 | // Vue 175 | Vue.config.errorHandler = (error) => { 176 | ClientMonitor.reportFrameErrors({ 177 | collector: 'http://127.0.0.1:12800', 178 | service: 'vue-demo', 179 | pagePath: '/app', 180 | serviceVersion: 'v1.0.0', 181 | }, error); 182 | } 183 | ``` 184 | 185 | ## According to different pages or modules, add custom tags to spans. 186 | 187 | ```js 188 | app.on('routeChange', function () { 189 | ClientMonitor.setCustomTags([ 190 | { key: 'key1', value: 'value1' }, 191 | { key: 'key2', value: 'value2' }, 192 | ]); 193 | }); 194 | ``` 195 | 196 | # Security Notice 197 | The SkyWalking client-js agent would be deployed and running outside of your datacenter. This means when you introduce this component you should be aware of the security impliciations. 198 | There are various kinds of telemetry relative data would be reported to backend separately or through your original HTTP requests. 199 | 200 | In order to implement **distributed tracing from the browser**, an HTTP header with the name `sw8` will be added to HTTP requests 201 | according to [Cross Process Propagation Headers Protocol v3](https://skywalking.apache.org/docs/main/latest/en/api/x-process-propagation-headers-v3/). 202 | `client-js` will also report spans and browser telemetry data through [Trace Data Protocol v3](https://skywalking.apache.org/docs/main/latest/en/api/trace-data-protocol-v3/) and 203 | [Browser Protocol](https://skywalking.apache.org/docs/main/latest/en/api/browser-protocol/). 204 | 205 | Because all of this data is reported from an unsecured environment, users should make sure to: 206 | 1. Not expose OAP server to the internet directly. 207 | 1. Set up TLS/HTTPs between browser and OAP server. 208 | 1. Set up authentification(such as TOKEN based) for client-js reporting. 209 | 1. Validate all fields in the body of the HTTP headers and telemetry data mentioned above to detect and reject malicious data. Without such protections, an attacker could embed executable Javascript code in those fields, causing XSS or even Remote Code Execution (RCE) issues. 210 | 211 | Please consult your security team before introducing this feature in your production environment. Don't expose the OAP server's IP/port(s) and URI without a security audit. 212 | 213 | # Demo project 214 | 215 | Demo project provides instrumented web application with necessary environment, you could just simple use it to see the data SkyWalking collected and how SkyWalking visualizes on the UI. 216 | See more information, [click here](https://github.com/SkyAPMTest/skywalking-client-test). 217 | 218 | # Contact Us 219 | * Submit an [issue](https://github.com/apache/skywalking/issues) 220 | * Mail list: **dev@skywalking.apache.org**. Mail to `dev-subscribe@skywalking.apache.org`, follow the reply to subscribe the mail list. 221 | * Join `#skywalking` channel at [Apache Slack](https://join.slack.com/t/the-asf/shared_invite/enQtNzc2ODE3MjI1MDk1LTAyZGJmNTg1NWZhNmVmOWZjMjA2MGUyOGY4MjE5ZGUwOTQxY2Q3MDBmNTM5YTllNGU4M2QyMzQ4M2U4ZjQ5YmY). If the linke is not working, find the latest one at [Apache INFRA WIKI](https://cwiki.apache.org/confluence/display/INFRA/Slack+Guest+Invites). 222 | 223 | # Release Guide 224 | All committers should follow [Release Guide](release.md) to publish the official release. 225 | 226 | # License 227 | Apache 2.0 228 | -------------------------------------------------------------------------------- /dist/licenses/LICENSES-js-base64.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Dan Kogai 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of {{{project}}} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import './lib/index'; 19 | 20 | export default ClientMonitor; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "skywalking-client-js", 3 | "version": "1.0.0", 4 | "description": "Client-side JavaScript exception and tracing library for Apache SkyWalking APM", 5 | "main": "index.js", 6 | "types": "lib/src/index.ts", 7 | "repository": "apache/skywalking-client-js", 8 | "homepage": "skywalking.apache.org", 9 | "license": "Apache 2.0", 10 | "bugs": { 11 | "url": "https://github.com/apache/skywalking/issues", 12 | "email": "dev@skywalking.apache.org" 13 | }, 14 | "devDependencies": { 15 | "cross-env": "^7.0.3", 16 | "husky": "^8.0.3", 17 | "lint-staged": "^13.2.1", 18 | "minimist": "^1.2.3", 19 | "prettier": "^2.1.1", 20 | "ts-loader": "^9.2.1", 21 | "tslint": "^5.20.1", 22 | "typescript": "^4.9.5", 23 | "webpack": "^5.75.0", 24 | "webpack-cli": "^5.0.1", 25 | "webpack-concat-files-plugin": "^0.5.2", 26 | "webpack-dev-server": "^5.2.1" 27 | }, 28 | "scripts": { 29 | "build": "cross-env NODE_ENV=production webpack", 30 | "rebuild": "rm -rf ./node_modules && rm -rf ./lib && npm install && npm run build", 31 | "start": "npx webpack serve", 32 | "release": "make release-src" 33 | }, 34 | "husky": { 35 | "hooks": { 36 | "pre-commit": "lint-staged" 37 | } 38 | }, 39 | "lint-staged": { 40 | "*.{ts,js}": [ 41 | "prettier --write", 42 | "git add" 43 | ] 44 | }, 45 | "files": [ 46 | "LICENSE", 47 | "NOTICE", 48 | "CHANGES.md", 49 | "README.md", 50 | "index.js", 51 | "lib/", 52 | "dist/licenses" 53 | ], 54 | "keywords": [ 55 | "skywalking", 56 | "observability", 57 | "apm", 58 | "distributed-tracing", 59 | "dapper", 60 | "javascript", 61 | "web-performance" 62 | ], 63 | "dependencies": { 64 | "js-base64": "^3.6.0" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /release.md: -------------------------------------------------------------------------------- 1 | # Release Guide 2 | All committer should follow these steps to do release for this repo. 3 | 4 | 1. Update the [package version](package.json) and [CHANGES.md](CHANGES.md) to prepare the official release. 5 | 6 | 2. Package the source release. 7 | 8 | ```shell 9 | > export VERSION=x.y.z 10 | > make release-src 11 | ``` 12 | 13 | Use SVN to upload the files(tgz, asc and sha512) in the `release` folder to `https://dist.apache.org/repos/dist/dev/skywalking/client-js/x.y.z`. 14 | 15 | 3. Make the internal announcements. Send an announcement mail in dev mail list. 16 | 17 | ``` 18 | [ANNOUNCE] SkyWalking Client JS x.y.z test build available 19 | 20 | The test build of x.y.z is available. 21 | 22 | We welcome any comments you may have, and will take all feedback into 23 | account if a quality vote is called for this build. 24 | 25 | Release notes: 26 | 27 | * https://github.com/apache/skywalking-client-js/blob/vx.y.z/CHANGES.md 28 | 29 | Release Candidate: 30 | 31 | * https://dist.apache.org/repos/dist/dev/skywalking/client-js/x.y.z/ 32 | * sha512 checksums 33 | - xxxxxxx skywalking-client-js-x.y.z-src.tgz 34 | 35 | Release Tag : 36 | 37 | * vx.y.z 38 | 39 | Release CommitID : 40 | 41 | * https://github.com/apache/skywalking-client-js/tree/xxxxxxxxxx 42 | 43 | Keys to verify the Release Candidate : 44 | 45 | * https://dist.apache.org/repos/dist/release/skywalking/KEYS 46 | 47 | 48 | A vote regarding the quality of this test build will be initiated 49 | within the next couple of days. 50 | ``` 51 | 52 | 4. Wait at least 48 hours for test responses. If there is a critical issue found and confirmed by the PMC, this release should be cancelled. 53 | 54 | 5. Call for a vote. Call a vote in dev@skywalking.apache.org 55 | 56 | ``` 57 | [VOTE] Release SkyWalking Client JS x.y.z 58 | 59 | This is a call for vote to release Apache SkyWalking Client JS version x.y.z. 60 | 61 | Release notes: 62 | 63 | * https://github.com/apache/skywalking-client-js/blob/vx.y.z/CHANGES.md 64 | 65 | Release Candidate: 66 | 67 | * https://dist.apache.org/repos/dist/dev/skywalking/client-js/x.y.z/ 68 | * sha512 checksums 69 | - xxxxxxx skywalking-client-js-x.y.z-src.tgz 70 | 71 | Release Tag : 72 | 73 | * vx.y.z 74 | 75 | Release CommitID : 76 | 77 | * https://github.com/apache/skywalking-client-js/tree/xxxxxxxxxx 78 | 79 | Keys to verify the Release Candidate : 80 | 81 | * https://dist.apache.org/repos/dist/release/skywalking/KEYS 82 | 83 | 84 | Voting will start now (xxxx date) and will remain open for at least 72 hours, Request all PMC members to give their vote. 85 | [ ] +1 Release this package. 86 | [ ] +0 No opinion. 87 | [ ] -1 Do not release this package because.... 88 | 89 | ``` 90 | 91 | 5. Publish release, if vote passed. 92 | 93 | Move the release from RC folder to the dist folder. This will begin the file sync across the global Apache mirrors. 94 | ``` 95 | > export SVN_EDITOR=vim 96 | > svn mv https://dist.apache.org/repos/dist/dev/skywalking/client-js/x.y.z https://dist.apache.org/repos/dist/release/skywalking/client-js 97 | .... 98 | enter your apache password 99 | .... 100 | ``` 101 | 102 | Send ANNOUNCE email to `dev@skywalking.apache.org`, `announce@apache.org`, the sender should use Apache email account. 103 | ``` 104 | Mail title: [ANNOUNCE] Release Apache SkyWalking Client JS version x.y.z 105 | 106 | Mail content: 107 | Hi all, 108 | 109 | Apache SkyWalking Team is glad to announce the first release of Apache SkyWalking Client JS x.y.z 110 | 111 | SkyWalking: APM (application performance monitor) tool for distributed systems, 112 | especially designed for microservices, cloud native and container-based (Docker, Kubernetes, Mesos) architectures. 113 | 114 | Skywalking Client Js provide browser metrics and error collection to SkyWalking backend. 115 | 116 | This release contains a number of new features, bug fixes and improvements compared to 117 | version a.b.c(last release). The notable changes since x.y.z include: 118 | 119 | (Highlight key changes) 120 | 1. ... 121 | 2. ... 122 | 3. ... 123 | 124 | Please refer to the change log for the complete list of changes: 125 | https://github.com/apache/skywalking-client-js/blob/x.y.z/CHANGES.md 126 | 127 | Apache SkyWalking website: 128 | http://skywalking.apache.org/ 129 | 130 | Downloads: 131 | http://skywalking.apache.org/downloads/ 132 | 133 | Twitter: 134 | https://twitter.com/ASFSkyWalking 135 | 136 | SkyWalking Resources: 137 | - GitHub: https://github.com/apache/skywalking 138 | - Issue: https://github.com/apache/skywalking/issues 139 | - Mailing list: dev@skywalkiing.apache.org 140 | 141 | 142 | - Apache SkyWalking Team 143 | ``` 144 | 145 | 6. publish package to NPM 146 | 147 | Login to NPM, the username is `apache-skywalking` and the password has been sent to `private@skwalking.apache.org`. 148 | 149 | ```bash 150 | > export NPM_TOKEN=npm_token_here 151 | > npm login 152 | > Username: apache-skywalking 153 | > Password: 154 | ``` 155 | 156 | Then publish the package to NPM. 157 | 158 | ``` 159 | > make publish 160 | ``` -------------------------------------------------------------------------------- /src/errors/ajax.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import uuid from '../services/uuid'; 19 | import Base from '../services/base'; 20 | import { GradeTypeEnum, ErrorsCategory, ReportTypes } from '../services/constant'; 21 | import { CustomReportOptions } from '../types'; 22 | 23 | class AjaxErrors extends Base { 24 | private infoOpt: CustomReportOptions = { 25 | service: '', 26 | pagePath: '', 27 | serviceVersion: '', 28 | }; 29 | // get http error info 30 | public handleError(options: CustomReportOptions) { 31 | // XMLHttpRequest Object 32 | if (!window.XMLHttpRequest) { 33 | return; 34 | } 35 | this.infoOpt = options; 36 | window.addEventListener( 37 | 'xhrReadyStateChange', 38 | (event: CustomEvent) => { 39 | const detail = event.detail; 40 | 41 | if (detail.readyState !== 4) { 42 | return; 43 | } 44 | if (detail.getRequestConfig[1] === options.collector + ReportTypes.ERRORS) { 45 | return; 46 | } 47 | if (detail.status !== 0 && detail.status < 400) { 48 | return; 49 | } 50 | 51 | this.logInfo = { 52 | ...this.infoOpt, 53 | uniqueId: uuid(), 54 | category: ErrorsCategory.AJAX_ERROR, 55 | grade: GradeTypeEnum.ERROR, 56 | errorUrl: detail.getRequestConfig[1], 57 | message: `status: ${detail.status}; statusText: ${detail.statusText};`, 58 | collector: options.collector, 59 | stack: detail.responseText, 60 | }; 61 | this.traceInfo(); 62 | }, 63 | ); 64 | } 65 | setOptions(opt: CustomReportOptions) { 66 | this.infoOpt = opt; 67 | } 68 | } 69 | 70 | export default new AjaxErrors(); 71 | -------------------------------------------------------------------------------- /src/errors/frames.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import uuid from '../services/uuid'; 19 | import Base from '../services/base'; 20 | import { GradeTypeEnum, ErrorsCategory } from '../services/constant'; 21 | import { CustomReportOptions } from '../types'; 22 | 23 | class FrameErrors extends Base { 24 | private infoOpt: CustomReportOptions = { 25 | service: '', 26 | pagePath: '', 27 | serviceVersion: '', 28 | }; 29 | public handleErrors(options: CustomReportOptions, error: Error) { 30 | this.infoOpt = options; 31 | this.logInfo = { 32 | ...this.infoOpt, 33 | uniqueId: uuid(), 34 | category: ErrorsCategory.JS_ERROR, 35 | grade: GradeTypeEnum.ERROR, 36 | errorUrl: error.name || location.href, 37 | message: error.message, 38 | collector: options.collector || location.origin, 39 | stack: error.stack, 40 | }; 41 | this.traceInfo(); 42 | } 43 | } 44 | export default new FrameErrors(); 45 | -------------------------------------------------------------------------------- /src/errors/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import JSErrors from './js'; 19 | import PromiseErrors from './promise'; 20 | import AjaxErrors from './ajax'; 21 | import ResourceErrors from './resource'; 22 | import VueErrors from './vue'; 23 | import FrameErrors from './frames'; 24 | 25 | export { JSErrors, PromiseErrors, AjaxErrors, ResourceErrors, VueErrors, FrameErrors }; 26 | -------------------------------------------------------------------------------- /src/errors/js.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import uuid from '../services/uuid'; 19 | import Base from '../services/base'; 20 | import { GradeTypeEnum, ErrorsCategory } from '../services/constant'; 21 | import { CustomReportOptions } from '../types'; 22 | class JSErrors extends Base { 23 | private infoOpt: CustomReportOptions = { 24 | service: '', 25 | pagePath: '', 26 | serviceVersion: '', 27 | }; 28 | public handleErrors(options: CustomReportOptions) { 29 | this.infoOpt = options; 30 | window.onerror = (message, url, line, col, error) => { 31 | this.logInfo = { 32 | ...this.infoOpt, 33 | uniqueId: uuid(), 34 | category: ErrorsCategory.JS_ERROR, 35 | grade: GradeTypeEnum.ERROR, 36 | errorUrl: url, 37 | line, 38 | col, 39 | message, 40 | collector: options.collector, 41 | stack: error ? error.stack : '', 42 | }; 43 | this.traceInfo(); 44 | }; 45 | } 46 | setOptions(opt: CustomReportOptions) { 47 | this.infoOpt = opt; 48 | } 49 | } 50 | export default new JSErrors(); 51 | -------------------------------------------------------------------------------- /src/errors/promise.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import uuid from '../services/uuid'; 19 | import Base from '../services/base'; 20 | import { GradeTypeEnum, ErrorsCategory } from '../services/constant'; 21 | import { CustomReportOptions } from '../types'; 22 | 23 | class PromiseErrors extends Base { 24 | private infoOpt: CustomReportOptions = { 25 | service: '', 26 | pagePath: '', 27 | serviceVersion: '', 28 | }; 29 | public handleErrors(options: CustomReportOptions) { 30 | this.infoOpt = options; 31 | window.addEventListener('unhandledrejection', (event) => { 32 | try { 33 | let url = ''; 34 | if (!event || !event.reason) { 35 | return; 36 | } 37 | if (event.reason.config && event.reason.config.url) { 38 | url = event.reason.config.url; 39 | } 40 | this.logInfo = { 41 | ...this.infoOpt, 42 | uniqueId: uuid(), 43 | category: ErrorsCategory.PROMISE_ERROR, 44 | grade: GradeTypeEnum.ERROR, 45 | errorUrl: url || location.href, 46 | message: event.reason.message, 47 | stack: event.reason.stack, 48 | collector: options.collector, 49 | }; 50 | 51 | this.traceInfo(); 52 | } catch (error) { 53 | console.log(error); 54 | } 55 | }); 56 | } 57 | setOptions(opt: CustomReportOptions) { 58 | this.infoOpt = opt; 59 | } 60 | } 61 | export default new PromiseErrors(); 62 | -------------------------------------------------------------------------------- /src/errors/resource.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import uuid from '../services/uuid'; 19 | import Base from '../services/base'; 20 | import { GradeTypeEnum, ErrorsCategory } from '../services/constant'; 21 | import { CustomReportOptions } from '../types'; 22 | 23 | class ResourceErrors extends Base { 24 | private infoOpt: CustomReportOptions = { 25 | service: '', 26 | pagePath: '', 27 | serviceVersion: '', 28 | }; 29 | public handleErrors(options: CustomReportOptions) { 30 | this.infoOpt = options; 31 | window.addEventListener('error', (event) => { 32 | try { 33 | if (!event) { 34 | return; 35 | } 36 | const target: any = event.target; 37 | const isElementTarget = 38 | target instanceof HTMLScriptElement || 39 | target instanceof HTMLLinkElement || 40 | target instanceof HTMLImageElement; 41 | 42 | if (!isElementTarget) { 43 | // return js error 44 | return; 45 | } 46 | this.logInfo = { 47 | ...this.infoOpt, 48 | uniqueId: uuid(), 49 | category: ErrorsCategory.RESOURCE_ERROR, 50 | grade: target.tagName === 'IMG' ? GradeTypeEnum.WARNING : GradeTypeEnum.ERROR, 51 | errorUrl: (target as HTMLScriptElement).src || (target as HTMLLinkElement).href || location.href, 52 | message: `load ${target.tagName} resource error`, 53 | collector: options.collector, 54 | stack: `load ${target.tagName} resource error`, 55 | }; 56 | this.traceInfo(); 57 | } catch (error) { 58 | throw error; 59 | } 60 | },true); 61 | } 62 | setOptions(opt: CustomReportOptions) { 63 | this.infoOpt = opt; 64 | } 65 | } 66 | export default new ResourceErrors(); 67 | -------------------------------------------------------------------------------- /src/errors/vue.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import uuid from '../services/uuid'; 19 | import Base from '../services/base'; 20 | import { GradeTypeEnum, ErrorsCategory } from '../services/constant'; 21 | import { CustomReportOptions } from '../types'; 22 | 23 | class VueErrors extends Base { 24 | private infoOpt: CustomReportOptions = { 25 | service: '', 26 | pagePath: '', 27 | serviceVersion: '', 28 | }; 29 | public handleErrors(options: CustomReportOptions, Vue: any) { 30 | this.infoOpt = options; 31 | if (!(Vue && Vue.config)) { 32 | return; 33 | } 34 | Vue.config.errorHandler = (error: Error, vm: any, info: string) => { 35 | try { 36 | this.logInfo = { 37 | ...this.infoOpt, 38 | uniqueId: uuid(), 39 | category: ErrorsCategory.VUE_ERROR, 40 | grade: GradeTypeEnum.ERROR, 41 | errorUrl: location.href, 42 | message: info, 43 | collector: options.collector, 44 | stack: error.stack, 45 | }; 46 | this.traceInfo(); 47 | } catch (error) { 48 | throw error; 49 | } 50 | }; 51 | } 52 | setOptions(opt: CustomReportOptions) { 53 | this.infoOpt = opt; 54 | } 55 | } 56 | 57 | export default new VueErrors(); 58 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import ClientMonitor from './monitor'; 19 | 20 | (window as any).ClientMonitor = ClientMonitor; 21 | 22 | export default ClientMonitor; 23 | -------------------------------------------------------------------------------- /src/monitor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import { CustomOptionsType, CustomReportOptions, TagOption } from './types'; 19 | import { JSErrors, PromiseErrors, AjaxErrors, ResourceErrors, VueErrors, FrameErrors } from './errors/index'; 20 | import tracePerf from './performance/index'; 21 | import traceSegment, { setConfig } from './trace/segment'; 22 | 23 | const ClientMonitor = { 24 | customOptions: { 25 | collector: location.origin, // report serve 26 | jsErrors: true, // vue, js and promise errors 27 | apiErrors: true, 28 | resourceErrors: true, 29 | autoTracePerf: true, // trace performance detail 30 | useWebVitals: false, 31 | enableSPA: false, 32 | traceSDKInternal: false, 33 | detailMode: true, 34 | noTraceOrigins: [], 35 | traceTimeInterval: 60000, // 1min 36 | } as CustomOptionsType, 37 | 38 | register(configs: CustomOptionsType) { 39 | this.customOptions = { 40 | ...this.customOptions, 41 | ...configs, 42 | }; 43 | this.validateOptions(); 44 | this.catchErrors(this.customOptions); 45 | if (!this.customOptions.enableSPA) { 46 | this.performance(this.customOptions); 47 | } 48 | 49 | traceSegment(this.customOptions); 50 | }, 51 | performance(configs: any) { 52 | tracePerf.getPerf(configs); 53 | if (configs.enableSPA) { 54 | // hash router 55 | window.addEventListener( 56 | 'hashchange', 57 | () => { 58 | tracePerf.getPerf(configs); 59 | }, 60 | false, 61 | ); 62 | } 63 | }, 64 | 65 | catchErrors(options: CustomOptionsType) { 66 | const { service, pagePath, serviceVersion, collector } = options; 67 | 68 | if (options.jsErrors) { 69 | JSErrors.handleErrors({ service, pagePath, serviceVersion, collector }); 70 | PromiseErrors.handleErrors({ service, pagePath, serviceVersion, collector }); 71 | if (options.vue) { 72 | VueErrors.handleErrors({ service, pagePath, serviceVersion, collector }, options.vue); 73 | } 74 | } 75 | if (options.apiErrors) { 76 | AjaxErrors.handleError({ service, pagePath, serviceVersion, collector }); 77 | } 78 | if (options.resourceErrors) { 79 | ResourceErrors.handleErrors({ service, pagePath, serviceVersion, collector }); 80 | } 81 | }, 82 | setPerformance(configs: CustomReportOptions) { 83 | // history router 84 | this.customOptions = { 85 | ...this.customOptions, 86 | ...configs, 87 | }; 88 | this.validateOptions(); 89 | this.performance(this.customOptions); 90 | const { service, pagePath, serviceVersion, collector } = this.customOptions; 91 | if (this.customOptions.jsErrors) { 92 | JSErrors.setOptions({ service, pagePath, serviceVersion, collector }); 93 | PromiseErrors.setOptions({ service, pagePath, serviceVersion, collector }); 94 | if (this.customOptions.vue) { 95 | VueErrors.setOptions({ service, pagePath, serviceVersion, collector }); 96 | } 97 | } 98 | if (this.customOptions.apiErrors) { 99 | AjaxErrors.setOptions({ service, pagePath, serviceVersion, collector }); 100 | } 101 | if (this.customOptions.resourceErrors) { 102 | ResourceErrors.setOptions({ service, pagePath, serviceVersion, collector }); 103 | } 104 | setConfig(this.customOptions); 105 | }, 106 | reportFrameErrors(configs: CustomReportOptions, error: Error) { 107 | FrameErrors.handleErrors(configs, error); 108 | }, 109 | validateTags(customTags?: TagOption[]) { 110 | if (!customTags) { 111 | return false; 112 | } 113 | if (!Array.isArray(customTags)) { 114 | this.customOptions.customTags = undefined; 115 | console.error('customTags error'); 116 | return false; 117 | } 118 | let isTags = true; 119 | for (const ele of customTags) { 120 | if (!(ele && ele.key && ele.value)) { 121 | isTags = false; 122 | } 123 | } 124 | if (!isTags) { 125 | this.customOptions.customTags = undefined; 126 | console.error('customTags error'); 127 | return false; 128 | } 129 | return true; 130 | }, 131 | validateOptions() { 132 | const { 133 | collector, 134 | service, 135 | pagePath, 136 | serviceVersion, 137 | jsErrors, 138 | apiErrors, 139 | resourceErrors, 140 | autoTracePerf, 141 | useWebVitals, 142 | enableSPA, 143 | traceSDKInternal, 144 | detailMode, 145 | noTraceOrigins, 146 | traceTimeInterval, 147 | customTags, 148 | vue, 149 | } = this.customOptions; 150 | this.validateTags(customTags); 151 | if (typeof collector !== 'string') { 152 | this.customOptions.collector = location.origin; 153 | } 154 | if (typeof service !== 'string') { 155 | this.customOptions.service = ''; 156 | } 157 | if (typeof pagePath !== 'string') { 158 | this.customOptions.pagePath = ''; 159 | } 160 | if (typeof serviceVersion !== 'string') { 161 | this.customOptions.serviceVersion = ''; 162 | } 163 | if (typeof jsErrors !== 'boolean') { 164 | this.customOptions.jsErrors = true; 165 | } 166 | if (typeof apiErrors !== 'boolean') { 167 | this.customOptions.apiErrors = true; 168 | } 169 | if (typeof resourceErrors !== 'boolean') { 170 | this.customOptions.resourceErrors = true; 171 | } 172 | if (typeof autoTracePerf !== 'boolean') { 173 | this.customOptions.autoTracePerf = true; 174 | } 175 | if (typeof useWebVitals !== 'boolean') { 176 | this.customOptions.useWebVitals = false; 177 | } 178 | if (typeof enableSPA !== 'boolean') { 179 | this.customOptions.enableSPA = false; 180 | } 181 | if (typeof traceSDKInternal !== 'boolean') { 182 | this.customOptions.traceSDKInternal = false; 183 | } 184 | if (typeof detailMode !== 'boolean') { 185 | this.customOptions.detailMode = true; 186 | } 187 | if (typeof detailMode !== 'boolean') { 188 | this.customOptions.detailMode = true; 189 | } 190 | if (!Array.isArray(noTraceOrigins)) { 191 | this.customOptions.noTraceOrigins = []; 192 | } 193 | if (typeof traceTimeInterval !== 'number') { 194 | this.customOptions.traceTimeInterval = 60000; 195 | } 196 | if (typeof vue !== 'function') { 197 | this.customOptions.vue = undefined; 198 | } 199 | }, 200 | setCustomTags(tags: TagOption[]) { 201 | const opt = { ...this.customOptions, customTags: tags }; 202 | if (this.validateTags(tags)) { 203 | setConfig(opt); 204 | } 205 | }, 206 | }; 207 | 208 | export default ClientMonitor; 209 | -------------------------------------------------------------------------------- /src/performance/fmp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { ICalScore, ElementList } from './type'; 18 | 19 | const getStyle = (element: Element | any, attr: any) => { 20 | if (window.getComputedStyle) { 21 | return window.getComputedStyle(element, null)[attr]; 22 | } else { 23 | return element.currentStyle[attr]; 24 | } 25 | }; 26 | // element weight for calculate score 27 | enum ELE_WEIGHT { 28 | SVG = 2, 29 | IMG = 2, 30 | CANVAS = 4, 31 | OBJECT = 4, 32 | EMBED = 4, 33 | VIDEO = 4, 34 | } 35 | 36 | const START_TIME: number = performance.now(); 37 | const IGNORE_TAG_SET: string[] = ['SCRIPT', 'STYLE', 'META', 'HEAD', 'LINK']; 38 | const LIMIT: number = 3000; 39 | const WW: number = window.innerWidth; 40 | const WH: number = window.innerHeight; 41 | const DELAY: number = 2000; // fmp retry interval 42 | 43 | class FMPTiming { 44 | public fmpTime: number = 0; 45 | private statusCollector: Array<{ time: number }> = []; // nodes change time 46 | private flag: boolean = true; 47 | private observer: MutationObserver = null; 48 | private callbackCount: number = 0; 49 | private entries: any = {}; 50 | 51 | constructor() { 52 | if (!performance || !performance.getEntries) { 53 | console.log('your browser do not support performance.getEntries'); 54 | return; 55 | } 56 | this.initObserver(); 57 | } 58 | private getFirstSnapShot() { 59 | const time: number = performance.now(); 60 | const $body: HTMLElement = document.body; 61 | if ($body) { 62 | this.setTag($body, this.callbackCount); 63 | } 64 | this.statusCollector.push({ 65 | time, 66 | }); 67 | } 68 | private initObserver() { 69 | this.getFirstSnapShot(); 70 | this.observer = new MutationObserver(() => { 71 | this.callbackCount += 1; 72 | const time = performance.now(); 73 | const $body: HTMLElement = document.body; 74 | if ($body) { 75 | this.setTag($body, this.callbackCount); 76 | } 77 | this.statusCollector.push({ 78 | time, 79 | }); 80 | }); 81 | // observe all child nodes 82 | this.observer.observe(document, { 83 | childList: true, 84 | subtree: true, 85 | }); 86 | this.calculateFinalScore(); 87 | } 88 | private calculateFinalScore() { 89 | if (!this.flag) { 90 | return; 91 | } 92 | if (!MutationObserver) { 93 | return; 94 | } 95 | if (this.checkNeedCancel(START_TIME)) { 96 | // cancel observer for dom change 97 | this.observer.disconnect(); 98 | this.flag = false; 99 | const res = this.getTreeScore(document.body); 100 | let tp: ICalScore = null; 101 | for (const item of res.dpss) { 102 | if (tp && tp.st) { 103 | if (tp.st < item.st) { 104 | tp = item; 105 | } 106 | } else { 107 | tp = item; 108 | } 109 | } 110 | // Get all of soures load time 111 | performance.getEntries().forEach((item: PerformanceResourceTiming) => { 112 | this.entries[item.name] = item.responseEnd; 113 | }); 114 | if (!tp) { 115 | return false; 116 | } 117 | const resultEls: ElementList = this.filterResult(tp.els); 118 | this.fmpTime = this.getFmpTime(resultEls); 119 | } else { 120 | setTimeout(() => { 121 | this.calculateFinalScore(); 122 | }, DELAY); 123 | } 124 | } 125 | private getFmpTime(resultEls: ElementList): number { 126 | let rt = 0; 127 | for (const item of resultEls) { 128 | let time: number = 0; 129 | if (item.weight === 1) { 130 | const index: number = parseInt(item.ele.getAttribute('fmp_c'), 10); 131 | time = this.statusCollector[index] && this.statusCollector[index].time; 132 | } else if (item.weight === 2) { 133 | if (item.ele.tagName === 'IMG') { 134 | time = this.entries[(item.ele as HTMLImageElement).src]; 135 | } else if (item.ele.tagName === 'SVG') { 136 | const index: number = parseInt(item.ele.getAttribute('fmp_c'), 10); 137 | time = this.statusCollector[index] && this.statusCollector[index].time; 138 | } else { 139 | const match = getStyle(item.ele, 'background-image').match(/url\(\"(.*?)\"\)/); 140 | let url: string = ''; 141 | if (match && match[1]) { 142 | url = match[1]; 143 | if (!url.includes('http')) { 144 | url = location.protocol + match[1]; 145 | } 146 | } 147 | time = this.entries[url]; 148 | } 149 | } else if (item.weight === 4) { 150 | if (item.ele.tagName === 'CANVAS') { 151 | const index: number = parseInt(item.ele.getAttribute('fmp_c'), 10); 152 | time = this.statusCollector[index] && this.statusCollector[index].time; 153 | } else if (item.ele.tagName === 'VIDEO') { 154 | time = this.entries[(item.ele as HTMLVideoElement).src]; 155 | if (!time) { 156 | time = this.entries[(item.ele as HTMLVideoElement).poster]; 157 | } 158 | } 159 | } 160 | if (typeof time !== 'number') { 161 | time = 0; 162 | } 163 | if (rt < time) { 164 | rt = time; 165 | } 166 | } 167 | return rt; 168 | } 169 | /** 170 | * The nodes with the highest score in the visible area are collected and the average value is taken, 171 | * and the low score ones are eliminated 172 | */ 173 | private filterResult(els: ElementList): ElementList { 174 | if (els.length === 1) { 175 | return els; 176 | } 177 | let sum: number = 0; 178 | els.forEach((item: any) => { 179 | sum += item.st; 180 | }); 181 | const avg: number = sum / els.length; 182 | return els.filter((item: any) => { 183 | return item.st > avg; 184 | }); 185 | } 186 | private checkNeedCancel(start: number): boolean { 187 | const time: number = performance.now() - start; 188 | const lastCalTime: number = 189 | this.statusCollector.length > 0 ? this.statusCollector[this.statusCollector.length - 1].time : 0; 190 | return time > LIMIT || time - lastCalTime > 1000; 191 | } 192 | private getTreeScore(node: Element): ICalScore | any { 193 | if (!node) { 194 | return {}; 195 | } 196 | const dpss = []; 197 | const children: any = node.children; 198 | for (const child of children) { 199 | // Only calculate marked elements 200 | if (!child.getAttribute('fmp_c')) { 201 | continue; 202 | } 203 | const s = this.getTreeScore(child); 204 | if (s.st) { 205 | dpss.push(s); 206 | } 207 | } 208 | 209 | return this.calcaulteGrades(node, dpss); 210 | } 211 | private calcaulteGrades(ele: Element, dpss: ICalScore[]): ICalScore { 212 | const { width, height, left, top } = ele.getBoundingClientRect(); 213 | let isInViewPort: boolean = true; 214 | if (WH < top || WW < left) { 215 | isInViewPort = false; 216 | } 217 | let sdp: number = 0; 218 | dpss.forEach((item: any) => { 219 | sdp += item.st; 220 | }); 221 | let weight: number = Number(ELE_WEIGHT[ele.tagName as any]) || 1; 222 | // If there is a common element of the background image, it is calculated according to the picture 223 | if ( 224 | weight === 1 && 225 | getStyle(ele, 'background-image') && 226 | getStyle(ele, 'background-image') !== 'initial' && 227 | getStyle(ele, 'background-image') !== 'none' 228 | ) { 229 | weight = ELE_WEIGHT.IMG; 230 | } 231 | // score = the area of element 232 | let st: number = isInViewPort ? width * height * weight : 0; 233 | let els = [{ ele, st, weight }]; 234 | const root = ele; 235 | // The percentage of the current element in the viewport 236 | const areaPercent = this.calculateAreaParent(ele); 237 | // If the sum of the child's weights is greater than the parent's true weight 238 | if (sdp > st * areaPercent || areaPercent === 0) { 239 | st = sdp; 240 | els = []; 241 | for (const item of dpss) { 242 | els = els.concat(item.els); 243 | } 244 | } 245 | return { 246 | dpss, 247 | st, 248 | els, 249 | root, 250 | }; 251 | } 252 | private calculateAreaParent(ele: Element): number { 253 | const { left, right, top, bottom, width, height } = ele.getBoundingClientRect(); 254 | const winLeft: number = 0; 255 | const winTop: number = 0; 256 | const winRight: number = WW; 257 | const winBottom: number = WH; 258 | const overlapX = right - left + (winRight - winLeft) - (Math.max(right, winRight) - Math.min(left, winLeft)); 259 | const overlapY = bottom - top + (winBottom - winTop) - (Math.max(bottom, winBottom) - Math.min(top, winTop)); 260 | 261 | if (overlapX <= 0 || overlapY <= 0) { 262 | return 0; 263 | } 264 | return (overlapX * overlapY) / (width * height); 265 | } 266 | // Depth first traversal to mark nodes 267 | private setTag(target: Element, callbackCount: number): void { 268 | const tagName: string = target.tagName; 269 | if (IGNORE_TAG_SET.indexOf(tagName) === -1) { 270 | const $children: HTMLCollection = target.children; 271 | if ($children && $children.length > 0) { 272 | for (let i = $children.length - 1; i >= 0; i--) { 273 | const $child: Element = $children[i]; 274 | const hasSetTag = $child.getAttribute('fmp_c') !== null; 275 | // If it is not marked, whether the marking condition is met is detected 276 | if (!hasSetTag) { 277 | const { left, top, width, height } = $child.getBoundingClientRect(); 278 | if (WH < top || WW < left || width === 0 || height === 0) { 279 | continue; 280 | } 281 | $child.setAttribute('fmp_c', `${callbackCount}`); 282 | } 283 | this.setTag($child, callbackCount); 284 | } 285 | } 286 | } 287 | } 288 | } 289 | 290 | export default new FMPTiming(); 291 | -------------------------------------------------------------------------------- /src/performance/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import {CustomOptionsType} from '../types'; 19 | import Report from '../services/report'; 20 | import {prerenderChangeListener, onHidden, runOnce, idlePeriod} from '../services/eventsListener'; 21 | import pagePerf from './perf'; 22 | import FMP from './fmp'; 23 | import {observe} from '../services/observe'; 24 | import {LCPMetric, INPMetric, CLSMetric} from './type'; 25 | import {LayoutShift} from '../services/types'; 26 | import {getVisibilityObserver} from '../services/getVisibilityObserver'; 27 | import {getActivationStart, getResourceEntry} from '../services/getEntries'; 28 | import {onBFCacheRestore} from '../services/bfcache'; 29 | import {handleInteractionEntry, clearInteractions, getLongestInteraction, DEFAULT_DURATION_THRESHOLD} from '../services/interactions'; 30 | import {isLayoutShiftSupported, isEventSupported, isLargestContentfulPaintSupported} from '../services/apiDetectSupported'; 31 | 32 | const handler = { 33 | set(target: {[key: string]: unknown}, prop: string, value: unknown) { 34 | if (target[prop] !== undefined) { 35 | return true 36 | } 37 | target[prop] = value; 38 | const source: {[key: string]: unknown} = { 39 | ...target, 40 | collector: undefined, 41 | useWebVitals: undefined, 42 | }; 43 | if (target.useWebVitals && !isNaN(Number(target.fmpTime)) && !isNaN(Number(target.lcpTime)) && !isNaN(Number(target.clsTime))) { 44 | new TracePerf().reportPerf(source, String(target.collector)); 45 | } 46 | return true; 47 | } 48 | }; 49 | const reportedMetricNames: Record = {}; 50 | const InitiatorTypes = ["beacon", "xmlhttprequest", "fetch"]; 51 | class TracePerf { 52 | private options: CustomOptionsType = { 53 | pagePath: '', 54 | serviceVersion: '', 55 | service: '', 56 | collector: '' 57 | }; 58 | private perfInfo = {}; 59 | private coreWebMetrics: Record = {}; 60 | private resources: {name: string, duration: number, size: number, protocol: string, resourceType: string}[] = []; 61 | private inpList: Record[] = []; 62 | public getPerf(options: CustomOptionsType) { 63 | this.options = options; 64 | this.perfInfo = { 65 | pagePath: options.pagePath, 66 | serviceVersion: options.serviceVersion, 67 | service: options.service, 68 | } 69 | this.coreWebMetrics = new Proxy({...this.perfInfo, collector: options.collector, useWebVitals: options.useWebVitals}, handler); 70 | this.observeResources(); 71 | // trace and report perf data and pv to serve when page loaded 72 | if (document.readyState === 'complete') { 73 | this.getBasicPerf(); 74 | } else { 75 | window.addEventListener('load', () => this.getBasicPerf()); 76 | } 77 | this.getCorePerf(); 78 | window.addEventListener('beforeunload', () => { 79 | this.reportINP(); 80 | this.reportResources(); 81 | }); 82 | } 83 | private observeResources() { 84 | observe('resource', (list) => { 85 | const newResources = list.filter((d: PerformanceResourceTiming) => !InitiatorTypes.includes(d.initiatorType)) 86 | .map((d: PerformanceResourceTiming) => ({ 87 | service: this.options.service, 88 | serviceVersion: this.options.serviceVersion, 89 | pagePath: this.options.pagePath, 90 | name: d.name, 91 | duration: Math.floor(d.duration), 92 | size: d.transferSize, 93 | protocol: d.nextHopProtocol, 94 | resourceType: d.initiatorType, 95 | })); 96 | this.resources.push(...newResources); 97 | }); 98 | } 99 | private reportResources() { 100 | const newResources = getResourceEntry().filter((d: PerformanceResourceTiming) => !InitiatorTypes.includes(d.initiatorType)) 101 | .map((d: PerformanceResourceTiming) => ({ 102 | service: this.options.service, 103 | serviceVersion: this.options.serviceVersion, 104 | pagePath: this.options.pagePath, 105 | name: d.name, 106 | duration: Math.floor(d.duration), 107 | size: d.transferSize, 108 | protocol: d.nextHopProtocol, 109 | resourceType: d.initiatorType, 110 | })); 111 | const list = [...newResources, ...this.resources]; 112 | if (!list.length) { 113 | return; 114 | } 115 | new Report('RESOURCES', this.options.collector).sendByBeacon(list); 116 | } 117 | private getCorePerf() { 118 | if (!this.options.useWebVitals) { 119 | return; 120 | } 121 | this.LCP(); 122 | this.INP(); 123 | this.CLS(); 124 | setTimeout(() => { 125 | this.coreWebMetrics.fmpTime = isNaN(FMP.fmpTime) ? -1 : Math.floor(FMP.fmpTime); 126 | }, 5000); 127 | } 128 | private CLS() { 129 | if (!isLayoutShiftSupported()) { 130 | return this.coreWebMetrics.clsTime = -1; 131 | } 132 | let partValue = 0; 133 | let entryList: LayoutShift[] = []; 134 | 135 | const handleEntries = (entries: LayoutShift[]) => { 136 | entries.forEach((entry) => { 137 | // Count layout shifts without recent user input only 138 | if (!entry.hadRecentInput) { 139 | const firstEntry = entryList[0]; 140 | const lastEntry = entryList[entryList.length - 1]; 141 | if ( 142 | partValue && 143 | entry.startTime - lastEntry.startTime < 1000 && 144 | entry.startTime - firstEntry.startTime < 5000 145 | ) { 146 | partValue += entry.value; 147 | } else { 148 | partValue = entry.value; 149 | } 150 | entryList.push(entry); 151 | } 152 | }); 153 | if (partValue > 0) { 154 | setTimeout(() => { 155 | this.coreWebMetrics.clsTime = partValue; 156 | }, 3000); 157 | } 158 | }; 159 | 160 | const obs = observe('layout-shift', handleEntries); 161 | 162 | if (!obs) { 163 | return; 164 | } 165 | onHidden(() => { 166 | handleEntries(obs.takeRecords() as CLSMetric['entries']); 167 | obs!.disconnect(); 168 | }); 169 | } 170 | private LCP() { 171 | if (!isLargestContentfulPaintSupported()) { 172 | return this.coreWebMetrics.lcpTime = -1; 173 | } 174 | prerenderChangeListener(() => { 175 | const visibilityObserver = getVisibilityObserver(); 176 | const processEntries = (entries: LCPMetric['entries']) => { 177 | entries = entries.slice(-1); 178 | for (const entry of entries) { 179 | if (entry.startTime < visibilityObserver.firstHiddenTime) { 180 | this.coreWebMetrics.lcpTime = Math.floor(Math.max(entry.startTime - getActivationStart(), 0)); 181 | } 182 | } 183 | }; 184 | 185 | const obs = observe('largest-contentful-paint', processEntries); 186 | if (!obs) { 187 | return; 188 | } 189 | const disconnect = runOnce(() => { 190 | if (!reportedMetricNames['lcp']) { 191 | processEntries(obs!.takeRecords() as LCPMetric['entries']); 192 | obs!.disconnect(); 193 | reportedMetricNames['lcp'] = true; 194 | } 195 | }); 196 | ['keydown', 'click'].forEach((type) => { 197 | addEventListener(type, () => idlePeriod(disconnect), true); 198 | }); 199 | onHidden(disconnect); 200 | }) 201 | } 202 | private INP() { 203 | if (!isEventSupported()) { 204 | return; 205 | } 206 | prerenderChangeListener(() => { 207 | const processEntries = (entries: INPMetric['entries']) => { 208 | idlePeriod(() => { 209 | entries.forEach(handleInteractionEntry); 210 | const interaction = getLongestInteraction(); 211 | const len = this.inpList.length; 212 | 213 | if (interaction && (!len || this.inpList[len - 1].inpTime !== interaction.latency)) { 214 | const param = { 215 | inpTime: interaction.latency, 216 | ...this.perfInfo, 217 | }; 218 | this.inpList.push(param); 219 | } 220 | }) 221 | }; 222 | const obs = observe('event', processEntries, { 223 | durationThreshold: DEFAULT_DURATION_THRESHOLD, 224 | }); 225 | if (!obs) { 226 | return; 227 | } 228 | obs.observe({type: 'first-input', buffered: true}); 229 | onHidden( 230 | runOnce(() => { 231 | processEntries(obs.takeRecords() as INPMetric['entries']); 232 | obs.disconnect(); 233 | }), 234 | ); 235 | onBFCacheRestore(() => { 236 | clearInteractions(); 237 | this.inpList.length = 0; 238 | }) 239 | }) 240 | } 241 | private reportINP() { 242 | if (!this.inpList.length) { 243 | return; 244 | } 245 | 246 | new Report('WEBINTERACTIONS', this.options.collector).sendByBeacon(this.inpList); 247 | } 248 | private getBasicPerf() { 249 | // auto report pv and perf data 250 | const perfDetail = this.options.autoTracePerf ? new pagePerf().getPerfTiming() : {}; 251 | const perfInfo = { 252 | ...perfDetail, 253 | ...this.perfInfo, 254 | }; 255 | new Report('PERF', this.options.collector).sendByXhr(perfInfo); 256 | // clear perf data 257 | this.clearPerf(); 258 | } 259 | 260 | public reportPerf(data: {[key: string]: unknown}, collector: string) { 261 | const perf = { 262 | ...data, 263 | ...this.perfInfo 264 | }; 265 | new Report('WEBVITALS', collector).sendByXhr(perf); 266 | } 267 | 268 | private clearPerf() { 269 | if (!(window.performance && window.performance.clearResourceTimings)) { 270 | return; 271 | } 272 | window.performance.clearResourceTimings(); 273 | } 274 | } 275 | 276 | export default new TracePerf(); 277 | -------------------------------------------------------------------------------- /src/performance/perf.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { IPerfDetail } from './type'; 18 | import {getNavigationEntry} from '../services/getEntries'; 19 | class PagePerf { 20 | public getPerfTiming(): IPerfDetail { 21 | try { 22 | let { timing } = window.performance as PerformanceNavigationTiming | any; // PerformanceTiming 23 | if (typeof window.PerformanceNavigationTiming === 'function') { 24 | const nt2Timing = getNavigationEntry(); 25 | 26 | if (nt2Timing) { 27 | timing = nt2Timing; 28 | } 29 | } 30 | let redirectTime = 0; 31 | 32 | if (timing.navigationStart !== undefined) { 33 | redirectTime = Math.floor(timing.fetchStart - timing.navigationStart); 34 | } else if (timing.redirectEnd !== undefined) { 35 | redirectTime = Math.floor(timing.redirectEnd - timing.redirectStart); 36 | } else { 37 | redirectTime = 0; 38 | } 39 | 40 | return { 41 | redirectTime, 42 | dnsTime: Math.floor(timing.domainLookupEnd - timing.domainLookupStart), 43 | ttfbTime: Math.floor(timing.responseStart - timing.requestStart), // Time to First Byte 44 | tcpTime: Math.floor(timing.connectEnd - timing.connectStart), 45 | transTime: Math.floor(timing.responseEnd - timing.responseStart), 46 | domAnalysisTime: Math.floor(timing.domInteractive - timing.responseEnd), 47 | fptTime: Math.floor(timing.responseEnd - timing.fetchStart), // First Paint Time or Blank Screen Time 48 | domReadyTime: Math.floor(timing.domContentLoadedEventEnd - timing.fetchStart), 49 | loadPageTime: Math.floor(timing.loadEventStart - timing.fetchStart), // Page full load time 50 | // Synchronous load resources in the page 51 | resTime: Math.floor(timing.loadEventStart - timing.domContentLoadedEventEnd), 52 | // Only valid for HTTPS 53 | sslTime: 54 | location.protocol === 'https:' && timing.secureConnectionStart > 0 55 | ? Math.floor(timing.connectEnd - timing.secureConnectionStart) 56 | : undefined, 57 | ttlTime: Math.floor(timing.domInteractive - timing.fetchStart), // time to interact 58 | firstPackTime: Math.floor(timing.responseStart - timing.domainLookupStart), // first pack time 59 | }; 60 | } catch (e) { 61 | throw e; 62 | } 63 | } 64 | } 65 | 66 | export default PagePerf; 67 | -------------------------------------------------------------------------------- /src/performance/type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import {LargestContentfulPaint, LayoutShift, PerformanceEventTiming} from "../services/types"; 18 | export interface ICalScore { 19 | dpss: ICalScore[]; 20 | st: number; 21 | els: ElementList; 22 | root?: Element; 23 | } 24 | export type ElementList = Array<{ 25 | ele: Element; 26 | st: number; 27 | weight: number; 28 | }>; 29 | export type IPerfDetail = { 30 | redirectTime: number | undefined; // Time of redirection 31 | dnsTime: number | undefined; // DNS query time 32 | ttfbTime: number | undefined; // Time to First Byte 33 | tcpTime: number | undefined; // Tcp connection time 34 | transTime: number | undefined; // Content transfer time 35 | domAnalysisTime: number | undefined; // Dom parsing time 36 | fptTime: number | undefined; // First Paint Time or Blank Screen Time 37 | domReadyTime: number | undefined; // Dom ready time 38 | loadPageTime: number | undefined; // Page full load time 39 | resTime: number | undefined; // Synchronous load resources in the page 40 | sslTime: number | undefined; // Only valid for HTTPS 41 | ttlTime: number | undefined; // Time to interact 42 | firstPackTime: number | undefined; // first pack time 43 | }; 44 | export interface LCPMetric { 45 | name: 'LCP'; 46 | entries: LargestContentfulPaint[]; 47 | } 48 | 49 | export interface INPMetric { 50 | name: 'INP'; 51 | entries: PerformanceEventTiming[]; 52 | } 53 | export interface CLSMetric { 54 | name: 'CLS'; 55 | entries: LayoutShift[]; 56 | } 57 | -------------------------------------------------------------------------------- /src/services/apiDetectSupported.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | export function isLayoutShiftSupported() { 18 | return PerformanceObserver.supportedEntryTypes.includes('layout-shift'); 19 | } 20 | 21 | export function isLargestContentfulPaintSupported() { 22 | return PerformanceObserver.supportedEntryTypes.includes('largest-contentful-paint'); 23 | } 24 | 25 | export function isEventSupported() { 26 | return PerformanceObserver.supportedEntryTypes.includes('event'); 27 | } 28 | -------------------------------------------------------------------------------- /src/services/base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import Task from './task'; 18 | import { ErrorsCategory, GradeTypeEnum } from './constant'; 19 | import { ErrorInfoFields, ReportFields } from './types'; 20 | 21 | let pageHasjsError: { [key: string]: boolean } = {}; 22 | let interval: NodeJS.Timeout; 23 | export default class Base { 24 | public logInfo: ErrorInfoFields & ReportFields & { collector: string } = { 25 | uniqueId: '', 26 | service: '', 27 | serviceVersion: '', 28 | pagePath: '', 29 | category: ErrorsCategory.UNKNOWN_ERROR, 30 | grade: GradeTypeEnum.INFO, 31 | errorUrl: '', 32 | line: 0, 33 | col: 0, 34 | message: '', 35 | firstReportedError: false, 36 | collector: '', 37 | }; 38 | 39 | public traceInfo(logInfo?: ErrorInfoFields & ReportFields & { collector: string }) { 40 | this.logInfo = logInfo || this.logInfo; 41 | const ExcludeErrorTypes: string[] = [ 42 | ErrorsCategory.AJAX_ERROR, 43 | ErrorsCategory.RESOURCE_ERROR, 44 | ErrorsCategory.UNKNOWN_ERROR, 45 | ]; 46 | // mark js error pv 47 | if (!pageHasjsError[location.href] && !ExcludeErrorTypes.includes(this.logInfo.category)) { 48 | pageHasjsError = { 49 | [location.href]: true, 50 | }; 51 | this.logInfo.firstReportedError = true; 52 | } 53 | const collector = this.logInfo.collector; 54 | 55 | delete this.logInfo.collector; 56 | Task.addTask(this.logInfo, collector); 57 | Task.finallyFireTasks(); 58 | if (interval) { 59 | return; 60 | } 61 | // report errors within 1min 62 | interval = setInterval(() => { 63 | Task.fireTasks(); 64 | }, 60000); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/services/bfcache.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | interface onBFCacheRestoreCallback { 19 | (event: PageTransitionEvent): void; 20 | } 21 | 22 | let bfcacheRestoreTime = -1; 23 | 24 | export const getBFCacheRestoreTime = () => bfcacheRestoreTime; 25 | 26 | export function onBFCacheRestore(cb: onBFCacheRestoreCallback) { 27 | addEventListener( 28 | 'pageshow', 29 | (event) => { 30 | if (event.persisted) { 31 | bfcacheRestoreTime = event.timeStamp; 32 | cb(event); 33 | } 34 | }, 35 | true, 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /src/services/constant.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | export enum ErrorsCategory { 18 | AJAX_ERROR = 'ajax', 19 | RESOURCE_ERROR = 'resource', 20 | VUE_ERROR = 'vue', 21 | PROMISE_ERROR = 'promise', 22 | JS_ERROR = 'js', 23 | UNKNOWN_ERROR = 'unknown', 24 | } 25 | export enum GradeTypeEnum { 26 | INFO = 'Info', 27 | WARNING = 'Warning', 28 | ERROR = 'Error', 29 | } 30 | export enum ReportTypes { 31 | ERROR = '/browser/errorLog', 32 | ERRORS = '/browser/errorLogs', 33 | PERF = '/browser/perfData', 34 | WEBVITALS = '/browser/perfData/webVitals', 35 | WEBINTERACTIONS = '/browser/perfData/webInteractions', 36 | RESOURCES = '/browser/perfData/resources', 37 | SEGMENT = '/v3/segment', 38 | SEGMENTS = '/v3/segments', 39 | } 40 | 41 | export const SpanLayer = 'Http'; 42 | export const SpanType = 'Exit'; 43 | 44 | export enum ReadyStatus { 45 | OPENED = 1, 46 | DONE = 4, 47 | } 48 | export const ComponentId = 10001; // ajax 49 | -------------------------------------------------------------------------------- /src/services/eventsListener.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | export function prerenderChangeListener(callback: () => void) { 18 | if ((document as any).prerendering) { 19 | addEventListener('prerenderingchange', callback, true); 20 | return; 21 | } 22 | callback(); 23 | } 24 | 25 | export function onHidden (cb: () => void) { 26 | document.addEventListener('visibilitychange', () => { 27 | if (document.visibilityState === 'hidden') { 28 | cb(); 29 | } 30 | }); 31 | }; 32 | 33 | export function runOnce (callback: () => void) { 34 | let called = false; 35 | return () => { 36 | if (!called) { 37 | callback(); 38 | called = true; 39 | } 40 | }; 41 | }; 42 | 43 | export function idlePeriod(callback: () => void): number { 44 | const func = window.requestIdleCallback || window.setTimeout; 45 | 46 | let handle = -1; 47 | callback = runOnce(callback); 48 | if (document.visibilityState === 'hidden') { 49 | callback(); 50 | } else { 51 | handle = func(callback); 52 | onHidden(callback); 53 | } 54 | return handle; 55 | }; 56 | -------------------------------------------------------------------------------- /src/services/getEntries.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | export function getNavigationEntry() { 19 | const navigationEntry: PerformanceEntry | any = 20 | self.performance && 21 | performance.getEntriesByType && 22 | performance.getEntriesByType('navigation')[0]; 23 | 24 | if ( 25 | navigationEntry && 26 | navigationEntry.responseStart > 0 && 27 | navigationEntry.responseStart < performance.now() 28 | ) { 29 | return navigationEntry; 30 | } 31 | }; 32 | 33 | 34 | export function getActivationStart() { 35 | const entry = getNavigationEntry(); 36 | return (entry && entry.activationStart) || 0; 37 | }; 38 | 39 | export function getResourceEntry() { 40 | const resourceEntry: PerformanceEntry | any = 41 | self.performance && 42 | performance.getEntriesByType && 43 | performance.getEntriesByType('resource'); 44 | return resourceEntry; 45 | }; -------------------------------------------------------------------------------- /src/services/getVisibilityObserver.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import {onBFCacheRestore} from './bfcache'; 19 | 20 | let firstHiddenTime = -1; 21 | 22 | function initHiddenTime () { 23 | return document.visibilityState === 'hidden' && !(document as any).prerendering 24 | ? 0 25 | : Infinity; 26 | }; 27 | 28 | function onVisibilityUpdate(event: Event) { 29 | if (document.visibilityState === 'hidden' && firstHiddenTime > -1) { 30 | firstHiddenTime = event.type === 'visibilitychange' ? event.timeStamp : 0; 31 | removeChangeListeners(); 32 | } 33 | }; 34 | 35 | function addChangeListeners() { 36 | addEventListener('visibilitychange', onVisibilityUpdate, true); 37 | addEventListener('prerenderingchange', onVisibilityUpdate, true); 38 | }; 39 | 40 | function removeChangeListeners() { 41 | removeEventListener('visibilitychange', onVisibilityUpdate, true); 42 | removeEventListener('prerenderingchange', onVisibilityUpdate, true); 43 | }; 44 | 45 | export function getVisibilityObserver() { 46 | if (firstHiddenTime < 0) { 47 | firstHiddenTime = initHiddenTime(); 48 | addChangeListeners(); 49 | onBFCacheRestore(() => { 50 | setTimeout(() => { 51 | firstHiddenTime = initHiddenTime(); 52 | addChangeListeners(); 53 | }, 0); 54 | }); 55 | } 56 | return { 57 | get firstHiddenTime() { 58 | return firstHiddenTime; 59 | }, 60 | }; 61 | }; 62 | -------------------------------------------------------------------------------- /src/services/interactions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import { Interaction, EntryPreProcessingHook, PerformanceEventTiming } from "./types"; 19 | 20 | export const DEFAULT_DURATION_THRESHOLD = 40; 21 | // Longest interactions list 22 | export const interactionList: Interaction[] = []; 23 | export const interactionsMap: Map = new Map(); 24 | export const entryPreProcessingCallbacks: EntryPreProcessingHook[] = []; 25 | 26 | const MAX_INTERACTIONS_TO_CONSIDER = 10; 27 | 28 | let prevInteractionCount = 0; 29 | 30 | export function handleInteractionEntry (entry: PerformanceEventTiming) { 31 | entryPreProcessingCallbacks.forEach((cb) => cb(entry)); 32 | 33 | if (!(entry.interactionId || entry.entryType === 'first-input')) return; 34 | 35 | const minLongestInteraction = interactionList[interactionList.length - 1]; 36 | 37 | const existingInteraction = interactionsMap.get(entry.interactionId!); 38 | 39 | if ( 40 | existingInteraction || interactionList.length < MAX_INTERACTIONS_TO_CONSIDER || 41 | entry.duration > minLongestInteraction.latency 42 | ) { 43 | if (existingInteraction) { 44 | if (entry.duration > existingInteraction.latency) { 45 | existingInteraction.entries = [entry]; 46 | existingInteraction.latency = entry.duration; 47 | } else if ( 48 | entry.duration === existingInteraction.latency && 49 | entry.startTime === existingInteraction.entries[0].startTime 50 | ) { 51 | existingInteraction.entries.push(entry); 52 | } 53 | } else { 54 | const interaction = { 55 | id: entry.interactionId!, 56 | latency: entry.duration, 57 | entries: [entry], 58 | }; 59 | interactionsMap.set(interaction.id, interaction); 60 | interactionList.push(interaction); 61 | } 62 | 63 | // Sort the entries by latency 64 | interactionList.sort((a, b) => b.latency - a.latency); 65 | if (interactionList.length > MAX_INTERACTIONS_TO_CONSIDER) { 66 | interactionList 67 | .splice(MAX_INTERACTIONS_TO_CONSIDER) 68 | .forEach((i) => interactionsMap.delete(i.id)); 69 | } 70 | } 71 | }; 72 | 73 | function getInteractionCountForNavigation() { 74 | return getInteractionCount() - prevInteractionCount; 75 | }; 76 | 77 | export function getLongestInteraction() { 78 | const index = Math.min(interactionList.length - 1, Math.floor(getInteractionCountForNavigation() / 50)); 79 | 80 | return interactionList[index]; 81 | }; 82 | 83 | export function clearInteractions() { 84 | prevInteractionCount = getInteractionCount(); 85 | interactionList.length = 0; 86 | interactionsMap.clear(); 87 | }; 88 | 89 | export function getInteractionCount() { 90 | return (performance.eventCounts as any).size || 0; 91 | }; 92 | -------------------------------------------------------------------------------- /src/services/observe.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import {LargestContentfulPaint, LayoutShift, PerformanceEventTiming, PerformanceObserverInit} from "./types"; 18 | interface PerformanceEntryObj { 19 | 'layout-shift': LayoutShift[]; 20 | 'largest-contentful-paint': LargestContentfulPaint[]; 21 | 'resource': PerformanceResourceTiming[]; 22 | 'event': PerformanceEventTiming[]; 23 | } 24 | 25 | export function observe ( 26 | type: K, 27 | callback: (entries: PerformanceEntryObj[K]) => void, 28 | opts?: PerformanceObserverInit, 29 | ): PerformanceObserver { 30 | try { 31 | if (PerformanceObserver.supportedEntryTypes.includes(type)) { 32 | const perfObs = new PerformanceObserver((list) => { 33 | 34 | Promise.resolve().then(() => { 35 | callback(list.getEntries() as PerformanceEntryObj[K]); 36 | }); 37 | }); 38 | perfObs.observe( 39 | Object.assign( 40 | { 41 | type, 42 | buffered: true, 43 | }, 44 | opts || {}, 45 | ), 46 | ); 47 | return perfObs; 48 | } 49 | } catch (e) { 50 | console.error(e); 51 | } 52 | }; -------------------------------------------------------------------------------- /src/services/report.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { ReportTypes } from './constant'; 18 | class Report { 19 | private url: string = ''; 20 | 21 | constructor(type: string, collector: string) { 22 | const typesMap: Record = { 23 | ERROR: ReportTypes.ERROR, 24 | ERRORS: ReportTypes.ERRORS, 25 | SEGMENT: ReportTypes.SEGMENT, 26 | SEGMENTS: ReportTypes.SEGMENTS, 27 | PERF: ReportTypes.PERF, 28 | WEBVITALS: ReportTypes.WEBVITALS, 29 | WEBINTERACTIONS: ReportTypes.WEBINTERACTIONS, 30 | RESOURCES: ReportTypes.RESOURCES, 31 | }; 32 | 33 | this.url = `${collector}${typesMap[type]}`; 34 | } 35 | 36 | public sendByFetch(data: any) { 37 | delete data.collector; 38 | if (!this.url) { 39 | return; 40 | } 41 | const sendRequest = new Request(this.url, { method: 'POST', body: JSON.stringify(data) }); 42 | 43 | fetch(sendRequest) 44 | .then((response) => { 45 | if (response.status >= 400 || response.status === 0) { 46 | throw new Error('Something went wrong on api server!'); 47 | } 48 | }) 49 | .catch((error) => { 50 | console.error(error); 51 | }); 52 | } 53 | 54 | public sendByXhr(data: any) { 55 | if (!this.url) { 56 | return; 57 | } 58 | const xhr = new XMLHttpRequest(); 59 | 60 | xhr.open('post', this.url, true); 61 | xhr.setRequestHeader('Content-Type', 'application/json'); 62 | xhr.onreadystatechange = function () { 63 | if (xhr.readyState === 4 && xhr.status < 400) { 64 | console.log('Report successfully'); 65 | } 66 | }; 67 | xhr.send(JSON.stringify(data)); 68 | } 69 | 70 | public sendByBeacon(data: any) { 71 | if (!this.url) { 72 | return; 73 | } 74 | if (typeof navigator.sendBeacon === 'function') { 75 | navigator.sendBeacon( 76 | this.url, 77 | new Blob([JSON.stringify(data)], { 78 | type: 'application/json' 79 | }) 80 | ); 81 | return; 82 | } 83 | 84 | this.sendByXhr(data); 85 | } 86 | } 87 | export default Report; 88 | -------------------------------------------------------------------------------- /src/services/task.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { ErrorInfoFields, ReportFields } from './types'; 18 | import Report from './report'; 19 | 20 | class TaskQueue { 21 | private queues: ((ErrorInfoFields & ReportFields) | undefined)[] = []; 22 | private collector: string = ''; 23 | 24 | public addTask(data: ErrorInfoFields & ReportFields, collector: string) { 25 | this.queues.push(data); 26 | this.collector = collector; 27 | } 28 | 29 | public fireTasks() { 30 | if (!(this.queues && this.queues.length)) { 31 | return; 32 | } 33 | 34 | new Report('ERRORS', this.collector).sendByXhr(this.queues); 35 | this.queues = []; 36 | } 37 | 38 | public finallyFireTasks() { 39 | window.addEventListener('beforeunload', () => { 40 | if (!this.queues.length) { 41 | return; 42 | } 43 | new Report('ERRORS', this.collector).sendByBeacon(this.queues); 44 | }); 45 | } 46 | } 47 | 48 | export default new TaskQueue(); 49 | -------------------------------------------------------------------------------- /src/services/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | export interface ErrorInfoFields { 19 | uniqueId: string; 20 | category: string; 21 | grade: string; 22 | message: any; 23 | errorUrl: string; 24 | line?: number; 25 | col?: number; 26 | stack?: string; 27 | firstReportedError?: boolean; 28 | } 29 | 30 | export interface ReportFields { 31 | service: string; 32 | serviceVersion: string; 33 | pagePath: string; 34 | } 35 | export interface LargestContentfulPaint extends PerformanceEntry { 36 | readonly renderTime: DOMHighResTimeStamp; 37 | readonly loadTime: DOMHighResTimeStamp; 38 | readonly size: number; 39 | readonly id: string; 40 | readonly url: string; 41 | readonly element: Element | null; 42 | } 43 | 44 | interface LayoutShiftAttribution { 45 | node?: Node; 46 | previousRect: DOMRectReadOnly; 47 | currentRect: DOMRectReadOnly; 48 | } 49 | export interface LayoutShift extends PerformanceEntry { 50 | value: number; 51 | sources: LayoutShiftAttribution[]; 52 | hadRecentInput: boolean; 53 | } 54 | export interface PerformanceEventTiming extends PerformanceEntry { 55 | duration: DOMHighResTimeStamp; 56 | interactionId: number; 57 | } 58 | export interface Interaction { 59 | id: number; 60 | latency: number; 61 | entries: PerformanceEventTiming[]; 62 | } 63 | export interface EntryPreProcessingHook { 64 | (entry: PerformanceEventTiming): void; 65 | } 66 | export interface PerformanceObserverInit { 67 | durationThreshold?: number; 68 | } -------------------------------------------------------------------------------- /src/services/uuid.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | export default function uuid() { 19 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { 20 | /* tslint:disable */ 21 | const r = (Math.random() * 16) | 0; 22 | /* tslint:disable */ 23 | const v = c === 'x' ? r : (r & 0x3) | 0x8; 24 | 25 | return v.toString(16); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /src/trace/interceptors/fetch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { encode } from 'js-base64'; 18 | import uuid from '../../services/uuid'; 19 | import { SegmentFields, SpanFields } from '../type'; 20 | import { CustomOptionsType } from '../../types'; 21 | import Base from '../../services/base'; 22 | import { ComponentId, ReportTypes, SpanLayer, SpanType, ErrorsCategory, GradeTypeEnum } from '../../services/constant'; 23 | let customConfig: any = {}; 24 | export default function windowFetch(options: CustomOptionsType, segments: SegmentFields[]) { 25 | const originFetch: any = window.fetch; 26 | setFetchOptions(options); 27 | 28 | window.fetch = async (...args: any) => { 29 | const startTime = new Date().getTime(); 30 | const traceId = uuid(); 31 | const traceSegmentId = uuid(); 32 | let segment = { 33 | traceId: '', 34 | service: customConfig.service, 35 | spans: [], 36 | serviceInstance: customConfig.serviceVersion, 37 | traceSegmentId: '', 38 | } as SegmentFields; 39 | let url = {} as URL; 40 | // for args[0] is Request object see: https://developer.mozilla.org/zh-CN/docs/Web/API/fetch 41 | if (Object.prototype.toString.call(args[0]) === '[object URL]') { 42 | url = args[0]; 43 | } else if (Object.prototype.toString.call(args[0]) === '[object Request]') { 44 | url = new URL(args[0].url); 45 | } else { 46 | if (args[0].startsWith('http://') || args[0].startsWith('https://')) { 47 | url = new URL(args[0]); 48 | } else if (args[0].startsWith('//')) { 49 | url = new URL(`${window.location.protocol}${args[0]}`); 50 | } else { 51 | url = new URL(window.location.href); 52 | url.pathname = args[0]; 53 | } 54 | } 55 | 56 | const noTraceOrigins = customConfig.noTraceOrigins.some((rule: string | RegExp) => { 57 | if (typeof rule === 'string') { 58 | if (rule === url.origin) { 59 | return true; 60 | } 61 | } else if (rule instanceof RegExp) { 62 | if (rule.test(url.origin)) { 63 | return true; 64 | } 65 | } 66 | }); 67 | const cURL = new URL(customConfig.collector); 68 | const pathname = cURL.pathname === '/' ? url.pathname : url.pathname.replace(new RegExp(`^${cURL.pathname}`), ''); 69 | const internals = [ReportTypes.ERROR, ReportTypes.ERRORS, ReportTypes.PERF, ReportTypes.SEGMENTS] as string[]; 70 | const isSDKInternal = internals.includes(pathname); 71 | const hasTrace = !noTraceOrigins || (isSDKInternal && customConfig.traceSDKInternal); 72 | 73 | if (hasTrace) { 74 | const traceIdStr = String(encode(traceId)); 75 | const segmentId = String(encode(traceSegmentId)); 76 | const service = String(encode(segment.service)); 77 | const instance = String(encode(segment.serviceInstance)); 78 | const endpoint = String(encode(customConfig.pagePath)); 79 | const peer = String(encode(url.host)); 80 | const index = segment.spans.length; 81 | const values = `${1}-${traceIdStr}-${segmentId}-${index}-${service}-${instance}-${endpoint}-${peer}`; 82 | 83 | if (args[0] instanceof Request) { 84 | args[0].headers.append('sw8', values); 85 | } else { 86 | if (!args[1]) { 87 | args[1] = {}; 88 | } 89 | if (!args[1].headers) { 90 | args[1].headers = new Headers(); 91 | } 92 | if (args[1].headers instanceof Headers) { 93 | args[1].headers.append('sw8', values); 94 | } else { 95 | args[1].headers['sw8'] = values; 96 | } 97 | } 98 | } 99 | 100 | const response = await originFetch(...args); 101 | 102 | try { 103 | if (response && (response.status === 0 || response.status >= 400)) { 104 | const logInfo = { 105 | uniqueId: uuid(), 106 | service: customConfig.service, 107 | serviceVersion: customConfig.serviceVersion, 108 | pagePath: customConfig.pagePath, 109 | category: ErrorsCategory.AJAX_ERROR, 110 | grade: GradeTypeEnum.ERROR, 111 | errorUrl: (response && response.url) || `${url.protocol}//${url.host}${url.pathname}`, 112 | message: `status: ${response ? response.status : 0}; statusText: ${response && response.statusText};`, 113 | collector: customConfig.collector, 114 | stack: 'Fetch: ' + response && response.statusText, 115 | }; 116 | new Base().traceInfo(logInfo); 117 | } 118 | if (hasTrace) { 119 | const tags = [ 120 | { 121 | key: 'http.method', 122 | value: args[0] instanceof Request ? args[0].method : args[1]?.method || 'GET', 123 | }, 124 | { 125 | key: 'url', 126 | value: (response && response.url) || `${url.protocol}//${url.host}${url.pathname}`, 127 | }, 128 | ]; 129 | const endTime = new Date().getTime(); 130 | const exitSpan: SpanFields = { 131 | operationName: customConfig.pagePath, 132 | startTime: startTime, 133 | endTime, 134 | spanId: segment.spans.length, 135 | spanLayer: SpanLayer, 136 | spanType: SpanType, 137 | isError: response && (response.status === 0 || response.status >= 400), // when requests failed, the status is 0 138 | parentSpanId: segment.spans.length - 1, 139 | componentId: ComponentId, 140 | peer: url.host, 141 | tags: customConfig.detailMode 142 | ? customConfig.customTags 143 | ? [...tags, ...customConfig.customTags] 144 | : tags 145 | : undefined, 146 | }; 147 | segment = { 148 | ...segment, 149 | traceId: traceId, 150 | traceSegmentId: traceSegmentId, 151 | }; 152 | segment.spans.push(exitSpan); 153 | segments.push(segment); 154 | } 155 | } catch (e) { 156 | throw e; 157 | } 158 | return response.clone(); 159 | }; 160 | } 161 | export function setFetchOptions(opt: CustomOptionsType) { 162 | customConfig = { ...customConfig, ...opt }; 163 | } 164 | -------------------------------------------------------------------------------- /src/trace/interceptors/xhr.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { ComponentId, ReadyStatus, ReportTypes, SpanLayer, SpanType } from '../../services/constant'; 18 | import uuid from '../../services/uuid'; 19 | import { encode } from 'js-base64'; 20 | import { CustomOptionsType } from '../../types'; 21 | import { SegmentFields, SpanFields } from '../type'; 22 | 23 | let customConfig: CustomOptionsType | any = {}; 24 | export default function xhrInterceptor(options: CustomOptionsType, segments: SegmentFields[]) { 25 | setOptions(options); 26 | const originalXHR = window.XMLHttpRequest as any; 27 | const xhrSend = XMLHttpRequest.prototype.send; 28 | const xhrOpen = XMLHttpRequest.prototype.open; 29 | 30 | if (!(xhrSend && xhrOpen)) { 31 | console.error('Tracing is not supported'); 32 | return; 33 | } 34 | originalXHR.getRequestConfig = []; 35 | 36 | function ajaxEventTrigger(event: string) { 37 | const ajaxEvent = new CustomEvent(event, { detail: this }); 38 | 39 | window.dispatchEvent(ajaxEvent); 40 | } 41 | 42 | function customizedXHR() { 43 | const liveXHR = new originalXHR(); 44 | 45 | liveXHR.addEventListener( 46 | 'readystatechange', 47 | function () { 48 | ajaxEventTrigger.call(this, 'xhrReadyStateChange'); 49 | }, 50 | false, 51 | ); 52 | 53 | liveXHR.open = function ( 54 | method: string, 55 | url: string, 56 | async: boolean, 57 | username?: string | null, 58 | password?: string | null, 59 | ) { 60 | this.getRequestConfig = arguments; 61 | 62 | return xhrOpen.apply(this, arguments); 63 | }; 64 | liveXHR.send = function (body?: Document | BodyInit | null) { 65 | return xhrSend.apply(this, arguments); 66 | }; 67 | 68 | return liveXHR; 69 | } 70 | 71 | (window as any).XMLHttpRequest = customizedXHR; 72 | 73 | const segCollector: { event: XMLHttpRequest; startTime: number; traceId: string; traceSegmentId: string }[] = []; 74 | 75 | window.addEventListener('xhrReadyStateChange', (event: CustomEvent) => { 76 | let segment = { 77 | traceId: '', 78 | service: customConfig.service, 79 | spans: [], 80 | serviceInstance: customConfig.serviceVersion, 81 | traceSegmentId: '', 82 | } as SegmentFields; 83 | const xhrState = event.detail.readyState; 84 | const config = event.detail.getRequestConfig; 85 | let url = {} as URL; 86 | if (config[1].startsWith('http://') || config[1].startsWith('https://')) { 87 | url = new URL(config[1]); 88 | } else if (config[1].startsWith('//')) { 89 | url = new URL(`${window.location.protocol}${config[1]}`); 90 | } else { 91 | url = new URL(window.location.href); 92 | url.pathname = config[1]; 93 | } 94 | 95 | const noTraceOrigins = customConfig.noTraceOrigins.some((rule: string | RegExp) => { 96 | if (typeof rule === 'string') { 97 | if (rule === url.origin) { 98 | return true; 99 | } 100 | } else if (rule instanceof RegExp) { 101 | if (rule.test(url.origin)) { 102 | return true; 103 | } 104 | } 105 | }); 106 | if (noTraceOrigins) { 107 | return; 108 | } 109 | 110 | const cURL = new URL(customConfig.collector); 111 | const pathname = cURL.pathname === '/' ? url.pathname : url.pathname.replace(new RegExp(`^${cURL.pathname}`), ''); 112 | const internals = [ReportTypes.ERROR, ReportTypes.ERRORS, ReportTypes.PERF, ReportTypes.SEGMENTS] as string[]; 113 | const isSDKInternal = internals.includes(pathname); 114 | 115 | if (isSDKInternal && !customConfig.traceSDKInternal) { 116 | return; 117 | } 118 | 119 | // The values of xhrState are from https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState 120 | if (xhrState === ReadyStatus.OPENED) { 121 | const traceId = uuid(); 122 | const traceSegmentId = uuid(); 123 | 124 | segCollector.push({ 125 | event: event.detail, 126 | startTime: new Date().getTime(), 127 | traceId, 128 | traceSegmentId, 129 | }); 130 | 131 | const traceIdStr = String(encode(traceId)); 132 | const segmentId = String(encode(traceSegmentId)); 133 | const service = String(encode(segment.service)); 134 | const instance = String(encode(segment.serviceInstance)); 135 | const endpoint = String(encode(customConfig.pagePath)); 136 | const peer = String(encode(url.host)); 137 | const index = segment.spans.length; 138 | const values = `${1}-${traceIdStr}-${segmentId}-${index}-${service}-${instance}-${endpoint}-${peer}`; 139 | 140 | event.detail.setRequestHeader('sw8', values); 141 | } 142 | 143 | if (xhrState === ReadyStatus.DONE) { 144 | const endTime = new Date().getTime(); 145 | for (let i = 0; i < segCollector.length; i++) { 146 | if (segCollector[i].event.readyState === ReadyStatus.DONE) { 147 | let responseURL = {} as URL; 148 | if (segCollector[i].event.status) { 149 | responseURL = new URL(segCollector[i].event.responseURL); 150 | } 151 | const tags = [ 152 | { 153 | key: 'http.method', 154 | value: config[0], 155 | }, 156 | { 157 | key: 'url', 158 | value: segCollector[i].event.responseURL || `${url.protocol}//${url.host}${url.pathname}`, 159 | }, 160 | ]; 161 | const exitSpan: SpanFields = { 162 | operationName: customConfig.pagePath, 163 | startTime: segCollector[i].startTime, 164 | endTime, 165 | spanId: segment.spans.length, 166 | spanLayer: SpanLayer, 167 | spanType: SpanType, 168 | isError: event.detail.status === 0 || event.detail.status >= 400, // when requests failed, the status is 0 169 | parentSpanId: segment.spans.length - 1, 170 | componentId: ComponentId, 171 | peer: responseURL.host, 172 | tags: customConfig.detailMode 173 | ? customConfig.customTags 174 | ? [...tags, ...customConfig.customTags] 175 | : tags 176 | : undefined, 177 | }; 178 | segment = { 179 | ...segment, 180 | traceId: segCollector[i].traceId, 181 | traceSegmentId: segCollector[i].traceSegmentId, 182 | }; 183 | segment.spans.push(exitSpan); 184 | segCollector.splice(i, 1); 185 | } 186 | } 187 | segments.push(segment); 188 | } 189 | }); 190 | } 191 | export function setOptions(opt: CustomOptionsType) { 192 | customConfig = { ...customConfig, ...opt }; 193 | } 194 | -------------------------------------------------------------------------------- /src/trace/segment.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import xhrInterceptor, { setOptions } from './interceptors/xhr'; 19 | import windowFetch, { setFetchOptions } from './interceptors/fetch'; 20 | import Report from '../services/report'; 21 | import { SegmentFields } from './type'; 22 | import { CustomOptionsType } from '../types'; 23 | 24 | export default function traceSegment(options: CustomOptionsType) { 25 | const segments = [] as SegmentFields[]; 26 | // inject interceptor 27 | xhrInterceptor(options, segments); 28 | windowFetch(options, segments); 29 | window.addEventListener('beforeunload', () => { 30 | if (!segments.length) { 31 | return; 32 | } 33 | new Report('SEGMENTS', options.collector).sendByBeacon(segments); 34 | }); 35 | //report per options.traceTimeInterval min 36 | setInterval(() => { 37 | if (!segments.length) { 38 | return; 39 | } 40 | new Report('SEGMENTS', options.collector).sendByXhr(segments); 41 | segments.splice(0, segments.length); 42 | }, options.traceTimeInterval); 43 | } 44 | 45 | export function setConfig(opt: CustomOptionsType) { 46 | setOptions(opt); 47 | setFetchOptions(opt); 48 | } 49 | -------------------------------------------------------------------------------- /src/trace/type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | export interface SegmentFields { 19 | traceId: string; 20 | service: string; 21 | spans: SpanFields[]; 22 | serviceInstance: string; 23 | traceSegmentId: string; 24 | } 25 | 26 | export interface SpanFields { 27 | operationName: string; 28 | startTime: number; 29 | endTime: number; 30 | spanId: number; 31 | spanLayer: string; 32 | spanType: string; 33 | isError: boolean; 34 | parentSpanId: number; 35 | componentId: number; 36 | peer: string; 37 | tags?: any; 38 | } 39 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | export interface CustomOptionsType extends CustomReportOptions { 19 | jsErrors?: boolean; 20 | apiErrors?: boolean; 21 | resourceErrors?: boolean; 22 | autoTracePerf?: boolean; 23 | enableSPA?: boolean; 24 | vue?: any; 25 | traceSDKInternal?: boolean; 26 | detailMode?: boolean; 27 | noTraceOrigins?: (string | RegExp)[]; 28 | traceTimeInterval?: number; 29 | customTags?: TagOption[]; 30 | useWebVitals?: boolean; 31 | } 32 | 33 | export interface CustomReportOptions { 34 | collector?: string; 35 | service: string; 36 | pagePath: string; 37 | serviceVersion: string; 38 | } 39 | 40 | export type TagOption = { 41 | key: string; 42 | value: string; 43 | }; 44 | -------------------------------------------------------------------------------- /test/base-compose.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | version: '2.1' 17 | 18 | services: 19 | oap: 20 | image: ghcr.io/apache/skywalking/oap:7a5fec56fed0eef60e36f619a8db09f823200201 21 | expose: 22 | - 11800 23 | - 12800 24 | - 10051 25 | - 5005 26 | networks: 27 | - e2e 28 | restart: on-failure 29 | environment: 30 | SW_CLUSTER_ZK_HOST_PORT: zk:2181 31 | SW_STORAGE_ES_CLUSTER_NODES: es:9200 32 | SW_JDBC_URL: jdbc:mysql://mysql:3306/swtest 33 | SW_STORAGE_INFLUXDB_URL: http://influxdb:8086 34 | JAVA_OPTS: >- 35 | -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 36 | healthcheck: 37 | test: ["CMD", "sh", "-c", "nc -zn 127.0.0.1 11800"] 38 | interval: 5s 39 | timeout: 60s 40 | retries: 120 41 | 42 | ui: 43 | image: ghcr.io/apache/skywalking/ui:7a5fec56fed0eef60e36f619a8db09f823200201 44 | expose: 45 | - 8080 46 | networks: 47 | - e2e 48 | environment: 49 | - SW_OAP_ADDRESS=oap:12800 50 | healthcheck: 51 | test: [ "CMD", "sh", "-c", "nc -zn 127.0.0.1 8080" ] 52 | interval: 5s 53 | timeout: 60s 54 | retries: 120 55 | -------------------------------------------------------------------------------- /test/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | version: '2.1' 17 | 18 | services: 19 | oap: 20 | extends: 21 | file: base-compose.yml 22 | service: oap 23 | ports: 24 | - 12800 25 | environment: 26 | SW_STORAGE: h2 27 | 28 | ui: 29 | extends: 30 | file: base-compose.yml 31 | service: ui 32 | ports: 33 | - 8080 34 | depends_on: 35 | oap: 36 | condition: service_healthy 37 | 38 | provider: 39 | build: 40 | context: ./ 41 | dockerfile: docker/Dockerfile.provider 42 | args: 43 | - SW_AGENT_PYTHON_COMMIT=${SW_AGENT_PYTHON_COMMIT} 44 | command: [ 'python3', '/entrypoint.py' ] 45 | networks: 46 | - e2e 47 | expose: 48 | - 9091 49 | environment: 50 | SW_AGENT_COLLECTOR_BACKEND_SERVICES: oap:11800 51 | SW_AGENT_LOGGING_LEVEL: DEBUG 52 | volumes: 53 | - ./docker/provider.py:/entrypoint.py 54 | depends_on: 55 | oap: 56 | condition: service_healthy 57 | healthcheck: 58 | test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9091" ] 59 | interval: 5s 60 | timeout: 60s 61 | retries: 120 62 | 63 | testui: 64 | build: 65 | context: ../ 66 | dockerfile: test/docker/Dockerfile.test-ui 67 | args: 68 | - SW_AGENT_CLIENT_JS_TEST_COMMIT=${SW_AGENT_CLIENT_JS_TEST_COMMIT} 69 | networks: 70 | - e2e 71 | ports: 72 | - 80 73 | depends_on: 74 | provider: 75 | condition: service_healthy 76 | oap: 77 | condition: service_healthy 78 | 79 | selenium-hub: 80 | image: selenium/hub:4.0.0-alpha-7-prerelease-20201009 81 | networks: 82 | - e2e 83 | expose: 84 | - 4444 85 | depends_on: 86 | testui: 87 | condition: service_started 88 | provider: 89 | condition: service_healthy 90 | 91 | chrome: 92 | image: selenium/node-chrome:4.0.0-alpha-7-prerelease-20201009 93 | networks: 94 | - e2e 95 | volumes: 96 | - /dev/shm:/dev/shm 97 | depends_on: 98 | selenium-hub: 99 | condition: service_started 100 | testui: 101 | condition: service_started 102 | provider: 103 | condition: service_healthy 104 | environment: 105 | - SE_EVENT_BUS_HOST=selenium-hub 106 | - SE_EVENT_BUS_PUBLISH_PORT=4442 107 | - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 108 | generate-traffic: 109 | build: 110 | context: . 111 | dockerfile: docker/Dockerfile.generate-traffic 112 | networks: 113 | - e2e 114 | restart: always 115 | depends_on: 116 | selenium-hub: 117 | condition: service_started 118 | chrome: 119 | condition: service_started 120 | provider: 121 | condition: service_healthy 122 | testui: 123 | condition: service_started 124 | 125 | networks: 126 | e2e: 127 | -------------------------------------------------------------------------------- /test/docker/Dockerfile.generate-traffic: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | FROM python:3.7 17 | 18 | COPY docker/test.py . 19 | 20 | RUN pip3 install selenium==4.9.1 21 | 22 | CMD ["python3", "/test.py"] 23 | -------------------------------------------------------------------------------- /test/docker/Dockerfile.provider: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | FROM python:3.7 17 | ARG SW_AGENT_PYTHON_COMMIT 18 | 19 | WORKDIR /app 20 | 21 | RUN git clone https://github.com/apache/skywalking-python.git $(pwd) 22 | 23 | RUN git reset --hard ${SW_AGENT_PYTHON_COMMIT} && git submodule update --init 24 | 25 | RUN make setup install 26 | -------------------------------------------------------------------------------- /test/docker/Dockerfile.test-ui: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | FROM node:18.12 AS builder 17 | 18 | ADD . /skywalking-client-js 19 | WORKDIR /skywalking-client-js 20 | RUN npm run rebuild \ 21 | && npm link 22 | 23 | # download and build skywalking client test 24 | ARG SW_AGENT_CLIENT_JS_TEST_COMMIT 25 | ARG CLIENT_JS_TEST_CODE=${SW_AGENT_CLIENT_JS_TEST_COMMIT}.tar.gz 26 | ARG CLIENT_JS_TEST_CODE_URL=https://github.com/SkyAPMTest/skywalking-client-test/archive/${CLIENT_JS_TEST_CODE} 27 | 28 | WORKDIR /skywalking-client-test 29 | ADD ${CLIENT_JS_TEST_CODE_URL} . 30 | RUN tar -xf ${CLIENT_JS_TEST_CODE} --strip 1 \ 31 | && rm ${CLIENT_JS_TEST_CODE} 32 | 33 | RUN npm install \ 34 | && rm -rf node_modules/skywalking-client-js \ 35 | && npm link skywalking-client-js \ 36 | && npm run build 37 | 38 | FROM nginx:1.19 39 | 40 | COPY --from=builder /skywalking-client-test/dist/ /etc/nginx/html/ 41 | COPY test/docker/nginx.conf /etc/nginx/nginx.conf 42 | -------------------------------------------------------------------------------- /test/docker/nginx.conf: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | worker_processes auto; 17 | events { 18 | worker_connections 1024; 19 | } 20 | http { 21 | server_tokens off; 22 | client_header_timeout 10; 23 | client_body_timeout 10; 24 | # limit_conn_zone $binary_remote_addr zone=addr:5m; 25 | # limit_conn addr 100; 26 | index index.html; 27 | include mime.types; 28 | server { 29 | listen 80; 30 | location /browser { 31 | proxy_pass http://oap:12800; 32 | } 33 | location /v3 { 34 | proxy_pass http://oap:12800; 35 | } 36 | location /info { 37 | proxy_pass http://provider:9091; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/docker/provider.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | import time 18 | 19 | from urllib import request 20 | 21 | from skywalking import agent, config 22 | 23 | if __name__ == '__main__': 24 | config.service_name = 'provider-py' 25 | config.logging_level = 'DEBUG' 26 | agent.start() 27 | 28 | import socketserver 29 | from http.server import BaseHTTPRequestHandler 30 | 31 | class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): 32 | 33 | def _send_cors_headers(self): 34 | """ sets headers required for cors """ 35 | self.send_header("Access-Control-Allow-Origin", "*") 36 | self.send_header("Access-Control-Allow-Methods", "*") 37 | self.send_header("Access-Control-Allow-Headers", "*") 38 | 39 | def do_OPTIONS(self): 40 | self.send_response(200) 41 | self._send_cors_headers() 42 | self.end_headers() 43 | 44 | def do_POST(self): 45 | time.sleep(0.5) 46 | self.send_response(200) 47 | self.send_header('Content-Type', 'application/json') 48 | self._send_cors_headers() 49 | self.end_headers() 50 | self.wfile.write('{"name": "whatever"}'.encode('ascii')) 51 | 52 | PORT = 9091 53 | Handler = SimpleHTTPRequestHandler 54 | 55 | with socketserver.TCPServer(("", PORT), Handler) as httpd: 56 | print("serving at port", PORT) 57 | httpd.serve_forever() 58 | -------------------------------------------------------------------------------- /test/docker/test.py: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import os 17 | import time 18 | 19 | from selenium import webdriver as wd 20 | from selenium.webdriver.common.desired_capabilities import DesiredCapabilities as DC 21 | 22 | hub_remote_url = os.environ.get("HUB_REMOTE_URL", "http://selenium-hub:4444/wd/hub") 23 | test_url = os.environ.get("TEST_URL", "http://testui:80/") 24 | 25 | 26 | def test_screenshot(): 27 | try: 28 | driver.get(test_url) 29 | except Exception as e: 30 | print(e) 31 | 32 | try: 33 | driver = wd.Remote( 34 | command_executor=hub_remote_url, 35 | desired_capabilities=DC.CHROME) 36 | 37 | while True: 38 | test_screenshot() 39 | time.sleep(10) 40 | 41 | finally: 42 | driver.quit() 43 | -------------------------------------------------------------------------------- /test/e2e.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # This file is used to show how to write configuration files and can be used to test. 17 | 18 | setup: 19 | env: compose 20 | file: docker-compose.yml 21 | timeout: 1200 22 | init-system-environment: env 23 | steps: 24 | - name: install yq 25 | command: |- 26 | BASE_DIR=/tmp/skywalking-infra-e2e 27 | BIN_DIR=/usr/local/bin 28 | 29 | if ! command -v yq &> /dev/null; then 30 | mkdir -p $BASE_DIR/yq && cd $BASE_DIR/yq 31 | curl -kLo yq.tar.gz https://github.com/mikefarah/yq/archive/v4.11.1.tar.gz 32 | tar -zxf yq.tar.gz --strip=1 33 | go install && go build -ldflags -s && cp yq $BIN_DIR/ 34 | fi 35 | - name: install swctl 36 | command: |- 37 | BASE_DIR=/tmp/skywalking-infra-e2e 38 | BIN_DIR=/usr/local/bin 39 | 40 | if ! command -v swctl &> /dev/null; then 41 | mkdir -p $BASE_DIR/swctl && cd $BASE_DIR/swctl 42 | curl -kLo skywalking-cli.tar.gz https://github.com/apache/skywalking-cli/archive/${SW_CTL_COMMIT}.tar.gz 43 | tar -zxf skywalking-cli.tar.gz --strip=1 44 | utype=$(uname | awk '{print tolower($0)}') 45 | make $utype && mv bin/swctl-*-$utype-amd64 $BIN_DIR/swctl 46 | fi 47 | cleanup: 48 | # always never success failure 49 | on: always 50 | 51 | trigger: 52 | action: http 53 | interval: 3s 54 | times: 5 55 | url: http://127.0.0.1:${ui_8080}/ 56 | method: GET 57 | 58 | verify: 59 | # verify with retry strategy 60 | retry: 61 | # max retry count 62 | count: 20 63 | # the interval between two retries, in millisecond. 64 | interval: 3s 65 | cases: 66 | # service 67 | - expected: expected/service.yml 68 | query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql browser service ls test-ui 69 | # version 70 | - expected: expected/version.yml 71 | query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql browser version ls --service-name test-ui 72 | # page 73 | - expected: expected/page.yml 74 | query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql browser page ls --service-name test-ui 75 | # browser error log 76 | - expected: expected/error-log.yml 77 | query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql browser logs ls --service-name test-ui --version-name v1.0.0 78 | # browser service metrics 79 | - expected: expected/metrics-has-value.yml 80 | query: | 81 | swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear \ 82 | --name=browser_app_pv --service-id dGVzdC11aQ==.1 |yq e 'to_entries' - 83 | - expected: expected/metrics-has-value.yml 84 | query: | 85 | swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear \ 86 | --name=browser_app_error_sum --service-id dGVzdC11aQ==.1 |yq e 'to_entries' - 87 | # browser version metrics 88 | - expected: expected/metrics-has-value.yml 89 | query: | 90 | swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear \ 91 | --name=browser_app_single_version_pv --service-id dGVzdC11aQ==.1 --instance-name v1.0.0 |yq e 'to_entries' - 92 | - expected: expected/metrics-has-value.yml 93 | query: | 94 | swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear \ 95 | --name=browser_app_single_version_error_sum --service-id dGVzdC11aQ==.1 --instance-name v1.0.0 |yq e 'to_entries' - 96 | # browser page metrics 97 | - expected: expected/metrics-has-value.yml 98 | query: | 99 | swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear \ 100 | --name=browser_app_page_pv --service-id dGVzdC11aQ==.1 --endpoint-name index.html |yq e 'to_entries' - 101 | - expected: expected/metrics-has-value.yml 102 | query: | 103 | swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear \ 104 | --name=browser_app_page_error_sum --service-id dGVzdC11aQ==.1 --endpoint-name index.html |yq e 'to_entries' - 105 | - expected: expected/metrics-has-value.yml 106 | query: | 107 | swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear \ 108 | --name=browser_app_page_js_error_sum --service-id dGVzdC11aQ==.1 --endpoint-name index.html |yq e 'to_entries' - 109 | # # browser performance metrics 110 | - expected: expected/metrics-has-value.yml 111 | query: | 112 | swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear \ 113 | --name=browser_app_page_dom_analysis_avg --service-id dGVzdC11aQ==.1 --endpoint-name index.html |yq e 'to_entries' - 114 | - expected: expected/metrics-has-value.yml 115 | query: | 116 | swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear \ 117 | --name=browser_app_page_dom_ready_avg --service-id dGVzdC11aQ==.1 --endpoint-name index.html |yq e 'to_entries' - 118 | - expected: expected/metrics-has-value.yml 119 | query: | 120 | swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear \ 121 | --name=browser_app_page_load_page_avg --service-id dGVzdC11aQ==.1 --endpoint-name index.html |yq e 'to_entries' - 122 | - expected: expected/metrics-has-value.yml 123 | query: | 124 | swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear \ 125 | --name=browser_app_page_ttl_avg --service-id dGVzdC11aQ==.1 --endpoint-name index.html |yq e 'to_entries' - 126 | - expected: expected/metrics-has-value-percentile.yml 127 | query: | 128 | swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics multiple-linear \ 129 | --name=browser_app_page_dom_ready_percentile --service-id dGVzdC11aQ==.1 --endpoint-name index.html |yq e 'to_entries | with(.[] ; .value=(.value | to_entries))' - 130 | - expected: expected/metrics-has-value-percentile.yml 131 | query: | 132 | swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics multiple-linear \ 133 | --name=browser_app_page_load_page_percentile --service-id dGVzdC11aQ==.1 --endpoint-name index.html |yq e 'to_entries | with(.[] ; .value=(.value | to_entries))' - 134 | # dependency service 135 | - expected: expected/dependency.yml 136 | query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql dependency service --service-id dGVzdC11aQ==.1 137 | # trace 138 | - expected: expected/traces.yml 139 | query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql trace ls 140 | - expected: expected/trace-detail.yml 141 | query: | 142 | swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql trace $( \ 143 | swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql trace ls | grep -A 5 'index.html' | tail -n1 | awk -F ' ' '{print $2}') 144 | -------------------------------------------------------------------------------- /test/env: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | SW_AGENT_PYTHON_COMMIT=c8479000eb729cc86509222fd48b942edcaaca74 17 | SW_AGENT_CLIENT_JS_TEST_COMMIT=7be42cbfd826ac62093a66792b9c66a2c4012772 18 | 19 | SW_CTL_COMMIT=d2f1cff71f3ea9f325ff1c0d99dd0c40a35e527c 20 | -------------------------------------------------------------------------------- /test/expected/dependency.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | nodes: 17 | {{- contains .nodes }} 18 | - id: {{ b64enc "provider-py"}}.1 19 | name: provider-py 20 | type: Python 21 | isreal: true 22 | - id: {{ b64enc "test-ui"}}.1 23 | name: test-ui 24 | type: null 25 | isreal: true 26 | {{- end }} 27 | calls: 28 | {{- contains .calls }} 29 | - source: {{ b64enc "test-ui"}}.1 30 | sourcecomponents: [] 31 | target: {{ b64enc "provider-py"}}.1 32 | targetcomponents: [] 33 | id: {{ b64enc "test-ui"}}.1-{{ b64enc "provider-py"}}.1 34 | detectpoints: 35 | - CLIENT 36 | - SERVER 37 | {{- end }} 38 | -------------------------------------------------------------------------------- /test/expected/error-log.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | logs: 16 | {{- contains .logs }} 17 | - service: test-ui 18 | serviceversion: v1.0.0 19 | time: {{ gt .time 0 }} 20 | pagepath: index.html 21 | category: {{ notEmpty .category }} 22 | grade: {{ notEmpty .grade }} 23 | message: {{ notEmpty .message }} 24 | line: 0 25 | col: 0 26 | stack: {{ .stack }} 27 | errorurl: {{ notEmpty .errorurl }} 28 | firstreportederror: {{ .firstreportederror }} 29 | {{- end }} 30 | total: {{ gt .total 0 }} 31 | -------------------------------------------------------------------------------- /test/expected/metrics-has-value-percentile.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | {{- contains . }} 17 | - key: 0 18 | value: 19 | {{- contains .value }} 20 | - key: {{ notEmpty .key }} 21 | value: {{ ge .value 1 }} 22 | {{- end }} 23 | - key: 1 24 | value: 25 | {{- contains .value }} 26 | - key: {{ notEmpty .key }} 27 | value: {{ ge .value 1 }} 28 | {{- end }} 29 | - key: 2 30 | value: 31 | {{- contains .value }} 32 | - key: {{ notEmpty .key }} 33 | value: {{ ge .value 1 }} 34 | {{- end }} 35 | - key: 3 36 | value: 37 | {{- contains .value }} 38 | - key: {{ notEmpty .key }} 39 | value: {{ ge .value 1 }} 40 | {{- end }} 41 | - key: 4 42 | value: 43 | {{- contains .value }} 44 | - key: {{ notEmpty .key }} 45 | value: {{ ge .value 1 }} 46 | {{- end }} 47 | {{- end }} 48 | -------------------------------------------------------------------------------- /test/expected/metrics-has-value.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | {{- contains . }} 17 | - key: {{ notEmpty .key }} 18 | value: {{ ge .value 1 }} 19 | {{- end }} 20 | -------------------------------------------------------------------------------- /test/expected/page.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | - id: {{ b64enc "test-ui" }}.1_{{ b64enc "index.html" }} 17 | name: index.html 18 | -------------------------------------------------------------------------------- /test/expected/service.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | - id: {{ b64enc "test-ui" }}.1 17 | name: test-ui 18 | group: "" 19 | -------------------------------------------------------------------------------- /test/expected/trace-detail.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | spans: 17 | {{- contains .spans }} 18 | - traceid: {{ notEmpty .traceid }} 19 | segmentid: {{ notEmpty .segmentid }} 20 | spanid: 0 21 | parentspanid: -1 22 | refs: [] 23 | servicecode: test-ui 24 | serviceinstancename: "v1.0.0" 25 | starttime: {{ gt .starttime 0 }} 26 | endtime: {{ gt .endtime 0 }} 27 | endpointname: index.html 28 | type: Exit 29 | peer: provider:9091 30 | component: ajax 31 | iserror: false 32 | layer: Http 33 | tags: 34 | - key: http.method 35 | value: POST 36 | - key: url 37 | value: http://provider:9091/info 38 | logs: [] 39 | - traceid: {{ notEmpty .traceid }} 40 | segmentid: {{ notEmpty .segmentid }} 41 | spanid: 0 42 | parentspanid: -1 43 | refs: 44 | {{- contains .refs }} 45 | - traceid: {{ notEmpty .traceid }} 46 | parentsegmentid: {{ notEmpty .parentsegmentid }} 47 | parentspanid: 0 48 | type: CROSS_PROCESS 49 | {{- end }} 50 | servicecode: provider-py 51 | serviceinstancename: {{ notEmpty .serviceinstancename }} 52 | starttime: {{ gt .starttime 0 }} 53 | endtime: {{ gt .endtime 0 }} 54 | endpointname: /info 55 | type: Entry 56 | peer: {{ notEmpty .peer }} 57 | component: Python 58 | iserror: false 59 | layer: Http 60 | tags: 61 | - key: http.method 62 | value: POST 63 | - key: http.url 64 | value: http://provider:9091/info 65 | - key: http.status.code 66 | value: "200" 67 | logs: [] 68 | {{- end }} 69 | -------------------------------------------------------------------------------- /test/expected/traces.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | traces: 17 | {{- contains .traces }} 18 | - segmentid: {{ notEmpty .segmentid }} 19 | endpointnames: 20 | - index.html 21 | duration: {{ gt .duration 0 }} 22 | start: "{{ notEmpty .start}}" 23 | iserror: false 24 | traceids: 25 | - {{ notEmpty (index .traceids 0) }} 26 | {{- end }} 27 | total: {{ gt .total 0 }} 28 | -------------------------------------------------------------------------------- /test/expected/version.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | - id: {{ b64enc "test-ui" }}.1_{{ b64enc "v1.0.0" }} 17 | name: v1.0.0 18 | attributes: [] 19 | language: UNKNOWN 20 | instanceuuid: {{ b64enc "test-ui" }}.1_{{ b64enc "v1.0.0" }} 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | { 18 | "compilerOptions": { 19 | "outDir": "./lib/", 20 | "noImplicitAny": true, 21 | "sourceMap": true, 22 | "module": "es6", 23 | "target": "es5", 24 | "allowJs": true, 25 | "allowSyntheticDefaultImports": true, 26 | "moduleResolution": "node" 27 | } 28 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | { 19 | "defaultSeverity": "warning", 20 | "extends": [ 21 | "tslint:recommended" 22 | ], 23 | "linterOptions": { 24 | "exclude": [ 25 | "node_modules/**" 26 | ] 27 | }, 28 | "rules": { 29 | "quotemark": [true, "single"], 30 | "indent": [true, "spaces", 2], 31 | "interface-name": false, 32 | "ordered-imports": false, 33 | "object-literal-sort-keys": false, 34 | "no-consecutive-blank-lines": false 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | const path = require('path'); 18 | const webpack = require('webpack'); 19 | const WebpackConcatPlugin = require('webpack-concat-files-plugin'); 20 | 21 | const isDev = process.env.NODE_ENV !== 'production'; 22 | const config = { 23 | entry: './src/index.ts', 24 | devtool: 'inline-source-map', 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.tsx?$/, 29 | use: 'ts-loader', 30 | exclude: /node_modules/, 31 | }, 32 | ], 33 | }, 34 | resolve: { 35 | extensions: ['.tsx', '.ts', '.js'], 36 | mainFiles: ['index'], 37 | }, 38 | output: { 39 | filename: 'index.js', 40 | path: path.resolve(__dirname, 'lib'), 41 | publicPath: '/', 42 | }, 43 | plugins: [ 44 | new WebpackConcatPlugin({ 45 | bundles: [ 46 | { 47 | dest: './lib/src/types.ts', 48 | src: './src/**/*.ts', 49 | }, 50 | ], 51 | }), 52 | ], 53 | optimization: { 54 | moduleIds: 'named', 55 | }, 56 | }; 57 | if (isDev) { 58 | config.mode = 'development'; 59 | } else { 60 | config.mode = 'production'; 61 | } 62 | module.exports = config; 63 | --------------------------------------------------------------------------------