├── .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 | [![Daily Tests](https://github.com/justindsmith/opentelemetry-instrumentations-js/actions/workflows/daily-test.yml/badge.svg)](https://github.com/justindsmith/opentelemetry-instrumentations-js/actions/workflows/daily-test.yml) 5 | [![Apache 2.0 License](https://img.shields.io/badge/license-Apache_2.0-green.svg)](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/) | [![NPM version](https://img.shields.io/npm/v/opentelemetry-instrumentation-remix.svg)](https://www.npmjs.com/package/opentelemetry-instrumentation-remix) [![opentelemetry-instrumentation-remix downloads](https://img.shields.io/npm/dm/opentelemetry-instrumentation-remix.svg)]()| 21 | | [opentelemetry-instrumentation-prisma-client](./packages/instrumentation-prisma-client) | [`@prisma/client`](https://github.com/prisma/prisma/tree/main/packages/client) | [![NPM version](https://img.shields.io/npm/v/opentelemetry-instrumentation-prisma-client.svg)](https://www.npmjs.com/package/opentelemetry-instrumentation-prisma-client) [![opentelemetry-instrumentation-prisma-client downloads](https://img.shields.io/npm/dm/opentelemetry-instrumentation-prisma-client.svg)]()| 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 | [![NPM version](https://img.shields.io/npm/v/opentelemetry-instrumentation-prisma-client.svg)](https://www.npmjs.com/package/opentelemetry-instrumentation-prisma-client) 5 | [![Apache 2.0 License](https://img.shields.io/badge/license-Apache_2.0-green.svg)](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 | [![NPM version](https://img.shields.io/npm/v/opentelemetry-instrumentation-remix.svg)](https://www.npmjs.com/package/opentelemetry-instrumentation-remix) 3 | [![Apache 2.0 License](https://img.shields.io/badge/license-Apache_2.0-green.svg)](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 | --------------------------------------------------------------------------------