├── .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 |
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 |
--------------------------------------------------------------------------------