├── .github
└── workflows
│ ├── daily-test.yml
│ ├── prerelease.yml
│ ├── publish.yml
│ └── test.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.yml
├── LICENSE
├── README.md
├── lerna.json
├── package.json
├── packages
├── instrumentation-prisma-client
│ ├── .tav.yml
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── instrumentation.ts
│ │ └── version.ts
│ └── tsconfig.json
└── instrumentation-remix
│ ├── .tav.yml
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── package.json
│ ├── src
│ ├── index.ts
│ ├── instrumentation.ts
│ └── version.ts
│ ├── test
│ └── instrumentation-remix.spec.ts
│ └── tsconfig.json
├── scripts
└── version-update.ts
├── tsconfig.base.json
└── yarn.lock
/.github/workflows/daily-test.yml:
--------------------------------------------------------------------------------
1 | name: Daily Tests
2 |
3 | on:
4 | schedule:
5 | # this is 4:17 PM UTC every day (pacific-time afternoon)
6 | - cron: '17 16 * * *'
7 |
8 | workflow_dispatch:
9 |
10 | jobs:
11 |
12 | test:
13 | runs-on: ubuntu-latest
14 |
15 | strategy:
16 | matrix:
17 | node-version: [14.x, 16.x, 18.x, 20.x]
18 |
19 | steps:
20 | - uses: actions/checkout@v2
21 |
22 | - name: Use Node.js ${{ matrix.node_version }}
23 | uses: actions/setup-node@v1
24 | with:
25 | node-version: ${{ matrix.node_version }}
26 |
27 | - name: Fetch all history for all tags and branches
28 | run: git fetch
29 |
30 | - name: Install Dependencies
31 | run: yarn
32 |
33 | - name: Build
34 | run: yarn build:ci
35 |
36 | - name: Test
37 | run: yarn test:ci:all
38 |
--------------------------------------------------------------------------------
/.github/workflows/prerelease.yml:
--------------------------------------------------------------------------------
1 | name: PreRelease
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | Publish:
8 | name: PreRelease
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: checkout
12 | uses: actions/checkout@v2
13 |
14 | - uses: actions/setup-node@v1
15 | with:
16 | node-version: 18
17 | registry-url: https://registry.npmjs.org/
18 |
19 | - name: Install Packages
20 | run: yarn install
21 |
22 | - name: Build
23 | run: yarn build
24 |
25 | - name: Authenticate with Registry
26 | run: |
27 | yarn logout
28 | echo "registry=http://registry.npmjs.org/" >> .npmrc
29 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc
30 | npm whoami
31 | env:
32 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
33 |
34 | - name: Publish package
35 | run: |
36 | yarn publish:ci:prerelease 0.0.0-$(date +"%Y-%m-%d--%H-%M")
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | semanticVersion:
7 | description: 'Semantic Version [major | minor | patch ]'
8 | required: true
9 | default: 'patch'
10 |
11 | jobs:
12 | Publish:
13 | name: Publish
14 | runs-on: ubuntu-latest
15 | if: github.ref == 'refs/heads/main'
16 | steps:
17 | - name: checkout
18 | uses: actions/checkout@v2
19 | with:
20 | token: ${{ secrets.ADMIN_TOKEN }}
21 | ref: 'main'
22 | # pulls all commits (needed for lerna / semantic release to correctly version)
23 | fetch-depth: '0'
24 |
25 | - uses: actions/setup-node@v1
26 | with:
27 | node-version: 18
28 | registry-url: https://registry.npmjs.org/
29 |
30 | - name: Configure CI Git User
31 | run: |
32 | git config --global user.name 'Justin Smith'
33 | git config --global user.email 'justindsmith@gmail.com'
34 | - name: Install Packages
35 | run: yarn install
36 |
37 | - name: Build
38 | run: yarn build
39 |
40 | - name: Authenticate with Registry
41 | run: |
42 | yarn logout
43 | echo "registry=http://registry.npmjs.org/" >> .npmrc
44 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc
45 | npm whoami
46 | env:
47 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
48 |
49 | - name: Publish package
50 | run: |
51 | yarn publish:ci ${{ github.event.inputs.semanticVersion }}
52 | env:
53 | GH_TOKEN: ${{ secrets.ADMIN_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Build and Test
2 |
3 | on:
4 | pull_request:
5 | branches: [main]
6 |
7 | jobs:
8 | prettier:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v2
13 |
14 | - name: Prettier Check
15 | run: yarn prettier:check
16 |
17 | test:
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 |
22 | - uses: actions/checkout@v2
23 | with:
24 | fetch-depth: 0
25 |
26 | - name: Fetch all history for all tags and branches
27 | run: git fetch
28 |
29 | - name: Install Dependencies
30 | run: yarn
31 |
32 | - name: Build
33 | run: yarn build:ci
34 |
35 | - name: Test
36 | run: yarn test:ci:changed
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Dependency directories
4 | node_modules/
5 | dist/
6 |
7 | # Logs
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 |
12 | # IDE configs
13 | .vscode/
14 | .idea
15 |
16 | # typescript
17 | *.tsbuildinfo
18 | *.DS_STORE*
19 |
20 | # resource
21 | post-install-git-resource.json
22 |
23 | # created from sequelize unit tests
24 | memory
25 |
26 | #local database files
27 | *.db
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | packages/*/dist/*
2 | node_modules
3 | *.md
4 | *.yml
--------------------------------------------------------------------------------
/.prettierrc.yml:
--------------------------------------------------------------------------------
1 | printWidth: 120
2 | tabWidth: 2
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # opentelemetry-instrumentations-js
2 |
3 |
4 | [](https://github.com/justindsmith/opentelemetry-instrumentations-js/actions/workflows/daily-test.yml)
5 | [](https://github.com/justindsmith/opentelemetry-instrumentations-js/blob/master/LICENSE)
6 |
7 | ---
8 |
9 | JavaScript extensions for the [OpenTelemetry](https://opentelemetry.io/) project.
10 |
11 | The instrumentations in this repo are:
12 | - vendor neutral
13 | - strictly complies with [open telemetry semantic conventions](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions)
14 | - up to date with latest SDK version
15 |
16 | **Compatible with [SDK stable v1.0.1](https://github.com/open-telemetry/opentelemetry-js/releases/tag/stable%2Fv1.0.1) and [SDK experimental v0.27.0](https://github.com/open-telemetry/opentelemetry-js/releases/tag/experimental%2Fv0.27.0)**
17 | ## Instrumentations
18 | | Instrumentation Package | Instrumented Lib | NPM |
19 | | --- | --- | --- |
20 | | [opentelemetry-instrumentation-remix](./packages/instrumentation-remix) | [`remix`](https://remix.run/) | [](https://www.npmjs.com/package/opentelemetry-instrumentation-remix) []()|
21 | | [opentelemetry-instrumentation-prisma-client](./packages/instrumentation-prisma-client) | [`@prisma/client`](https://github.com/prisma/prisma/tree/main/packages/client) | [](https://www.npmjs.com/package/opentelemetry-instrumentation-prisma-client) []()|
22 |
23 |
24 |
25 | ---
26 |
27 | This repository is inspired by / forked from the [Aspecto](https://www.aspecto.io) [opentelemetry-ext-js](https://github.com/aspecto-io/opentelemetry-ext-js/) repository.
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": ["packages/*"],
3 | "version": "independent",
4 | "npmClient": "yarn",
5 | "useWorkspaces": true,
6 | "command": {
7 | "publish": {
8 | "message": "chore(release): publish"
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "root",
3 | "private": true,
4 | "scripts": {
5 | "watch": "lerna run watch --parallel",
6 | "test": "lerna run test",
7 | "test:ci:changed": "lerna run test:ci --since origin/main --parallel",
8 | "test:ci:all": "lerna run test:ci --parallel",
9 | "build": "lerna run build",
10 | "build:ci": "lerna run build",
11 | "postinstall": "lerna bootstrap",
12 | "prettier": "prettier --config .prettierrc.yml --write --ignore-unknown \"**/*\"",
13 | "prettier:check": "npx prettier@2.3.2 --config .prettierrc.yml --check --ignore-unknown \"**/*\"",
14 | "version:update": "lerna run version:update",
15 | "version": "git add packages/**/version.ts",
16 | "publish:ci": "lerna publish --yes --no-verify-access --allow-branch main --create-release github --conventionalCommits",
17 | "publish:ci:prerelease": "lerna publish --yes --no-verify-access --no-git-tag-version --no-push --no-changelog --dist-tag alpha"
18 | },
19 | "devDependencies": {
20 | "lerna": "^3.22.1",
21 | "prettier": "2.3.2"
22 | },
23 | "workspaces": {
24 | "packages": [
25 | "packages/*"
26 | ],
27 | "nohoist": [
28 | "**/mocha",
29 | "**/ts-node"
30 | ]
31 | },
32 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
33 | }
34 |
--------------------------------------------------------------------------------
/packages/instrumentation-prisma-client/.tav.yml:
--------------------------------------------------------------------------------
1 | '@prisma/client':
2 | versions: "3.8.0 - 3.x"
3 | commands:
4 | - yarn test
--------------------------------------------------------------------------------
/packages/instrumentation-prisma-client/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | # [0.4.0](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-prisma-client@0.3.0...opentelemetry-instrumentation-prisma-client@0.4.0) (2024-10-11)
7 |
8 |
9 | ### Features
10 |
11 | * upgrade opentelemetry instrumentation and semantic-convention deps (v2) ([#58](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/58)) ([af0e41b](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/af0e41b71da4f734a59d1ab09439cb95c53070dd))
12 |
13 |
14 |
15 |
16 |
17 | # [0.3.0](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-prisma-client@0.2.1...opentelemetry-instrumentation-prisma-client@0.3.0) (2023-09-28)
18 |
19 | **Note:** Version bump only for package opentelemetry-instrumentation-prisma-client
20 |
21 |
22 |
23 |
24 |
25 | ## [0.2.1](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-prisma-client@0.2.0...opentelemetry-instrumentation-prisma-client@0.2.1) (2023-09-19)
26 |
27 | **Note:** Version bump only for package opentelemetry-instrumentation-prisma-client
28 |
29 |
30 |
31 |
32 |
33 | # [0.2.0](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-prisma-client@0.1.0...opentelemetry-instrumentation-prisma-client@0.2.0) (2023-01-14)
34 |
35 | **Note:** Version bump only for package opentelemetry-instrumentation-prisma-client
36 |
37 |
38 |
39 |
40 |
41 | # [0.1.0](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-prisma-client@0.0.4...opentelemetry-instrumentation-prisma-client@0.1.0) (2022-09-01)
42 |
43 |
44 | ### Features
45 |
46 | * **prisma:** Use OTel prescribed way of recording exception and span status ([#24](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/24)) ([600921b](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/600921b220c9495f66b4de717f378596f368d085))
47 |
48 |
49 |
50 |
51 |
52 | ## [0.0.4](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-prisma-client@0.0.3...opentelemetry-instrumentation-prisma-client@0.0.4) (2022-07-07)
53 |
54 |
55 | ### Features
56 |
57 | * **prisma:** Add db.statement attribute for raw queries; add optional span attribute configs ([#21](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/21)) ([d400cbf](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/d400cbf3691ee34ec45c1d078af68ae0da4e11a3))
58 |
59 |
60 |
61 |
62 |
63 | ## [0.0.3](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-prisma-client@0.0.2...opentelemetry-instrumentation-prisma-client@0.0.3) (2022-07-04)
64 |
65 | **Note:** Version bump only for package opentelemetry-instrumentation-prisma-client
66 |
67 |
68 |
69 |
70 |
71 | ## [0.0.2](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-prisma-client@0.0.1...opentelemetry-instrumentation-prisma-client@0.0.2) (2022-01-28)
72 |
73 |
74 | ### Bug Fixes
75 |
76 | * **prisma-client:** fix instrumentation class name ([#7](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/7)) ([846ce67](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/846ce675b807a71e734c19336724c3a356b84a4a))
77 |
78 |
79 |
80 |
81 |
82 | ## 0.0.1 (2022-01-28)
83 |
84 |
85 | ### Features
86 |
87 | * **prisma-client:** initial prisma-client instrumentation ([#5](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/5)) ([2113447](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/211344725bb310a4850228cb6133f419b7131ac6))
88 |
--------------------------------------------------------------------------------
/packages/instrumentation-prisma-client/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 |
--------------------------------------------------------------------------------
/packages/instrumentation-prisma-client/README.md:
--------------------------------------------------------------------------------
1 | ## IMPORTANT NOTE: PRISMA NOW HAS [OFFICIAL SUPPORT FOR OPENTELEMETRY](https://www.prisma.io/docs/concepts/components/prisma-client/opentelemetry-tracing). THIS PACKAGE IS DEPRECATED.
2 |
3 | # OpenTelemetry Prisma Client Instrumentation for Node.js
4 | [](https://www.npmjs.com/package/opentelemetry-instrumentation-prisma-client)
5 | [](https://github.com/justindsmith/opentelemetry-instrumentations-js/blob/master/LICENSE)
6 |
7 | This module provides automatic instrumentation for [`@prisma/client`](https://github.com/prisma/prisma/tree/main/packages/client).
8 |
9 | ## Installation
10 |
11 | ```
12 | npm install --save opentelemetry-instrumentation-prisma-client
13 | ```
14 |
15 | ## Supported Versions
16 | - `3.8.0 - 3.x`
17 |
18 | ## Usage
19 | For further automatic instrumentation instruction see the [@opentelemetry/instrumentation](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-instrumentation) package.
20 |
21 | ```js
22 | const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
23 | const { registerInstrumentations } = require('@opentelemetry/instrumentation');
24 | const { PrismaClientInstrumentation } = require('opentelemetry-instrumentation-prisma-client');
25 |
26 | const tracerProvider = new NodeTracerProvider();
27 | tracerProvider.register();
28 |
29 | registerInstrumentations({
30 | instrumentations: [
31 | new PrismaClientInstrumentation()
32 | ]
33 | });
34 | ```
35 |
36 | ## Configuration
37 |
38 | | Name | Type | Default Value | Description | |
39 | |----------------|-------------------------|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------|--|
40 | | spanAttributes | Attributes
| `undefined` | An optional set of Opentelemetry Attributes to be added to the span. For example `spanAttributes: {[SemanticAttributes.DB_SYSTEM]: 'postgresql'}` | |
41 |
42 | ## License
43 | Apache 2.0 - See [LICENSE](https://github.com/justindsmith/opentelemetry-instrumentation-js/blob/main/LICENSE) for more information.
44 |
--------------------------------------------------------------------------------
/packages/instrumentation-prisma-client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "opentelemetry-instrumentation-prisma-client",
3 | "version": "0.4.0",
4 | "description": "open telemetry instrumentation for the prisma auto-generated @prisma/client package",
5 | "keywords": [
6 | "prisma",
7 | "opentelemetry"
8 | ],
9 | "homepage": "https://github.com/justindsmith/opentelemetry-instrumentations-js",
10 | "license": "Apache-2.0",
11 | "main": "dist/src/index.js",
12 | "files": [
13 | "dist/src/**/*.js",
14 | "dist/src/**/*.d.ts",
15 | "dist/src/**/*.js.map",
16 | "LICENSE",
17 | "README.md"
18 | ],
19 | "repository": {
20 | "type": "git",
21 | "url": "https://github.com/justindsmith/opentelemetry-instrumentations-js.git"
22 | },
23 | "scripts": {
24 | "build": "tsc",
25 | "prepare": "yarn run build",
26 | "test": "echo \"No tests enabled\"",
27 | "test:jaeger": "OTEL_EXPORTER_JAEGER_AGENT_HOST=localhost mocha",
28 | "watch": "tsc -w",
29 | "version:update": "ts-node ../../scripts/version-update.ts",
30 | "test-all-versions": "tav",
31 | "test:ci": "yarn test-all-versions",
32 | "version": "yarn run version:update"
33 | },
34 | "bugs": {
35 | "url": "https://github.com/justindsmith/opentelemetry-instrumentations-js/issues"
36 | },
37 | "peerDependencies": {
38 | "@opentelemetry/api": "^1.3.0"
39 | },
40 | "dependencies": {
41 | "@opentelemetry/instrumentation": "^0.52.1",
42 | "@opentelemetry/semantic-conventions": "^1.25.1"
43 | },
44 | "devDependencies": {
45 | "@opentelemetry/api": "^1.6.0",
46 | "@opentelemetry/core": "^1.17.0",
47 | "@prisma/client": "^3.8.1",
48 | "@types/lodash": "^4.14.168",
49 | "@types/mocha": "^8.2.2",
50 | "@types/sinon": "^9.0.11",
51 | "expect": "^26.6.2",
52 | "lodash": "^4.17.21",
53 | "mocha": "^8.4.0",
54 | "opentelemetry-instrumentation-mocha": "0.0.7-alpha.1",
55 | "opentelemetry-instrumentation-testing-utils": "^0.27.0",
56 | "sinon": "^9.2.4",
57 | "test-all-versions": "^5.0.1",
58 | "ts-node": "^10.4.0",
59 | "typescript": "4.5.4"
60 | },
61 | "mocha": {
62 | "extension": [
63 | "ts"
64 | ],
65 | "spec": "test/**/*.spec.ts",
66 | "require": [
67 | "ts-node/register",
68 | "opentelemetry-instrumentation-testing-utils",
69 | "opentelemetry-instrumentation-mocha"
70 | ]
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/packages/instrumentation-prisma-client/src/index.ts:
--------------------------------------------------------------------------------
1 | export { PrismaClientInstrumentation } from "./instrumentation";
2 |
--------------------------------------------------------------------------------
/packages/instrumentation-prisma-client/src/instrumentation.ts:
--------------------------------------------------------------------------------
1 | import opentelemetry, { Attributes, SpanKind, SpanStatusCode } from "@opentelemetry/api";
2 | import {
3 | InstrumentationBase,
4 | InstrumentationConfig,
5 | InstrumentationNodeModuleDefinition,
6 | isWrapped,
7 | } from "@opentelemetry/instrumentation";
8 | import { SemanticAttributes } from "@opentelemetry/semantic-conventions";
9 |
10 | import type * as prismaClient from "@prisma/client";
11 |
12 | import { VERSION } from "./version";
13 |
14 | export interface PrismaClientInstrumentationConfig extends InstrumentationConfig {
15 | /**
16 | * Attibute set to be added to each database span.
17 | */
18 | spanAttributes?: Attributes;
19 | }
20 |
21 | export class PrismaClientInstrumentation extends InstrumentationBase {
22 | constructor(config: PrismaClientInstrumentationConfig = {}) {
23 | super("PrismaClientInstrumentation", VERSION, config);
24 | }
25 |
26 | override getConfig(): PrismaClientInstrumentationConfig {
27 | return this._config;
28 | }
29 |
30 | protected init() {
31 | const prismaClientModule = new InstrumentationNodeModuleDefinition(
32 | "@prisma/client",
33 | ["*"],
34 | (moduleExports: typeof prismaClient) => {
35 | const PrismaClient = moduleExports.PrismaClient.prototype as any;
36 |
37 | // _request
38 | if (isWrapped(PrismaClient["_request"])) {
39 | this._unwrap(PrismaClient, "_request");
40 | }
41 | this._wrap(PrismaClient, "_request", this._patchRequest());
42 |
43 | return moduleExports;
44 | },
45 | (moduleExports: typeof prismaClient) => {
46 | const PrismaClient = moduleExports.PrismaClient.prototype as any;
47 | this._unwrap(PrismaClient, "_request");
48 | }
49 | );
50 |
51 | return [prismaClientModule];
52 | }
53 |
54 | private _patchRequest() {
55 | const plugin = this;
56 | return function (original: () => any) {
57 | return function patchedRequest(this: any) {
58 | const args = arguments[0] as {
59 | clientMethod: string;
60 | args: {
61 | query: string;
62 | parameters: {
63 | values: Array;
64 | __prismaRawParameters__: boolean;
65 | };
66 | };
67 | };
68 |
69 | const span = plugin.tracer.startSpan(
70 | args.clientMethod,
71 | {
72 | kind: SpanKind.CLIENT,
73 | attributes: {
74 | component: "prisma",
75 | [SemanticAttributes.DB_STATEMENT]: args.args.query,
76 | },
77 | },
78 | opentelemetry.context.active()
79 | );
80 |
81 | // Add the supplied attributes from instrumentation configuration
82 | const { spanAttributes: spanAttributes } = plugin.getConfig();
83 | span.setAttributes(spanAttributes);
84 |
85 | return opentelemetry.context.with(opentelemetry.trace.setSpan(opentelemetry.context.active(), span), () => {
86 | const promiseResponse = original.apply(this, arguments as any) as Promise;
87 |
88 | promiseResponse
89 | .catch((error) => {
90 | // Capture error status and exception details in span attributes
91 | span.setAttribute("error", true);
92 | if (error.message) {
93 | span.setAttribute(SemanticAttributes.EXCEPTION_MESSAGE, error.message);
94 | }
95 | if (error.stack) {
96 | span.setAttribute(SemanticAttributes.EXCEPTION_STACKTRACE, error.stack);
97 | }
98 |
99 | // Also use the OTel model for recording exception details and setting the span status
100 | span.setStatus({ code: SpanStatusCode.ERROR });
101 | span.recordException(error);
102 | })
103 | .finally(() => {
104 | span.end();
105 | });
106 |
107 | return promiseResponse;
108 | });
109 | };
110 | };
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/packages/instrumentation-prisma-client/src/version.ts:
--------------------------------------------------------------------------------
1 | // this is autogenerated file, see scripts/version-update.js
2 | export const VERSION = "0.4.0";
3 |
--------------------------------------------------------------------------------
/packages/instrumentation-prisma-client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "outDir": "dist"
6 | },
7 | "include": ["src/**/*.ts", "test/**/*.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/instrumentation-remix/.tav.yml:
--------------------------------------------------------------------------------
1 | '@remix-run/server-runtime':
2 | versions: ">=1.1.0"
3 | commands:
4 | - yarn test
--------------------------------------------------------------------------------
/packages/instrumentation-remix/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | # [0.8.0](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.7.1...opentelemetry-instrumentation-remix@0.8.0) (2024-10-11)
7 |
8 |
9 | ### Features
10 |
11 | * upgrade opentelemetry instrumentation and semantic-convention deps ([#57](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/57)) ([b114c31](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/b114c319c78f786fb8e3ded9a354c244f1e0204f))
12 |
13 |
14 |
15 |
16 |
17 | ## [0.7.1](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.7.0...opentelemetry-instrumentation-remix@0.7.1) (2024-05-31)
18 |
19 |
20 | ### Bug Fixes
21 |
22 | * **remix:** handle a `null` response when adding attributes ([#50](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/50)) ([8d694fa](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/8d694fa7c7242b49fd3d24c3f0248b7b75870856))
23 |
24 |
25 |
26 |
27 |
28 | # [0.7.0](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.6.0...opentelemetry-instrumentation-remix@0.7.0) (2024-04-26)
29 |
30 |
31 | ### Bug Fixes
32 |
33 | * **remix:** support Remix v2.9 ([#49](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/49)) ([c24d291](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/c24d291ac0aff88ebf4a3bf08afc5e4e21302964))
34 |
35 |
36 |
37 |
38 |
39 | # [0.6.0](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.5.2...opentelemetry-instrumentation-remix@0.6.0) (2023-09-28)
40 |
41 | **Note:** Version bump only for package opentelemetry-instrumentation-remix
42 |
43 |
44 |
45 |
46 |
47 | ## [0.5.2](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.5.1...opentelemetry-instrumentation-remix@0.5.2) (2023-09-19)
48 |
49 |
50 | ### Features
51 |
52 | * **remix:** support remix v2 ([#45](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/45)) ([65314bf](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/65314bf20c06a2a30c5ae3fe79ce4ad99db80611))
53 |
54 |
55 |
56 |
57 |
58 | ## [0.5.1](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.5.0...opentelemetry-instrumentation-remix@0.5.1) (2023-09-19)
59 |
60 |
61 | ### Bug Fixes
62 |
63 | * **instrumentation-remix:** handle undefined action keys correctly ([#44](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/44)) ([d9dfcd4](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/d9dfcd4d62f35c83847f9fd34539468049f93632))
64 |
65 |
66 |
67 |
68 |
69 | # [0.5.0](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.4.0...opentelemetry-instrumentation-remix@0.5.0) (2023-08-04)
70 |
71 |
72 | ### Features
73 |
74 | * **remix:** Add `http.route` to request handler spans ([#41](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/41)) ([1fc2a88](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/1fc2a8863c69518fa4828ba5630a995fe51c1efe))
75 |
76 |
77 | ### BREAKING CHANGES
78 |
79 | * **remix:** The `remix.request` span name now appends the route path. Use the `code.function`: `requestHandler` attribute as a stable way to find the requestHandler span.
80 |
81 |
82 |
83 |
84 |
85 | # [0.4.0](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.3.0...opentelemetry-instrumentation-remix@0.4.0) (2023-07-26)
86 |
87 |
88 | ### Features
89 |
90 | * **remix:** Report errors as exception events ([#37](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/37)) ([a21f2ab](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/a21f2ab91c39888865d74270249ee204cdcbd04d))
91 |
92 |
93 |
94 |
95 |
96 | # [0.3.0](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.2.0...opentelemetry-instrumentation-remix@0.3.0) (2023-01-14)
97 |
98 |
99 | ### Bug Fixes
100 |
101 | * **remix:** add compatibility with remix v1.8.0 ([#28](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/28)) ([efc0c85](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/efc0c8509829551979e9c46fd72c848d8e6f772b))
102 | * **remix:** Update tests based on @remix-run/router loader revalidation change ([#29](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/29)) ([75b525e](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/75b525e7b4a5c15726c67546277ba7d2230931f4)), closes [/github.com/remix-run/react-router/blob/main/packages/router/CHANGELOG.md#105](https://github.com//github.com/remix-run/react-router/blob/main/packages/router/CHANGELOG.md/issues/105)
103 |
104 |
105 |
106 |
107 |
108 | # [0.2.0](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.1.2...opentelemetry-instrumentation-remix@0.2.0) (2022-11-02)
109 |
110 |
111 | * feat(remix)!: support remix 1.7.3+ breaking changes (#26) ([d7e132a](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/d7e132aff89844cd8b2906f6738784c9c21ddf3a)), closes [#26](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/26)
112 |
113 |
114 | ### BREAKING CHANGES
115 |
116 | * `match.pathname` and `match.route.path` have been removed due to inability to read associated properties from Remix loader and action function signatures.
117 |
118 |
119 |
120 |
121 |
122 | ## [0.1.2](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.1.1...opentelemetry-instrumentation-remix@0.1.2) (2022-07-07)
123 |
124 | **Note:** Version bump only for package opentelemetry-instrumentation-remix
125 |
126 |
127 |
128 |
129 |
130 | ## [0.1.1](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.1.0...opentelemetry-instrumentation-remix@0.1.1) (2022-07-03)
131 |
132 |
133 | ### Bug Fixes
134 |
135 | * **remix:** update remix to 1.6.3; fix changed server-runtime data import paths ([#15](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/15)) ([4bd5cfb](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/4bd5cfb9e356d2d79413d1b72aa6fce19c0e4d10))
136 |
137 |
138 |
139 |
140 |
141 | # [0.1.0](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.0.5...opentelemetry-instrumentation-remix@0.1.0) (2022-06-19)
142 |
143 |
144 | ### Bug Fixes
145 |
146 | * **remix:** fix tests to support new createRequestHandler definition ([#13](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/13)) ([36b1864](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/36b186482c8ea9dace0a5aadc7cf7fabd7ef4200))
147 |
148 |
149 | ### Features
150 |
151 | * **remix:** update form handling to default include form attributes ([#14](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/14)) ([84d1edd](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/84d1edd8754bfb6cfa4e17c7c983231352f0fc70))
152 |
153 |
154 | ### BREAKING CHANGES
155 |
156 | * **remix:** form attributes now default to being included, use the `actionFormDataAttributes` to exclude attributes
157 |
158 |
159 |
160 |
161 |
162 | ## [0.0.5](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.0.4...opentelemetry-instrumentation-remix@0.0.5) (2022-03-19)
163 |
164 |
165 | ### Bug Fixes
166 |
167 | * **remix:** use @remix-run/server-runtime for version testing ([#12](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/12)) ([6e514de](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/6e514dea8cb65c4f064341b2f5be6993743601b9))
168 |
169 |
170 | ### Features
171 |
172 | * **remix:** add FormData field attributes to actions ([#10](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/10)) ([bb2f0f3](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/bb2f0f3872359bd66fb8331752dc42660bb8170c)), closes [#8](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/8)
173 |
174 |
175 |
176 |
177 |
178 | ## [0.0.4](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.0.3...opentelemetry-instrumentation-remix@0.0.4) (2022-01-28)
179 |
180 | **Note:** Version bump only for package opentelemetry-instrumentation-remix
181 |
182 |
183 |
184 |
185 |
186 | ## [0.0.3](https://github.com/justindsmith/opentelemetry-instrumentations-js/compare/opentelemetry-instrumentation-remix@0.0.2...opentelemetry-instrumentation-remix@0.0.3) (2022-01-18)
187 |
188 |
189 | ### Features
190 |
191 | * **remix:** add code function attribute ([#4](https://github.com/justindsmith/opentelemetry-instrumentations-js/issues/4)) ([c61ae28](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/c61ae286da837665ce2128078a449eff529bff51))
192 |
193 |
194 |
195 |
196 |
197 | ## 0.0.2 (2022-01-17)
198 |
199 |
200 | ### Features
201 |
202 | * **remix:** add createRequestHanlder instrumentation ([dc7427f](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/dc7427f3883e2d34bcb1786bfb707922b235715d))
203 | * **remix:** add loader and action unit tests ([a379a58](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/a379a58032df3db795f7cfbabf4c85108454b395))
204 | * **remix:** initial instrumentation-remix ([55ee874](https://github.com/justindsmith/opentelemetry-instrumentations-js/commit/55ee8748427c74165895a73c4c1c2edf746a65d1))
205 |
--------------------------------------------------------------------------------
/packages/instrumentation-remix/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 |
--------------------------------------------------------------------------------
/packages/instrumentation-remix/README.md:
--------------------------------------------------------------------------------
1 | # OpenTelemetry Remix Instrumentation for Node.js
2 | [](https://www.npmjs.com/package/opentelemetry-instrumentation-remix)
3 | [](https://github.com/justindsmith/opentelemetry-instrumentations-js/blob/master/LICENSE)
4 |
5 | This module provides automatic instrumentation for [`remix`](https://remix.run/).
6 |
7 | ## Installation
8 |
9 | ```
10 | npm install --save opentelemetry-instrumentation-remix
11 | ```
12 |
13 | ## Supported Versions
14 | - `1.1.0 - 2.x`
15 |
16 | ### Cloudflare Worker Warning
17 | This instrumentation does NOT support Cloudflare Workers. For more details follow [opentelemetry-js issue #1214](https://github.com/open-telemetry/opentelemetry-js/issues/1214).
18 |
19 | ## Usage
20 | For further automatic instrumentation instruction see the [@opentelemetry/instrumentation](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-instrumentation) package.
21 |
22 | ```js
23 | const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
24 | const { registerInstrumentations } = require('@opentelemetry/instrumentation');
25 | const { RemixInstrumentation } = require('opentelemetry-instrumentation-remix');
26 |
27 | const tracerProvider = new NodeTracerProvider();
28 | tracerProvider.register();
29 |
30 | registerInstrumentations({
31 | instrumentations: [
32 | new RemixInstrumentation()
33 | ]
34 | });
35 | ```
36 | ## Configuration
37 |
38 | | Name | Type | Default Value | Description |
39 | |--------------------------|----------------------------------------------------|-----------------------------|---------------------------------------------------------------------------------------------|
40 | | actionFormDataAttributes | Record
| `{ _action: "actionType" }` | Mapping of FormData field to span attribute names. |
41 | | legacyErrorAttributes | boolean
| `false` | Whether to emit errors in the form of span attributes, as well as in span exception events. |
42 |
43 | ## Instrumentation
44 |
45 | ### requestHandler
46 | Emitted for every request into remix server.
47 |
48 | | Operation | Example | Notes |
49 | |-----------------------------|--------------------------------|-------------------------------------------------------------------------------------|
50 | | `remix.request [routePath]` | `remix.request /jokes/:jokeId` | If the request does not match a route, the name of the span will be `remix.request` |
51 |
52 |
53 | | Attribute | Description | Example Value |
54 | |------------------------|------------------------------------------------------------------------|--------------------------------------------------------------|
55 | | `code.function` | Name of executed function | `"requestHandler"` |
56 | | `http.method` | HTTP method | `"POST"` |
57 | | `http.url` | HTTP URL | `"https://remix.jokes/jokes/new?_data=routes%2Fjokes%2Fnew"` |
58 | | `http.route` | HTTP route path, added if the request matches a route | `"/jokes/:jokeId"` |
59 | | `http.status_code` | Response status code | `200` |
60 | | `match.route.id` | Remix matched route ID, added if the request matches a route | `"routes/jokes/$jokeId"` |
61 | | `error` | Added if error detected and if `legacyErrorAttributes` enabled | `true` |
62 | | `exception.message` | Error message, if `legacyErrorAttributes` enabled and if applicable | `"Kaboom!"` |
63 | | `exception.stacktrace` | Error stacktrace, if `legacyErrorAttributes` enabled and if applicable | [stacktrace] |
64 |
65 | | Status Code | Description |
66 | |------------------------|--------------------------------------------|
67 | | `SpanStatusCode.ERROR` | When an exception occurs during evaluation |
68 |
69 | | Event | Attributes | Description |
70 | |-----------|------------------------|---------------------------------|
71 | | Exception | `exception.message` | Error message, if applicable |
72 | | | `exception.stacktrace` | Error stacktrace, if applicable |
73 |
74 | ### loader
75 | Emitted for every `loader` called.
76 |
77 | | Operation | Example |
78 | |--------------------|-------------------------------|
79 | | `LOADER [routeId]` | `LOADER routes/jokes/$jokeId` |
80 |
81 | | Attribute | Description | Example Value |
82 | |----------------------------|------------------------------------------------------------------------|----------------------------------------------------------------|
83 | | `code.function` | Name of executed function | `"loader"` |
84 | | `http.method` | HTTP method | `"POST"` |
85 | | `http.url` | HTTP URL | `"https://remix.jokes/jokes/new?_data=routes%2Fjokes%2Fnew"` |
86 | | `http.status_code` | Response status code | `200` |
87 | | `match.route.id` | Remix matched route id | `"routes/jokes/$jokeId"` |
88 | | `match.params.[paramName]` | Value for each remix matched param | `[match.params.jokeId]: 23fc7bcf-2d35-4c70-877f-338eca1fd3ef"` |
89 | | `error` | Added if error detected and if `legacyErrorAttributes` enabled | `true` |
90 | | `exception.message` | Error message, if `legacyErrorAttributes` enabled and if applicable | `"Kaboom!"` |
91 | | `exception.stacktrace` | Error stacktrace, if `legacyErrorAttributes` enabled and if applicable | [stacktrace] |
92 |
93 | | Status Code | Description |
94 | |------------------------|--------------------------------------------|
95 | | `SpanStatusCode.ERROR` | When an exception occurs during evaluation |
96 |
97 | | Event | Attributes | Description |
98 | |-----------|------------------------|---------------------------------|
99 | | Exception | `exception.message` | Error message, if applicable |
100 | | | `exception.stacktrace` | Error stacktrace, if applicable |
101 |
102 | ### action
103 | Emitted for every `action` called.
104 |
105 | | Operation | Example |
106 | |--------------------|---------------------------|
107 | | `ACTION [routeId]` | `ACTION routes/jokes/new` |
108 |
109 | | Attribute | Description | Example Value |
110 | |----------------------------|------------------------------------------------------------------------|-----------------------------------------------------------------|
111 | | `code.function` | Name of executed function | `"action"` |
112 | | `http.method` | HTTP method | `"POST"` |
113 | | `http.url` | HTTP URL | `"https://remix.jokes/jokes/new?_data=routes%2Fjokes%2Fnew"` |
114 | | `http.status_code` | Response status code | `200` |
115 | | `match.route.id` | Remix matched route id | `"routes/jokes/$jokeId"` |
116 | | `match.params.[paramName]` | Value for each remix matched param | `[match.params.jokeId]: "23fc7bcf-2d35-4c70-877f-338eca1fd3ef"` |
117 | | `formData.[fieldName]` | Value for each configured FormData field | `[formData.actionType]: "createJoke"` |
118 | | `error` | Added if error detected and if `legacyErrorAttributes` enabled | `true` |
119 | | `exception.message` | Error message, if `legacyErrorAttributes` enabled and if applicable | `"Kaboom!"` |
120 | | `exception.stacktrace` | Error stacktrace, if `legacyErrorAttributes` enabled and if applicable | [stacktrace] |
121 |
122 | | Status Code | Description |
123 | |------------------------|--------------------------------------------|
124 | | `SpanStatusCode.ERROR` | When an exception occurs during evaluation |
125 |
126 | | Event | Attributes | Description |
127 | |-----------|------------------------|---------------------------------|
128 | | Exception | `exception.message` | Error message, if applicable |
129 | | | `exception.stacktrace` | Error stacktrace, if applicable |
130 |
131 | ## License
132 | Apache 2.0 - See [LICENSE](https://github.com/justindsmith/opentelemetry-instrumentation-js/blob/main/LICENSE) for more information.
133 |
--------------------------------------------------------------------------------
/packages/instrumentation-remix/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "opentelemetry-instrumentation-remix",
3 | "version": "0.8.0",
4 | "description": "open telemetry instrumentation for the `remix` package",
5 | "keywords": [
6 | "remix",
7 | "opentelemetry"
8 | ],
9 | "homepage": "https://github.com/justindsmith/opentelemetry-instrumentations-js",
10 | "license": "Apache-2.0",
11 | "main": "dist/src/index.js",
12 | "files": [
13 | "dist/src/**/*.js",
14 | "dist/src/**/*.d.ts",
15 | "dist/src/**/*.js.map",
16 | "LICENSE",
17 | "README.md"
18 | ],
19 | "repository": {
20 | "type": "git",
21 | "url": "https://github.com/justindsmith/opentelemetry-instrumentations-js.git"
22 | },
23 | "scripts": {
24 | "build": "tsc",
25 | "prepare": "yarn run build",
26 | "test": "mocha",
27 | "test:jaeger": "OTEL_EXPORTER_JAEGER_AGENT_HOST=localhost mocha",
28 | "watch": "tsc -w",
29 | "version:update": "ts-node ../../scripts/version-update.ts",
30 | "test-all-versions": "tav",
31 | "test:ci": "yarn test-all-versions",
32 | "version": "yarn run version:update"
33 | },
34 | "bugs": {
35 | "url": "https://github.com/justindsmith/opentelemetry-instrumentations-js/issues"
36 | },
37 | "peerDependencies": {
38 | "@opentelemetry/api": "^1.3.0"
39 | },
40 | "dependencies": {
41 | "@opentelemetry/instrumentation": "^0.52.1",
42 | "@opentelemetry/semantic-conventions": "^1.25.1"
43 | },
44 | "devDependencies": {
45 | "@opentelemetry/api": "^1.6.0",
46 | "@opentelemetry/core": "^1.17.0",
47 | "@remix-run/node": "2.0.0",
48 | "@remix-run/router": "1.9.0",
49 | "@remix-run/server-runtime": "2.0.0",
50 | "@types/lodash": "^4.14.168",
51 | "@types/mocha": "^8.2.2",
52 | "@types/node": "^20.6.2",
53 | "@types/react": "^18.0.15",
54 | "@types/react-dom": "^18.0.11",
55 | "@types/semver": "^7.3.9",
56 | "@types/sinon": "^9.0.11",
57 | "expect": "^26.6.2",
58 | "history": "5.3.0",
59 | "lodash": "^4.17.21",
60 | "mocha": "^10.2.0",
61 | "opentelemetry-instrumentation-mocha": "0.0.7-alpha.1",
62 | "opentelemetry-instrumentation-testing-utils": "^0.27.0",
63 | "react": "^18.2.0",
64 | "react-dom": "^18.2.0",
65 | "react-router-dom": "6.14.2",
66 | "semver": "^7.3.5",
67 | "sinon": "^9.2.4",
68 | "test-all-versions": "^5.0.1",
69 | "ts-node": "^10.9.1",
70 | "typescript": "5.1.6"
71 | },
72 | "mocha": {
73 | "extension": [
74 | "ts"
75 | ],
76 | "spec": "test/**/*.spec.ts",
77 | "require": [
78 | "ts-node/register",
79 | "opentelemetry-instrumentation-testing-utils",
80 | "opentelemetry-instrumentation-mocha"
81 | ]
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/packages/instrumentation-remix/src/index.ts:
--------------------------------------------------------------------------------
1 | export { RemixInstrumentation } from "./instrumentation";
2 |
--------------------------------------------------------------------------------
/packages/instrumentation-remix/src/instrumentation.ts:
--------------------------------------------------------------------------------
1 | import opentelemetry, { Span, SpanStatusCode } from "@opentelemetry/api";
2 | import {
3 | InstrumentationBase,
4 | InstrumentationConfig,
5 | InstrumentationNodeModuleDefinition,
6 | InstrumentationNodeModuleFile,
7 | isWrapped,
8 | } from "@opentelemetry/instrumentation";
9 | import { SemanticAttributes } from "@opentelemetry/semantic-conventions";
10 |
11 | import type * as remixRunServerRuntime from "@remix-run/server-runtime";
12 | import type * as remixRunServerRuntimeRouteMatching from "@remix-run/server-runtime/dist/routeMatching";
13 | import type { RouteMatch } from "@remix-run/server-runtime/dist/routeMatching";
14 | import type { ServerRoute } from "@remix-run/server-runtime/dist/routes";
15 | import type * as remixRunServerRuntimeData from "@remix-run/server-runtime/dist/data";
16 |
17 | import type { Params } from "@remix-run/router";
18 |
19 | import { VERSION } from "./version";
20 |
21 | const RemixSemanticAttributes = {
22 | MATCH_PARAMS: "match.params",
23 | MATCH_ROUTE_ID: "match.route.id",
24 | };
25 |
26 | export interface RemixInstrumentationConfig extends InstrumentationConfig {
27 | /**
28 | * Mapping of FormData field to span attribute names. Appends attribute as `formData.${name}`.
29 | *
30 | * Provide `true` value to use the FormData field name as the attribute name, or provide
31 | * a `string` value to map the field name to a custom attribute name.
32 | *
33 | * @default { _action: "actionType" }
34 | */
35 | actionFormDataAttributes?: Record;
36 | /**
37 | * Whether to emit errors in the form of span attributes, as well as in span exception events.
38 | * Defaults to `false`, meaning that only span exception events are emitted.
39 | */
40 | legacyErrorAttributes?: boolean;
41 | }
42 |
43 | const DEFAULT_CONFIG: RemixInstrumentationConfig = {
44 | actionFormDataAttributes: {
45 | _action: "actionType",
46 | },
47 | legacyErrorAttributes: false,
48 | };
49 |
50 | export class RemixInstrumentation extends InstrumentationBase {
51 | constructor(config: RemixInstrumentationConfig = {}) {
52 | super("RemixInstrumentation", VERSION, Object.assign({}, DEFAULT_CONFIG, config));
53 | }
54 |
55 | override getConfig(): RemixInstrumentationConfig {
56 | return this._config;
57 | }
58 |
59 | override setConfig(config: RemixInstrumentationConfig = {}) {
60 | this._config = Object.assign({}, DEFAULT_CONFIG, config);
61 | }
62 |
63 | protected init() {
64 | const remixRunServerRuntimeRouteMatchingFile = new InstrumentationNodeModuleFile(
65 | "@remix-run/server-runtime/dist/routeMatching.js",
66 | ["1.6.2 - 2.x"],
67 | (moduleExports: typeof remixRunServerRuntimeRouteMatching) => {
68 | // createRequestHandler
69 | if (isWrapped(moduleExports["matchServerRoutes"])) {
70 | this._unwrap(moduleExports, "matchServerRoutes");
71 | }
72 | this._wrap(moduleExports, "matchServerRoutes", this._patchMatchServerRoutes());
73 |
74 | return moduleExports;
75 | },
76 | (moduleExports: typeof remixRunServerRuntimeRouteMatching) => {
77 | this._unwrap(moduleExports, "matchServerRoutes");
78 | }
79 | );
80 |
81 | /*
82 | * Before Remix v1.6.2 we needed to wrap `@remix-run/server-runtime/routeMatching` module import instead of
83 | * `@remix-run/server-runtime/dist/routeMatching` module import. The wrapping logic is all the same though.
84 | */
85 | const remixRunServerRuntimeRouteMatchingPre_1_6_2_File = new InstrumentationNodeModuleFile(
86 | "@remix-run/server-runtime/routeMatching.js",
87 | ["1.0 - 1.6.1"],
88 | (moduleExports: typeof remixRunServerRuntimeRouteMatching) => {
89 | // matchServerRoutes
90 | if (isWrapped(moduleExports["matchServerRoutes"])) {
91 | this._unwrap(moduleExports, "matchServerRoutes");
92 | }
93 | this._wrap(moduleExports, "matchServerRoutes", this._patchMatchServerRoutes());
94 |
95 | return moduleExports;
96 | },
97 | (moduleExports: typeof remixRunServerRuntimeRouteMatching) => {
98 | this._unwrap(moduleExports, "matchServerRoutes");
99 | }
100 | );
101 |
102 | /*
103 | * Before Remix v1.6.2 we needed to wrap `@remix-run/server-runtime/data` module import instead of
104 | * `@remix-run/server-runtime/dist/data` module import. The wrapping logic is all the same though.
105 | */
106 | const remixRunServerRuntimeDataPre_1_6_2_File = new InstrumentationNodeModuleFile(
107 | "@remix-run/server-runtime/data.js",
108 | ["1.0 - 1.6.1"],
109 | (moduleExports: typeof remixRunServerRuntimeData) => {
110 | // callRouteLoader
111 | if (isWrapped(moduleExports["callRouteLoader"])) {
112 | // @ts-ignore
113 | this._unwrap(moduleExports, "callRouteLoader");
114 | }
115 | // @ts-ignore
116 | this._wrap(moduleExports, "callRouteLoader", this._patchCallRouteLoaderPre_1_7_2());
117 |
118 | // callRouteAction
119 | if (isWrapped(moduleExports["callRouteAction"])) {
120 | // @ts-ignore
121 | this._unwrap(moduleExports, "callRouteAction");
122 | }
123 | // @ts-ignore
124 | this._wrap(moduleExports, "callRouteAction", this._patchCallRouteActionPre_1_7_2());
125 | return moduleExports;
126 | },
127 | (moduleExports: typeof remixRunServerRuntimeData) => {
128 | // @ts-ignore
129 | this._unwrap(moduleExports, "callRouteLoader");
130 | // @ts-ignore
131 | this._unwrap(moduleExports, "callRouteAction");
132 | }
133 | );
134 |
135 | /**
136 | * Before Remix 1.7.3 we received the full `Match` object for each path in the route chain,
137 | * afterwards we only receive the `routeId` and associated `params`.
138 | */
139 | const remixRunServerRuntimeDataPre_1_7_2_File = new InstrumentationNodeModuleFile(
140 | "@remix-run/server-runtime/dist/data.js",
141 | ["1.6.2 - 1.7.2"],
142 | (moduleExports: typeof remixRunServerRuntimeData) => {
143 | // callRouteLoader
144 | if (isWrapped(moduleExports["callRouteLoader"])) {
145 | // @ts-ignore
146 | this._unwrap(moduleExports, "callRouteLoader");
147 | }
148 | // @ts-ignore
149 | this._wrap(moduleExports, "callRouteLoader", this._patchCallRouteLoaderPre_1_7_2());
150 |
151 | // callRouteAction
152 | if (isWrapped(moduleExports["callRouteAction"])) {
153 | // @ts-ignore
154 | this._unwrap(moduleExports, "callRouteAction");
155 | }
156 | // @ts-ignore
157 | this._wrap(moduleExports, "callRouteAction", this._patchCallRouteActionPre_1_7_2());
158 | return moduleExports;
159 | },
160 | (moduleExports: typeof remixRunServerRuntimeData) => {
161 | // @ts-ignore
162 | this._unwrap(moduleExports, "callRouteLoader");
163 | // @ts-ignore
164 | this._unwrap(moduleExports, "callRouteAction");
165 | }
166 | );
167 |
168 | const remixRunServerRuntimeDataPre_1_7_6_And_Post_2_9_File = new InstrumentationNodeModuleFile(
169 | "@remix-run/server-runtime/dist/data.js",
170 | ["1.7.3 - 1.7.6", "2.9.0 - 2.x"],
171 | (moduleExports: typeof remixRunServerRuntimeData) => {
172 | // callRouteLoader
173 | if (isWrapped(moduleExports["callRouteLoader"])) {
174 | // @ts-ignore
175 | this._unwrap(moduleExports, "callRouteLoader");
176 | }
177 | // @ts-ignore
178 | this._wrap(moduleExports, "callRouteLoader", this._patchCallRouteLoader());
179 |
180 | // callRouteAction
181 | if (isWrapped(moduleExports["callRouteAction"])) {
182 | // @ts-ignore
183 | this._unwrap(moduleExports, "callRouteAction");
184 | }
185 | // @ts-ignore
186 | this._wrap(moduleExports, "callRouteAction", this._patchCallRouteAction());
187 | return moduleExports;
188 | },
189 | (moduleExports: typeof remixRunServerRuntimeData) => {
190 | // @ts-ignore
191 | this._unwrap(moduleExports, "callRouteLoader");
192 | // @ts-ignore
193 | this._unwrap(moduleExports, "callRouteAction");
194 | }
195 | );
196 |
197 | /*
198 | * In Remix 1.8.0, the callXXLoader functions were renamed to callXXLoaderRR. They were renamed back in 2.9.0.
199 | */
200 | const remixRunServerRuntimeDataBetween_1_8_And_2_8_File = new InstrumentationNodeModuleFile(
201 | "@remix-run/server-runtime/dist/data.js",
202 | ["1.8.0 - 2.8.x"],
203 | (moduleExports: typeof remixRunServerRuntimeData) => {
204 | // callRouteLoader
205 | if (isWrapped(moduleExports["callRouteLoaderRR"])) {
206 | // @ts-ignore
207 | this._unwrap(moduleExports, "callRouteLoaderRR");
208 | }
209 | // @ts-ignore
210 | this._wrap(moduleExports, "callRouteLoaderRR", this._patchCallRouteLoader());
211 |
212 | // callRouteAction
213 | if (isWrapped(moduleExports["callRouteActionRR"])) {
214 | // @ts-ignore
215 | this._unwrap(moduleExports, "callRouteActionRR");
216 | }
217 | // @ts-ignore
218 | this._wrap(moduleExports, "callRouteActionRR", this._patchCallRouteAction());
219 | return moduleExports;
220 | },
221 | (moduleExports: typeof remixRunServerRuntimeData) => {
222 | // @ts-ignore
223 | this._unwrap(moduleExports, "callRouteLoaderRR");
224 | // @ts-ignore
225 | this._unwrap(moduleExports, "callRouteActionRR");
226 | }
227 | );
228 |
229 | const remixRunServerRuntimeModule = new InstrumentationNodeModuleDefinition(
230 | "@remix-run/server-runtime",
231 | [">=1.*"],
232 | (moduleExports: typeof remixRunServerRuntime) => {
233 | // createRequestHandler
234 | if (isWrapped(moduleExports["createRequestHandler"])) {
235 | this._unwrap(moduleExports, "createRequestHandler");
236 | }
237 | this._wrap(moduleExports, "createRequestHandler", this._patchCreateRequestHandler());
238 |
239 | return moduleExports;
240 | },
241 | (moduleExports: typeof remixRunServerRuntime) => {
242 | this._unwrap(moduleExports, "createRequestHandler");
243 | },
244 | [
245 | remixRunServerRuntimeRouteMatchingFile,
246 | remixRunServerRuntimeRouteMatchingPre_1_6_2_File,
247 | remixRunServerRuntimeDataPre_1_6_2_File,
248 | remixRunServerRuntimeDataPre_1_7_2_File,
249 | remixRunServerRuntimeDataPre_1_7_6_And_Post_2_9_File,
250 | remixRunServerRuntimeDataBetween_1_8_And_2_8_File,
251 | ]
252 | );
253 |
254 | return remixRunServerRuntimeModule;
255 | }
256 |
257 | private _patchMatchServerRoutes(): (original: typeof remixRunServerRuntimeRouteMatching.matchServerRoutes) => any {
258 | const plugin = this;
259 | return function matchServerRoutes(original) {
260 | return function patchMatchServerRoutes(this: any): RouteMatch {
261 | const result = original.apply(this, arguments as any);
262 |
263 | const span = opentelemetry.trace.getSpan(opentelemetry.context.active());
264 |
265 | const route = (result || []).slice(-1)[0]?.route;
266 |
267 | const routePath = route?.path;
268 | if (span && routePath) {
269 | span.setAttribute(SemanticAttributes.HTTP_ROUTE, routePath);
270 | span.updateName(`remix.request ${routePath}`);
271 | }
272 |
273 | const routeId = route?.id;
274 | if (span && routeId) {
275 | span.setAttribute(RemixSemanticAttributes.MATCH_ROUTE_ID, routeId);
276 | }
277 |
278 | return result;
279 | };
280 | };
281 | }
282 |
283 | private _patchCreateRequestHandler(): (original: typeof remixRunServerRuntime.createRequestHandler) => any {
284 | const plugin = this;
285 | return function createRequestHandler(original) {
286 | return function patchCreateRequestHandler(this: any): remixRunServerRuntime.RequestHandler {
287 | const originalRequestHandler: remixRunServerRuntime.RequestHandler = original.apply(this, arguments as any);
288 |
289 | return (request: Request, loadContext?: remixRunServerRuntime.AppLoadContext) => {
290 | const span = plugin.tracer.startSpan(
291 | `remix.request`,
292 | {
293 | attributes: { [SemanticAttributes.CODE_FUNCTION]: "requestHandler" },
294 | },
295 | opentelemetry.context.active()
296 | );
297 | addRequestAttributesToSpan(span, request);
298 |
299 | const originalResponsePromise = opentelemetry.context.with(
300 | opentelemetry.trace.setSpan(opentelemetry.context.active(), span),
301 | () => originalRequestHandler(request, loadContext)
302 | );
303 | return originalResponsePromise
304 | .then((response) => {
305 | addResponseAttributesToSpan(span, response);
306 | return response;
307 | })
308 | .catch((error) => {
309 | plugin.addErrorToSpan(span, error);
310 | throw error;
311 | })
312 | .finally(() => {
313 | span.end();
314 | });
315 | };
316 | };
317 | };
318 | }
319 |
320 | // @ts-ignore
321 | private _patchCallRouteLoader(): (original: typeof remixRunServerRuntimeData.callRouteLoader) => any {
322 | const plugin = this;
323 | return function callRouteLoader(original) {
324 | return function patchCallRouteLoader(this: any): Promise {
325 | const [params] = arguments as unknown as any;
326 |
327 | const span = plugin.tracer.startSpan(
328 | `LOADER ${params.routeId}`,
329 | { attributes: { [SemanticAttributes.CODE_FUNCTION]: "loader" } },
330 | opentelemetry.context.active()
331 | );
332 |
333 | addRequestAttributesToSpan(span, params.request);
334 | addMatchAttributesToSpan(span, { routeId: params.routeId, params: params.params });
335 |
336 | return opentelemetry.context.with(opentelemetry.trace.setSpan(opentelemetry.context.active(), span), () => {
337 | const originalResponsePromise: Promise = original.apply(this, arguments as any);
338 | return originalResponsePromise
339 | .then((response) => {
340 | addResponseAttributesToSpan(span, response);
341 | return response;
342 | })
343 | .catch((error) => {
344 | plugin.addErrorToSpan(span, error);
345 | throw error;
346 | })
347 | .finally(() => {
348 | span.end();
349 | });
350 | });
351 | };
352 | };
353 | }
354 |
355 | // @ts-ignore
356 | private _patchCallRouteLoaderPre_1_7_2(): (original: typeof remixRunServerRuntimeData.callRouteLoader) => any {
357 | const plugin = this;
358 | return function callRouteLoader(original) {
359 | return function patchCallRouteLoader(this: any): Promise {
360 | // Cast as `any` to avoid typescript errors since this is patching an older version
361 | const [params] = arguments as unknown as any;
362 |
363 | const span = plugin.tracer.startSpan(
364 | `LOADER ${params.match.route.id}`,
365 | { attributes: { [SemanticAttributes.CODE_FUNCTION]: "loader" } },
366 | opentelemetry.context.active()
367 | );
368 |
369 | addRequestAttributesToSpan(span, params.request);
370 | addMatchAttributesToSpan(span, { routeId: params.match.route.id, params: params.match.params });
371 |
372 | return opentelemetry.context.with(opentelemetry.trace.setSpan(opentelemetry.context.active(), span), () => {
373 | const originalResponsePromise: Promise = original.apply(this, arguments as any);
374 | return originalResponsePromise
375 | .then((response) => {
376 | addResponseAttributesToSpan(span, response);
377 | return response;
378 | })
379 | .catch((error) => {
380 | plugin.addErrorToSpan(span, error);
381 | throw error;
382 | })
383 | .finally(() => {
384 | span.end();
385 | });
386 | });
387 | };
388 | };
389 | }
390 |
391 | // @ts-ignore
392 | private _patchCallRouteAction(): (original: typeof remixRunServerRuntimeData.callRouteAction) => any {
393 | const plugin = this;
394 | return function callRouteAction(original) {
395 | return async function patchCallRouteAction(this: any): Promise {
396 | const [params] = arguments as unknown as any;
397 | const clonedRequest = params.request.clone();
398 | const span = plugin.tracer.startSpan(
399 | `ACTION ${params.routeId}`,
400 | { attributes: { [SemanticAttributes.CODE_FUNCTION]: "action" } },
401 | opentelemetry.context.active()
402 | );
403 |
404 | addRequestAttributesToSpan(span, clonedRequest);
405 | addMatchAttributesToSpan(span, { routeId: params.routeId, params: params.params });
406 |
407 | return opentelemetry.context.with(
408 | opentelemetry.trace.setSpan(opentelemetry.context.active(), span),
409 | async () => {
410 | const originalResponsePromise: Promise = original.apply(this, arguments as any);
411 |
412 | return originalResponsePromise
413 | .then(async (response) => {
414 | addResponseAttributesToSpan(span, response);
415 |
416 | try {
417 | const formData = await clonedRequest.formData();
418 | const { actionFormDataAttributes: actionFormAttributes } = plugin.getConfig();
419 | formData.forEach((value, key) => {
420 | if (actionFormAttributes[key] && actionFormAttributes[key] !== false) {
421 | const keyName = actionFormAttributes[key] === true ? key : actionFormAttributes[key];
422 | span.setAttribute(`formData.${keyName}`, value.toString());
423 | }
424 | });
425 | } catch {
426 | // Silently continue on any error. Typically happens because the action body cannot be processed
427 | // into FormData, in which case we should just continue.
428 | }
429 |
430 | return response;
431 | })
432 | .catch(async (error) => {
433 | plugin.addErrorToSpan(span, error);
434 | throw error;
435 | })
436 | .finally(() => {
437 | span.end();
438 | });
439 | }
440 | );
441 | };
442 | };
443 | }
444 |
445 | // @ts-ignore
446 | private _patchCallRouteActionPre_1_7_2(): (original: typeof remixRunServerRuntimeData.callRouteAction) => any {
447 | const plugin = this;
448 | return function callRouteAction(original) {
449 | return async function patchCallRouteAction(this: any): Promise {
450 | // Cast as `any` to avoid typescript errors since this is patching an older version
451 | const [params] = arguments as unknown as any;
452 | const clonedRequest = params.request.clone();
453 | const span = plugin.tracer.startSpan(
454 | `ACTION ${params.match.route.id}`,
455 | { attributes: { [SemanticAttributes.CODE_FUNCTION]: "action" } },
456 | opentelemetry.context.active()
457 | );
458 |
459 | addRequestAttributesToSpan(span, clonedRequest);
460 | addMatchAttributesToSpan(span, { routeId: params.match.route.id, params: params.match.params });
461 |
462 | return opentelemetry.context.with(
463 | opentelemetry.trace.setSpan(opentelemetry.context.active(), span),
464 | async () => {
465 | const originalResponsePromise: Promise = original.apply(this, arguments as any);
466 |
467 | return originalResponsePromise
468 | .then(async (response) => {
469 | addResponseAttributesToSpan(span, response);
470 |
471 | try {
472 | const formData = await clonedRequest.formData();
473 | const { actionFormDataAttributes: actionFormAttributes } = plugin.getConfig();
474 | formData.forEach((value, key) => {
475 | if (actionFormAttributes[key] && actionFormAttributes[key] !== false) {
476 | const keyName = actionFormAttributes[key] === true ? key : actionFormAttributes[key];
477 | span.setAttribute(`formData.${keyName}`, value.toString());
478 | }
479 | });
480 | } catch {
481 | // Silently continue on any error. Typically happens because the action body cannot be processed
482 | // into FormData, in which case we should just continue.
483 | }
484 |
485 | return response;
486 | })
487 | .catch(async (error) => {
488 | plugin.addErrorToSpan(span, error);
489 | throw error;
490 | })
491 | .finally(() => {
492 | span.end();
493 | });
494 | }
495 | );
496 | };
497 | };
498 | }
499 |
500 | private addErrorToSpan(span: Span, error: Error) {
501 | addErrorEventToSpan(span, error);
502 |
503 | if (this.getConfig().legacyErrorAttributes || false) {
504 | addErrorAttributesToSpan(span, error);
505 | }
506 | }
507 | }
508 |
509 | const addRequestAttributesToSpan = (span: Span, request: Request) => {
510 | span.setAttributes({
511 | [SemanticAttributes.HTTP_METHOD]: request.method,
512 | [SemanticAttributes.HTTP_URL]: request.url,
513 | });
514 | };
515 |
516 | const addMatchAttributesToSpan = (span: Span, match: { routeId: string; params: Params }) => {
517 | span.setAttributes({
518 | [RemixSemanticAttributes.MATCH_ROUTE_ID]: match.routeId,
519 | });
520 |
521 | Object.keys(match.params).forEach((paramName) => {
522 | span.setAttribute(`${RemixSemanticAttributes.MATCH_PARAMS}.${paramName}`, match.params[paramName] || "(undefined)");
523 | });
524 | };
525 |
526 | const addResponseAttributesToSpan = (span: Span, response: Response | null) => {
527 | if (response) {
528 | span.setAttributes({
529 | [SemanticAttributes.HTTP_STATUS_CODE]: response.status,
530 | });
531 | }
532 | };
533 |
534 | const addErrorEventToSpan = (span: Span, error: Error) => {
535 | span.recordException(error);
536 | span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
537 | };
538 |
539 | const addErrorAttributesToSpan = (span: Span, error: Error) => {
540 | span.setAttribute("error", true);
541 | if (error.message) {
542 | span.setAttribute(SemanticAttributes.EXCEPTION_MESSAGE, error.message);
543 | }
544 | if (error.stack) {
545 | span.setAttribute(SemanticAttributes.EXCEPTION_STACKTRACE, error.stack);
546 | }
547 | };
548 |
--------------------------------------------------------------------------------
/packages/instrumentation-remix/src/version.ts:
--------------------------------------------------------------------------------
1 | // this is autogenerated file, see scripts/version-update.js
2 | export const VERSION = "0.8.0";
3 |
--------------------------------------------------------------------------------
/packages/instrumentation-remix/test/instrumentation-remix.spec.ts:
--------------------------------------------------------------------------------
1 | import "mocha";
2 | import * as React from "react";
3 | import expect from "expect";
4 | import { RemixInstrumentation } from "../src";
5 | import { SemanticAttributes } from "@opentelemetry/semantic-conventions";
6 | import { getTestSpans } from "opentelemetry-instrumentation-testing-utils";
7 | import type { ReadableSpan } from "@opentelemetry/sdk-trace-base";
8 |
9 | import * as semver from "semver";
10 |
11 | const instrumentationConfig = {
12 | actionFormDataAttributes: {
13 | _action: "actionType",
14 | foo: false,
15 | num: true,
16 | },
17 | legacyErrorAttributes: false,
18 | };
19 |
20 | const instrumentation = new RemixInstrumentation(instrumentationConfig);
21 |
22 | import { installGlobals } from "@remix-run/node";
23 |
24 | import * as remixServerRuntime from "@remix-run/server-runtime";
25 | import type { ServerBuild, ServerEntryModule } from "@remix-run/server-runtime";
26 | import { SpanStatusCode } from "@opentelemetry/api";
27 | const remixServerRuntimePackage = require("@remix-run/server-runtime/package.json");
28 |
29 | /** REMIX SERVER BUILD */
30 |
31 | const routes: ServerBuild["routes"] = {
32 | "routes/parent": {
33 | id: "routes/parent",
34 | path: "/parent",
35 | module: {
36 | loader: () => "LOADER",
37 | action: () => "ACTION",
38 | default: () => React.createElement("div", {}, "routes/parent"),
39 | },
40 | },
41 | "routes/parent/child/$id": {
42 | id: "routes/parent/child/$id",
43 | parentId: "routes/parent",
44 | path: "/parent/child/:id",
45 | module: {
46 | loader: () => "LOADER",
47 | action: () => "ACTION",
48 | default: () => React.createElement("div", {}, "routes/parent/child/$id"),
49 | },
50 | },
51 | "routes/throws-error": {
52 | id: "routes/throws-error",
53 | path: "/throws-error",
54 | module: {
55 | loader: async () => {
56 | throw new Error("oh no loader");
57 | },
58 | action: async () => {
59 | throw new Error("oh no action");
60 | },
61 | default: undefined,
62 | },
63 | },
64 | "routes/has-no-loader-or-action": {
65 | id: "routes/has-no-loader-or-action",
66 | path: "/has-no-loader-or-action",
67 | module: {
68 | default: () => React.createElement("div", {}, "routes/has-no-loader-or-action"),
69 | },
70 | },
71 | // Implicitly used to handle 404s
72 | root: {
73 | id: "root",
74 | module: {
75 | default: () => React.createElement("div", {}, "root"),
76 | },
77 | },
78 | };
79 |
80 | const entryModule: ServerEntryModule = {
81 | default: (request, responseStatusCode, responseHeaders, context) => {
82 | if (new URL(request.url).search.includes("throwEntryModuleError")) {
83 | throw new Error("oh no entry module");
84 | }
85 | return new Response(undefined, { status: responseStatusCode, headers: responseHeaders });
86 | },
87 | };
88 |
89 | let build: ServerBuild = {
90 | routes,
91 | assets: {
92 | routes,
93 | },
94 | entry: {
95 | module: entryModule,
96 | },
97 | future: {},
98 | } as unknown as ServerBuild;
99 |
100 | /**
101 | * The `remixServerRuntime.createRequestHandler` function definition can change across versions. This
102 | * function will provide the proper signature based on version to creat the request handler.
103 | *
104 | * This versions used here should mirror the versions defined in `.tav.yml`.
105 | */
106 | function createRequestHandlerForPackageVersion(version: string): remixServerRuntime.RequestHandler {
107 | if (semver.satisfies(version, "1.1.0 - 1.3.2")) {
108 | // Version <=1.3.2 uses a configuration object
109 | return (remixServerRuntime.createRequestHandler as any)(build, {}) as remixServerRuntime.RequestHandler;
110 | } else if (semver.satisfies(version, ">=1.3.3")) {
111 | // Version >=1.3.3 uses a "mode" param of type "production" | "deployment" | "test"
112 | return (remixServerRuntime.createRequestHandler as any)(build, "test") as remixServerRuntime.RequestHandler;
113 | } else {
114 | throw new Error("Unsupported @remix-run/server-runtime version");
115 | }
116 | }
117 |
118 | // Expects no error to appear as span attributes.
119 | const expectNoAttributeError = (span: ReadableSpan) => {
120 | expect(span.attributes["error"]).toBeUndefined();
121 | expect(span.attributes[SemanticAttributes.EXCEPTION_MESSAGE]).toBeUndefined();
122 | expect(span.attributes[SemanticAttributes.EXCEPTION_STACKTRACE]).toBeUndefined();
123 | };
124 |
125 | // Expects no error to appear as a span exception event.
126 | const expectNoEventError = (span: ReadableSpan) => {
127 | expect(span.status.code).not.toBe(SpanStatusCode.ERROR);
128 | expect(span.events.length).toBe(0);
129 | };
130 |
131 | // Expects no error to appear, either as span attributes or as a span exception event.
132 | const expectNoError = (span: ReadableSpan) => {
133 | expectNoAttributeError(span);
134 | expectNoEventError(span);
135 | };
136 |
137 | // Expects an error to appear, both as span attributes and as a span exception event.
138 | const expectError = (span: ReadableSpan, message: string) => {
139 | expectEventError(span, message);
140 | expectAttributeError(span, message);
141 | };
142 |
143 | // Expects an error to appear as span attributes.
144 | const expectAttributeError = (span: ReadableSpan, message: string) => {
145 | expect(span.attributes["error"]).toBe(true);
146 | expect(span.attributes[SemanticAttributes.EXCEPTION_MESSAGE]).toBe(message);
147 | expect(span.attributes[SemanticAttributes.EXCEPTION_STACKTRACE]).toBeDefined();
148 | };
149 |
150 | // Expects an error to appear as a span exception event.
151 | const expectEventError = (span: ReadableSpan, message: string) => {
152 | expect(span.status.code).toBe(SpanStatusCode.ERROR);
153 | expect(span.events.length).toBe(1);
154 | expect(span.events[0].attributes[SemanticAttributes.EXCEPTION_MESSAGE]).toBe(message);
155 | expect(span.events[0].attributes[SemanticAttributes.EXCEPTION_STACKTRACE]).toBeDefined();
156 | };
157 |
158 | const expectLoaderSpan = (span: ReadableSpan, route: string, params: { [key: string]: any } = {}) => {
159 | expect(span.name).toBe(`LOADER ${route}`);
160 | expect(span.attributes["match.route.id"]).toBe(route);
161 |
162 | Object.entries(params).forEach(([key, value]) => {
163 | expect(span.attributes[`match.params.${key}`]).toBe(value);
164 | });
165 | };
166 |
167 | const expectActionSpan = (span: ReadableSpan, route: string, formData: { [key: string]: any } = {}) => {
168 | expect(span.name).toBe(`ACTION ${route}`);
169 | expect(span.attributes["match.route.id"]).toBe(route);
170 |
171 | Object.entries(span.attributes)
172 | .filter(([key]) => key.startsWith("formData."))
173 | .forEach(([key, value]) => {
174 | expect(formData[key.replace("formData.", "")]).toBe(value);
175 | });
176 | Object.entries(formData).forEach(([key, value]) => {
177 | expect(span.attributes[`formData.${key}`]).toBe(value);
178 | });
179 | };
180 |
181 | const expectRequestHandlerSpan = (span: ReadableSpan, { path, id }: { path: string; id: string }) => {
182 | expect(span.name).toBe(`remix.request ${path}`);
183 | expect(span.attributes[SemanticAttributes.HTTP_ROUTE]).toBe(path);
184 | expect(span.attributes["match.route.id"]).toBe(id);
185 | };
186 |
187 | const expectRequestHandlerMatchNotFoundSpan = (span: ReadableSpan) => {
188 | expect(span.name).toBe("remix.request");
189 | expect(span.attributes[SemanticAttributes.HTTP_ROUTE]).toBeUndefined();
190 | expect(span.attributes["match.route.id"]).toBeUndefined();
191 | };
192 |
193 | const expectParentSpan = (parent: ReadableSpan, child: ReadableSpan) => {
194 | expect(parent.spanContext().traceId).toBe(child.spanContext().traceId);
195 | expect(parent.spanContext().spanId).toBe(child.parentSpanId);
196 | };
197 |
198 | const expectResponseAttributes = (span: ReadableSpan, { status }: { status: number }) => {
199 | expect(span.attributes[SemanticAttributes.HTTP_STATUS_CODE]).toBe(status);
200 | };
201 |
202 | const expectNoResponseAttributes = (span: ReadableSpan) => {
203 | expect(span.attributes[SemanticAttributes.HTTP_STATUS_CODE]).toBeUndefined();
204 | };
205 |
206 | type RequestAttributes = { method: string; url: string };
207 |
208 | const expectRequestAttributes = (span: ReadableSpan, { method, url }: RequestAttributes) => {
209 | expect(span.attributes[SemanticAttributes.HTTP_METHOD]).toBe(method);
210 | expect(span.attributes[SemanticAttributes.HTTP_URL]).toBe(url);
211 | };
212 |
213 | const loaderRevalidation = (attributes: RequestAttributes): RequestAttributes => {
214 | if (semver.satisfies(remixServerRuntimePackage.version, "<1.8.2")) {
215 | return attributes;
216 | }
217 |
218 | // Remix v1.8.2+ uses @remix-run/router v1.0.5, which uses a `GET` for loader revalidation instead of `POST`.
219 | // See: https://github.com/remix-run/react-router/blob/main/packages/router/CHANGELOG.md#105
220 | return {
221 | ...attributes,
222 | method: "GET",
223 | };
224 | };
225 |
226 | /** TESTS */
227 |
228 | describe("instrumentation-remix", () => {
229 | let requestHandler: remixServerRuntime.RequestHandler;
230 | let consoleErrorImpl;
231 | before(() => {
232 | installGlobals();
233 | instrumentation.enable();
234 | requestHandler = createRequestHandlerForPackageVersion(remixServerRuntimePackage.version);
235 | consoleErrorImpl = console.error;
236 | console.error = () => {};
237 | });
238 |
239 | after(() => {
240 | instrumentation.disable();
241 | console.error = consoleErrorImpl;
242 | });
243 |
244 | describe("requestHandler", () => {
245 | it("has a route match when there is no loader or action", async () => {
246 | const request = new Request("http://localhost/has-no-loader-or-action", { method: "GET" });
247 | await requestHandler(request, {});
248 |
249 | const spans = getTestSpans();
250 | expect(spans.length).toBe(1);
251 |
252 | const [requestHandlerSpan] = spans;
253 |
254 | const expectedRequestAttributes = {
255 | method: "GET",
256 | url: "http://localhost/has-no-loader-or-action",
257 | };
258 |
259 | // Request handler span
260 | expectRequestHandlerSpan(requestHandlerSpan, {
261 | path: "/has-no-loader-or-action",
262 | id: "routes/has-no-loader-or-action",
263 | });
264 | expectRequestAttributes(requestHandlerSpan, expectedRequestAttributes);
265 | expectResponseAttributes(requestHandlerSpan, { status: 200 });
266 | expectNoError(requestHandlerSpan);
267 | });
268 |
269 | it("does not have a route match when there is no route", async () => {
270 | const request = new Request("http://localhost/does-not-exist", { method: "GET" });
271 |
272 | await requestHandler(request, {});
273 |
274 | const spans = getTestSpans();
275 | expect(spans.length).toBe(1);
276 |
277 | const [requestHandlerSpan] = spans;
278 |
279 | const expectedRequestAttributes = {
280 | method: "GET",
281 | url: "http://localhost/does-not-exist",
282 | };
283 |
284 | // Request handler span
285 | expectRequestHandlerMatchNotFoundSpan(requestHandlerSpan);
286 | expectRequestAttributes(requestHandlerSpan, expectedRequestAttributes);
287 | expectResponseAttributes(requestHandlerSpan, { status: 404 });
288 | expectNoError(requestHandlerSpan);
289 | });
290 |
291 | it("handles thrown error from entry module", async () => {
292 | const request = new Request("http://localhost/parent?throwEntryModuleError", { method: "GET" });
293 | await requestHandler(request, {});
294 |
295 | const spans = getTestSpans();
296 | expect(spans.length).toBe(2);
297 |
298 | const [loaderSpan, requestHandlerSpan] = spans;
299 |
300 | expectParentSpan(requestHandlerSpan, loaderSpan);
301 |
302 | const expectedRequestAttributes = {
303 | method: "GET",
304 | url: "http://localhost/parent?throwEntryModuleError",
305 | };
306 |
307 | // Loader span
308 | expectLoaderSpan(loaderSpan, "routes/parent");
309 | expectRequestAttributes(loaderSpan, expectedRequestAttributes);
310 | expectResponseAttributes(loaderSpan, { status: 200 });
311 | expectNoError(loaderSpan);
312 |
313 | // Request handler span
314 | expectRequestHandlerSpan(requestHandlerSpan, {
315 | path: "/parent",
316 | id: "routes/parent",
317 | });
318 | expectRequestAttributes(requestHandlerSpan, expectedRequestAttributes);
319 | expectResponseAttributes(requestHandlerSpan, { status: 500 });
320 | expectNoError(requestHandlerSpan);
321 | });
322 | });
323 |
324 | describe("loaders", () => {
325 | it("handles basic loader", async () => {
326 | const request = new Request("http://localhost/parent", { method: "GET" });
327 | await requestHandler(request, {});
328 |
329 | const spans = getTestSpans();
330 | expect(spans.length).toBe(2);
331 |
332 | const [loaderSpan, requestHandlerSpan] = spans;
333 |
334 | expectParentSpan(requestHandlerSpan, loaderSpan);
335 |
336 | const expectedRequestAttributes = {
337 | method: "GET",
338 | url: "http://localhost/parent",
339 | };
340 |
341 | // Loader span
342 | expectLoaderSpan(loaderSpan, "routes/parent");
343 | expectRequestAttributes(loaderSpan, expectedRequestAttributes);
344 | expectResponseAttributes(loaderSpan, { status: 200 });
345 | expectNoError(loaderSpan);
346 |
347 | // Request handler span
348 | expectRequestHandlerSpan(requestHandlerSpan, {
349 | path: "/parent",
350 | id: "routes/parent",
351 | });
352 | expectRequestAttributes(requestHandlerSpan, expectedRequestAttributes);
353 | expectResponseAttributes(requestHandlerSpan, { status: 200 });
354 | expectNoError(requestHandlerSpan);
355 | });
356 |
357 | it("handles parent-child loaders", async () => {
358 | const request = new Request("http://localhost/parent/child/123", { method: "GET" });
359 | await requestHandler(request, {});
360 |
361 | const spans = getTestSpans();
362 | expect(spans.length).toBe(3);
363 |
364 | const [parentLoaderSpan, childLoaderSpan, requestHandlerSpan] = spans;
365 |
366 | expectParentSpan(requestHandlerSpan, parentLoaderSpan);
367 | expectParentSpan(requestHandlerSpan, childLoaderSpan);
368 |
369 | const expectedRequestAttributes = {
370 | method: "GET",
371 | url: "http://localhost/parent/child/123",
372 | };
373 |
374 | // Parent span
375 | expectLoaderSpan(parentLoaderSpan, "routes/parent", { id: "123" });
376 | expectRequestAttributes(parentLoaderSpan, expectedRequestAttributes);
377 | expectResponseAttributes(parentLoaderSpan, { status: 200 });
378 | expectNoError(parentLoaderSpan);
379 |
380 | // Child span
381 | expectLoaderSpan(childLoaderSpan, "routes/parent/child/$id", { id: "123" });
382 | expectRequestAttributes(childLoaderSpan, expectedRequestAttributes);
383 | expectResponseAttributes(childLoaderSpan, { status: 200 });
384 | expectNoError(childLoaderSpan);
385 |
386 | // Request handler span
387 | expectRequestHandlerSpan(requestHandlerSpan, {
388 | path: "/parent/child/:id",
389 | id: "routes/parent/child/$id",
390 | });
391 | expectRequestAttributes(requestHandlerSpan, expectedRequestAttributes);
392 | expectResponseAttributes(requestHandlerSpan, { status: 200 });
393 | expectNoError(requestHandlerSpan);
394 | });
395 |
396 | it("handles a thrown error from loader", async () => {
397 | const request = new Request("http://localhost/throws-error", { method: "GET" });
398 | await requestHandler(request, {});
399 |
400 | const spans = getTestSpans();
401 | expect(spans.length).toBe(2);
402 |
403 | const [loaderSpan, requestHandlerSpan] = spans;
404 |
405 | expectParentSpan(requestHandlerSpan, loaderSpan);
406 |
407 | const expectedRequestAttributes = {
408 | method: "GET",
409 | url: "http://localhost/throws-error",
410 | };
411 |
412 | // Loader span
413 | expectLoaderSpan(loaderSpan, "routes/throws-error");
414 | expectRequestAttributes(loaderSpan, expectedRequestAttributes);
415 | expectNoResponseAttributes(loaderSpan);
416 | expectEventError(loaderSpan, "oh no loader");
417 | expectNoAttributeError(loaderSpan);
418 |
419 | // Request handler span
420 | expectRequestHandlerSpan(requestHandlerSpan, {
421 | path: "/throws-error",
422 | id: "routes/throws-error",
423 | });
424 | expectRequestAttributes(requestHandlerSpan, expectedRequestAttributes);
425 | expectResponseAttributes(requestHandlerSpan, { status: 500 });
426 | expectNoError(requestHandlerSpan);
427 | });
428 |
429 | describe("with legacyErrorAttributes", () => {
430 | before(() => {
431 | instrumentation.setConfig({
432 | ...instrumentationConfig,
433 | legacyErrorAttributes: true,
434 | });
435 | });
436 |
437 | after(() => {
438 | instrumentation.setConfig(instrumentationConfig);
439 | });
440 |
441 | it("handles a thrown error from loader", async () => {
442 | const request = new Request("http://localhost/throws-error", { method: "GET" });
443 | await requestHandler(request, {});
444 |
445 | const spans = getTestSpans();
446 | expect(spans.length).toBe(2);
447 |
448 | const [loaderSpan, requestHandlerSpan] = spans;
449 |
450 | expectParentSpan(requestHandlerSpan, loaderSpan);
451 |
452 | const expectedRequestAttributes = {
453 | method: "GET",
454 | url: "http://localhost/throws-error",
455 | };
456 |
457 | // Loader span
458 | expectLoaderSpan(loaderSpan, "routes/throws-error");
459 | expectRequestAttributes(loaderSpan, expectedRequestAttributes);
460 | expectNoResponseAttributes(loaderSpan);
461 | expectError(loaderSpan, "oh no loader");
462 |
463 | // Request handler span
464 | expectRequestHandlerSpan(requestHandlerSpan, {
465 | path: "/throws-error",
466 | id: "routes/throws-error",
467 | });
468 | expectRequestAttributes(requestHandlerSpan, expectedRequestAttributes);
469 | expectResponseAttributes(requestHandlerSpan, { status: 500 });
470 | expectNoError(requestHandlerSpan);
471 | });
472 | });
473 | });
474 |
475 | describe("actions", () => {
476 | it("handles basic action", async () => {
477 | const request = new Request("http://localhost/parent", { method: "POST" });
478 | await requestHandler(request, {});
479 |
480 | const spans = getTestSpans();
481 | expect(spans.length).toBe(3);
482 |
483 | const [actionSpan, loaderSpan, requestHandlerSpan] = spans;
484 |
485 | expectParentSpan(requestHandlerSpan, loaderSpan);
486 | expectParentSpan(requestHandlerSpan, actionSpan);
487 |
488 | const expectedRequestAttributes = {
489 | method: "POST",
490 | url: "http://localhost/parent",
491 | };
492 |
493 | // Action span
494 | expectActionSpan(actionSpan, "routes/parent");
495 | expectRequestAttributes(actionSpan, expectedRequestAttributes);
496 | expectResponseAttributes(actionSpan, { status: 200 });
497 | expectNoError(actionSpan);
498 |
499 | // Loader span
500 | expectLoaderSpan(loaderSpan, "routes/parent");
501 | expectRequestAttributes(loaderSpan, loaderRevalidation(expectedRequestAttributes));
502 | expectResponseAttributes(loaderSpan, { status: 200 });
503 | expectNoError(loaderSpan);
504 |
505 | // Request handler span
506 | expectRequestHandlerSpan(requestHandlerSpan, {
507 | path: "/parent",
508 | id: "routes/parent",
509 | });
510 | expectRequestAttributes(requestHandlerSpan, expectedRequestAttributes);
511 | expectResponseAttributes(requestHandlerSpan, { status: 200 });
512 | expectNoError(requestHandlerSpan);
513 | });
514 |
515 | it("extracts action formData fields from form data", async () => {
516 | const body = new FormData();
517 | body.append("_action", "myAction");
518 | body.append("foo", "bar");
519 | body.append("num", "123");
520 | body.append("password", "test123");
521 | const request = new Request("http://localhost/parent", {
522 | method: "POST",
523 | body,
524 | });
525 |
526 | await requestHandler(request, {});
527 |
528 | const spans = getTestSpans();
529 | expect(spans.length).toBe(3);
530 |
531 | const [actionSpan, loaderSpan, requestHandlerSpan] = spans;
532 |
533 | expectParentSpan(requestHandlerSpan, loaderSpan);
534 | expectParentSpan(requestHandlerSpan, actionSpan);
535 |
536 | const expectedRequestAttributes = {
537 | method: "POST",
538 | url: "http://localhost/parent",
539 | };
540 |
541 | // Action span
542 | expectActionSpan(actionSpan, "routes/parent", {
543 | actionType: "myAction",
544 | foo: undefined,
545 | num: "123",
546 | });
547 | expectRequestAttributes(actionSpan, expectedRequestAttributes);
548 | expectResponseAttributes(actionSpan, { status: 200 });
549 | expectNoError(actionSpan);
550 |
551 | // Loader span
552 | expectLoaderSpan(loaderSpan, "routes/parent");
553 | expectRequestAttributes(loaderSpan, loaderRevalidation(expectedRequestAttributes));
554 | expectResponseAttributes(loaderSpan, { status: 200 });
555 | expectNoError(loaderSpan);
556 |
557 | // Request handler span
558 | expectRequestHandlerSpan(requestHandlerSpan, {
559 | path: "/parent",
560 | id: "routes/parent",
561 | });
562 | expectRequestAttributes(requestHandlerSpan, expectedRequestAttributes);
563 | expectResponseAttributes(requestHandlerSpan, { status: 200 });
564 | expectNoError(requestHandlerSpan);
565 | });
566 |
567 | it("handles a thrown error from action", async () => {
568 | const request = new Request("http://localhost/throws-error", { method: "POST" });
569 | await requestHandler(request, {});
570 | const spans = getTestSpans();
571 | expect(spans.length).toBe(2);
572 |
573 | const [actionSpan, requestHandlerSpan] = spans;
574 |
575 | expectParentSpan(requestHandlerSpan, actionSpan);
576 |
577 | const expectedRequestAttributes = {
578 | method: "POST",
579 | url: "http://localhost/throws-error",
580 | };
581 |
582 | // Action span
583 | expectActionSpan(actionSpan, "routes/throws-error");
584 | expectRequestAttributes(actionSpan, expectedRequestAttributes);
585 | expectNoResponseAttributes(actionSpan);
586 | expectEventError(actionSpan, "oh no action");
587 | expectNoAttributeError(actionSpan);
588 |
589 | // Request handler span
590 | expectRequestHandlerSpan(requestHandlerSpan, {
591 | path: "/throws-error",
592 | id: "routes/throws-error",
593 | });
594 | expectRequestAttributes(requestHandlerSpan, expectedRequestAttributes);
595 | expectResponseAttributes(requestHandlerSpan, { status: 500 });
596 | expectNoError(requestHandlerSpan);
597 | });
598 |
599 | describe("with legacyErrorAttributes", () => {
600 | before(() => {
601 | instrumentation.setConfig({
602 | ...instrumentationConfig,
603 | legacyErrorAttributes: true,
604 | });
605 | });
606 |
607 | after(() => {
608 | instrumentation.setConfig(instrumentationConfig);
609 | });
610 |
611 | it("handles a thrown error from action", async () => {
612 | const request = new Request("http://localhost/throws-error", { method: "POST" });
613 | await requestHandler(request, {});
614 | const spans = getTestSpans();
615 | expect(spans.length).toBe(2);
616 |
617 | const [actionSpan, requestHandlerSpan] = spans;
618 |
619 | expectParentSpan(requestHandlerSpan, actionSpan);
620 |
621 | const expectedRequestAttributes = {
622 | method: "POST",
623 | url: "http://localhost/throws-error",
624 | };
625 |
626 | // Action span
627 | expectActionSpan(actionSpan, "routes/throws-error");
628 | expectRequestAttributes(actionSpan, expectedRequestAttributes);
629 | expectNoResponseAttributes(actionSpan);
630 | expectError(actionSpan, "oh no action");
631 |
632 | // Request handler span
633 | expectRequestHandlerSpan(requestHandlerSpan, {
634 | path: "/throws-error",
635 | id: "routes/throws-error",
636 | });
637 | expectRequestAttributes(requestHandlerSpan, expectedRequestAttributes);
638 | expectResponseAttributes(requestHandlerSpan, { status: 500 });
639 | expectNoError(requestHandlerSpan);
640 | });
641 | });
642 | });
643 | });
644 |
--------------------------------------------------------------------------------
/packages/instrumentation-remix/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "outDir": "dist"
6 | },
7 | "include": ["src/**/*.ts", "test/**/*.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/scripts/version-update.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import os from "os";
3 | import path from "path";
4 |
5 | const appRoot = process.cwd();
6 | const packageJsonUrl = path.resolve(`${appRoot}/package.json`);
7 | const pjson = require(packageJsonUrl);
8 |
9 | const content = `// this is autogenerated file, see scripts/version-update.js
10 | export const VERSION = "${pjson.version}";
11 | `;
12 |
13 | const fileUrl = path.join(appRoot, "src", "version.ts");
14 | fs.writeFileSync(fileUrl, content.replace(/\n/g, os.EOL));
15 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["ES2019", "DOM"],
5 | "module": "commonjs",
6 | "moduleResolution": "node",
7 | "strict": false,
8 | "declaration": true,
9 | "declarationMap": true,
10 | "sourceMap": true,
11 | "composite": true,
12 | "inlineSources": true,
13 | "types": ["node"],
14 | "allowSyntheticDefaultImports": true,
15 | "esModuleInterop": true,
16 | "noImplicitOverride": true
17 | }
18 | }
19 |
--------------------------------------------------------------------------------