├── .eslintignore
├── .eslintrc.json
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ └── feature_request.yml
├── dependabot.yml
└── workflows
│ ├── README
│ └── ci.yml
├── .gitignore
├── .husky
└── commit-msg
├── .nvmrc
├── .prettierignore
├── .prettierrc.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── commitlint.config.js
├── docs
├── README.md
├── favicon.png
├── introduction.mdx
├── jest-otel
│ ├── architecture.mdx
│ ├── architecture.png
│ ├── contributing
│ │ ├── local-env.mdx
│ │ ├── overview.mdx
│ │ └── test-servers.mdx
│ ├── getting-started.mdx
│ ├── introduction.mdx
│ ├── no-otel.mdx
│ └── syntax
│ │ ├── db-pg.mdx
│ │ ├── db-redis.mdx
│ │ ├── overview.mdx
│ │ ├── services-grpc.mdx
│ │ └── services-rest.mdx
├── logo
│ ├── dark.svg
│ └── light.svg
└── mint.json
├── img
└── logo.png
├── jest-opentelemetry.config.js
├── jest.config.js
├── lerna.json
├── package-lock.json
├── package.json
├── packages
├── expect-opentelemetry
│ ├── .npmignore
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ ├── rollup.config.mjs
│ └── src
│ │ ├── index.ts
│ │ ├── matchers
│ │ ├── index.ts
│ │ ├── service
│ │ │ ├── grpc-request.test.ts
│ │ │ ├── http-request.test.ts
│ │ │ ├── index.ts
│ │ │ ├── to-query-postgresql.test.ts
│ │ │ ├── to-query-postgresql.ts
│ │ │ ├── to-receive-grpc-request.ts
│ │ │ ├── to-receive-http-request.ts
│ │ │ ├── to-send-grpc-request.ts
│ │ │ ├── to-send-http-request.ts
│ │ │ └── to-send-redis-command.ts
│ │ └── utils
│ │ │ ├── comparators.ts
│ │ │ ├── compare-types.ts
│ │ │ ├── filters.ts
│ │ │ └── index.ts
│ │ ├── options.ts
│ │ ├── resources
│ │ ├── grpc-request.test.ts
│ │ ├── grpc-request.ts
│ │ ├── http-request.test.ts
│ │ ├── http-request.ts
│ │ ├── index.ts
│ │ ├── postgresql-query.ts
│ │ ├── redis-command.ts
│ │ └── service.ts
│ │ ├── trace-loop
│ │ ├── fetch-traces.ts
│ │ ├── filter-service-spans.ts
│ │ └── index.ts
│ │ └── utils.ts
├── instrument-opentelemetry
│ ├── .dockerignore
│ ├── CHANGELOG.md
│ ├── Dockerfile
│ ├── package.json
│ ├── rollup.config.mjs
│ └── src
│ │ ├── otel-custom
│ │ ├── constants.js
│ │ ├── express.js
│ │ ├── http.js
│ │ ├── mime-type.js
│ │ └── stream-chunks.js
│ │ └── tracing.ts
├── jest-environment-otel
│ ├── .npmignore
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── index.js
│ ├── jest.config.js
│ ├── package.json
│ ├── rollup.config.mjs
│ ├── setup.js
│ ├── src
│ │ ├── env.js
│ │ ├── global.ts
│ │ └── readConfig.ts
│ └── teardown.js
├── jest-opentelemetry
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── jest-preset.js
│ └── package.json
├── otel-proto
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── package.json
│ ├── rollup.config.mjs
│ └── src
│ │ ├── index.d.ts
│ │ ├── index.js
│ │ ├── opentelemetry
│ │ └── proto
│ │ │ ├── common
│ │ │ └── v1
│ │ │ │ └── common.proto
│ │ │ ├── resource
│ │ │ └── v1
│ │ │ │ └── resource.proto
│ │ │ └── trace
│ │ │ └── v1
│ │ │ └── trace.proto
│ │ └── traceloop
│ │ └── proto
│ │ └── v1
│ │ └── traceloop.proto
├── otel-receiver
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── package.json
│ ├── rollup.config.mjs
│ └── src
│ │ ├── index.ts
│ │ └── store.ts
└── test-servers
│ ├── .dockerignore
│ ├── CHANGELOG.md
│ ├── Dockerfile
│ ├── deployment.yaml
│ ├── package.json
│ ├── rollup.config.mjs
│ └── src
│ ├── helloworld.proto
│ └── index.ts
└── tsconfig.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | jest.config.js
2 | tsconfig.json
3 | dist/
4 | packages/otel-proto
5 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true,
4 | "es2021": true,
5 | "jest": true
6 | },
7 | "extends": [
8 | "eslint:recommended",
9 | "plugin:@typescript-eslint/recommended",
10 | "prettier"
11 | ],
12 | "parser": "@typescript-eslint/parser",
13 | "parserOptions": {
14 | "ecmaVersion": "latest",
15 | "sourceType": "module"
16 | },
17 | "plugins": ["@typescript-eslint"]
18 | }
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: '🐛 Bug Report'
2 | description: 'Submit a bug report to help us improve'
3 | title: '🐛 Bug Report: '
4 | labels: ['type: bug']
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: We value your time and effort to submit this bug report. 🙏
9 | - type: textarea
10 | id: description
11 | validations:
12 | required: true
13 | attributes:
14 | label: '📜 Description'
15 | description: 'A clear and concise description of what the bug is.'
16 | placeholder: 'It bugs out when ...'
17 | - type: textarea
18 | id: steps-to-reproduce
19 | validations:
20 | required: true
21 | attributes:
22 | label: '👟 Reproduction steps'
23 | description: 'How do you trigger this bug? Please walk us through it step by step.'
24 | placeholder: "1. Go to '...'
25 | 2. Click on '....'
26 | 3. Scroll down to '....'
27 | 4. See error"
28 | - type: textarea
29 | id: expected-behavior
30 | validations:
31 | required: true
32 | attributes:
33 | label: '👍 Expected behavior'
34 | description: 'What did you think should happen?'
35 | placeholder: 'It should ...'
36 | - type: textarea
37 | id: actual-behavior
38 | validations:
39 | required: true
40 | attributes:
41 | label: '👎 Actual Behavior with Screenshots'
42 | description: 'What did actually happen? Add screenshots, if applicable.'
43 | placeholder: 'It actually ...'
44 | - type: input
45 | id: node-version
46 | validations:
47 | required: false
48 | attributes:
49 | label: '🤖 Node Version'
50 | description: >
51 | What Node version are you using?
52 | - type: textarea
53 | id: additional-context
54 | validations:
55 | required: false
56 | attributes:
57 | label: '📃 Provide any additional context for the Bug.'
58 | description: 'Add any other context about the problem here.'
59 | placeholder: 'It actually ...'
60 | - type: checkboxes
61 | id: no-duplicate-issues
62 | attributes:
63 | label: '👀 Have you spent some time to check if this bug has been raised before?'
64 | options:
65 | - label: "I checked and didn't find similar issue"
66 | required: true
67 | - type: dropdown
68 | attributes:
69 | label: Are you willing to submit PR?
70 | description: This is absolutely not required, but we are happy to guide you in the contribution process.
71 | options:
72 | - 'Yes I am willing to submit a PR!'
73 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: 🚀 Feature
2 | description: 'Submit a proposal for a new feature'
3 | title: '🚀 Feature: '
4 | labels: [feature]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | We value your time and efforts to submit this Feature request form. 🙏
10 | - type: textarea
11 | id: feature-description
12 | validations:
13 | required: true
14 | attributes:
15 | label: '🔖 Feature description'
16 | description: 'A clear and concise description of what the feature is.'
17 | placeholder: 'You should add ...'
18 | - type: textarea
19 | id: pitch
20 | validations:
21 | required: true
22 | attributes:
23 | label: '🎤 Why is this feature needed ?'
24 | description: 'Please explain why this feature should be implemented and how it would be used. Add examples, if applicable.'
25 | placeholder: 'In my use-case, ...'
26 | - type: textarea
27 | id: solution
28 | validations:
29 | required: true
30 | attributes:
31 | label: '✌️ How do you aim to achieve this?'
32 | description: 'A clear and concise description of what you want to happen.'
33 | placeholder: 'I want this feature to, ...'
34 | - type: textarea
35 | id: alternative
36 | validations:
37 | required: false
38 | attributes:
39 | label: '🔄️ Additional Information'
40 | description: "A clear and concise description of any alternative solutions or additional solutions you've considered."
41 | placeholder: 'I tried, ...'
42 | - type: checkboxes
43 | id: no-duplicate-issues
44 | attributes:
45 | label: '👀 Have you spent some time to check if this feature request has been raised before?'
46 | options:
47 | - label: "I checked and didn't find similar issue"
48 | required: true
49 | - type: dropdown
50 | id: willing-to-submit-pr
51 | attributes:
52 | label: Are you willing to submit PR?
53 | description: This is absolutely not required, but we are happy to guide you in the contribution process.
54 | options:
55 | - 'Yes I am willing to submit a PR!'
56 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: 'npm'
4 | directory: '/'
5 | schedule:
6 | interval: 'weekly'
7 | versioning-strategy: increase
8 |
--------------------------------------------------------------------------------
/.github/workflows/README:
--------------------------------------------------------------------------------
1 | # Debugging GitHub workflows locally
2 |
3 | 1. Install [act](https://github.com/nektos/act) by running `brew install act`
4 | 2. Install [Docker Desktop](https://www.docker.com/products/docker-desktop/) (if not installed)
5 | 3. Create a [Github token](https://github.com/settings/tokens)
6 | 4. Run `act -l` to list all available events and actions
7 | 5. Run `act -s GITHUB_TOKEN= -j` on the root directory of the repo (see [here](https://github.com/nektos/act#example-commands) for more options) to run the job you want to test. If you need to supply other secrets you can add them with `-s SECRET=`.
8 |
9 | ## Tip
10 |
11 | If you get an error like:
12 |
13 | ```
14 | Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
15 | ```
16 |
17 | Run `export DOCKER_HOST=$(docker context inspect --format '{{.Endpoints.docker.Host}}')` and then re-run `act` as written above.
18 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: 'CI'
2 | on:
3 | pull_request:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | Test:
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | node-version: [16, 18]
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v3
17 | - uses: actions/setup-node@v3
18 | with:
19 | node-version: ${{ matrix.node-version }}
20 | - uses: harmon758/postgresql-action@v1
21 | with:
22 | postgresql version: '15'
23 | postgresql db: 'postgres'
24 | postgresql user: 'postgres'
25 | postgresql password: 'postgres'
26 | - run: npm ci
27 | - run: npm run build
28 | - run: npm run lint
29 | - run: npm run test-ci
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 | lib/
107 |
108 | .DS_Store
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx --no -- commitlint --edit
5 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 16.15
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | *.proto
2 | dist/
3 | packages/otel-proto
4 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "trailingComma": "all",
4 | "singleQuote": true
5 | }
6 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | - Demonstrating empathy and kindness toward other people
21 | - Being respectful of differing opinions, viewpoints, and experiences
22 | - Giving and gracefully accepting constructive feedback
23 | - Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | - Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | - The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | - Trolling, insulting or derogatory comments, and personal or political attacks
33 | - Public or private harassment
34 | - Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | - Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | support@traceloop.dev.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to jest-opentelemetry
2 |
3 | Thanks for taking the time to contribute! 😃 🚀
4 |
5 | Please refer to our [Contributing Guide](https://traceloop.com/docs/jest-otel/contributing/overview) for instructions on how to contribute.
6 |
--------------------------------------------------------------------------------
/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 |
2 |
3 |
4 |
5 |
6 | Jest OpenTelemetry
7 |
8 |
Integeration tests with 10 lines of code
9 |
10 |
19 |
20 |
38 |
39 | Jest OpenTelemetry allows you to write, build and run integration tests based on OpenTelemetry traces with [Jest-like](https://jestjs.io/) syntax. You can:
40 |
41 | - 🕵️ Test any side-effect in your system: an email was sent, a database was updated, a BI event was reported, etc.
42 | - 👩💻 Run your tests and connect to a local or remote test environment.
43 |
44 | All with just a few lines of code.
45 |
46 | It's built and maintained by Traceloop under the Apache 2.0 license.
47 |
48 | ## 🚀 Getting Started
49 |
50 | ```js
51 | npm i --save-dev @traceloop/jest-opentelemetry
52 | ```
53 |
54 | Then, you can start testing your microservices:
55 |
56 | ```js
57 | const traceloop = new TraceLoop();
58 |
59 | await traceloop.axiosInstance.post('http://my.awesome.website/orders/create');
60 | await traceloop.fetchTraces();
61 |
62 | expectTrace(traceloop.serviceByName('emails-service'))
63 | .toReceiveHttpRequest()
64 | .ofMethod('POST')
65 | .withBody({ emailTemplate: 'orderCreated', itemId: '123' });
66 | ```
67 |
68 | More info can be found in our [docs](https://traceloop.com/docs/jest-otel/getting-started).
69 |
70 | ## What can you test?
71 |
72 | Jest OpenTelemetry can be used to test anything that's happening in your system.
73 | We're constantly adding more, and you're welcome to [suggest yours](https://github.com/traceloop/jest-opentelemetry/issues).
74 |
75 | ### Service Assertions
76 |
77 | - [x] [REST](http://traceloop.com/docs/jest-otel/syntax/services-rest)
78 | - [ ] GraphQL
79 | - [x] [GRPC](http://traceloop.com/docs/jest-otel/syntax/services-grpc)
80 |
81 | ### Database Assertions
82 |
83 | - [x] [PostgreSQL](http://traceloop.com/docs/jest-otel/syntax/db-pg)
84 | - [ ] MongoDB
85 | - [x] [Redis](http://traceloop.com/docs/jest-otel/syntax/db-redis)
86 | - [ ] S3
87 |
88 | ### Analytics Reporting Assertions
89 |
90 | - [ ] Segment
91 | - [ ] Snowflake
92 | - [ ] BigQuery
93 | - [ ] Posthog
94 |
95 | ### External Systems Assertions
96 |
97 | - [ ] SendGrid
98 | - [ ] Stripe
99 |
100 | ## 🌱 Contributing
101 |
102 | Whether it's big or small, we love contributions ❤️ Check out our guide to see how to [get started](https://traceloop.com/docs/contributing/overview).
103 |
104 | Not sure where to get started? You can:
105 |
106 | - [Book a free pairing session with one of our teammates](mailto:nir@traceloop.com?subject=Pairing%20session&body=I'd%20like%20to%20do%20a%20pairing%20session!)!
107 | - Join our Slack, and ask us any questions there.
108 |
109 | ## 💚 Community & Support
110 |
111 | - [Slack](https://join.slack.com/t/traceloopcommunity/shared_invite/zt-1plpfpm6r-zOHKI028VkpcWdobX65C~g) (For live discussion with the community and the Traceloop team)
112 | - [GitHub Discussions](https://github.com/traceloop/jest-opentelemetry/discussions) (For help with building and deeper conversations about features)
113 | - [GitHub Issues](https://github.com/traceloop/jest-opentelemetry/issues) (For any bugs and errors you encounter using Jest OpenTelemetry)
114 | - [Twitter](https://twitter.com/traceloopdev) (Get news fast)
115 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security
2 |
3 | Contact: security@traceloop.com
4 |
5 | Based on [https://supabase.com/.well-known/security.txt](https://supabase.com/.well-known/security.txt)
6 |
7 | We place a high priority on the security of our systems at Traceloop. However, no matter how hard we try to make our systems secure, vulnerabilities can still exist.
8 |
9 | In the event that you discover a vulnerability, please let us know so we can address it as soon as possible. We would like to ask you to help us better protect our clients and our systems.
10 |
11 | ## Out of scope vulnerabilities:
12 |
13 | - Clickjacking on pages with no sensitive actions.
14 |
15 | - Unauthenticated/logout/login CSRF.
16 |
17 | - Attacks requiring MITM or physical access to a user's device.
18 |
19 | - Any activity that could lead to the disruption of our service (DoS).
20 |
21 | - Content spoofing and text injection issues without showing an attack vector/without being able to modify HTML/CSS.
22 |
23 | - Email spoofing
24 |
25 | - Missing DNSSEC, CAA, CSP headers
26 |
27 | - Lack of Secure or HTTP only flag on non-sensitive cookies
28 |
29 | - Deadlinks
30 |
31 | ## Please do the following:
32 |
33 | - E-mail your findings to [security@traceloop.dev](mailto:security@traceloop.dev).
34 |
35 | - Do not run automated scanners on our infrastructure or dashboard. If you wish to do this, contact us and we will set up a sandbox for you.
36 |
37 | - Do not take advantage of the vulnerability or problem you have discovered, for example by downloading more data than necessary to demonstrate the vulnerability or deleting or modifying other people's data,
38 |
39 | - Do not reveal the problem to others until it has been resolved,
40 |
41 | - Do not use attacks on physical security, social engineering, distributed denial of service, spam or applications of third parties,
42 |
43 | - Do provide sufficient information to reproduce the problem, so we will be able to resolve it as quickly as possible. Usually, the IP address or the URL of the affected system and a description of the vulnerability will be sufficient, but complex vulnerabilities may require further explanation.
44 |
45 | ## What we promise:
46 |
47 | - We will respond to your report within 3 business days with our evaluation of the report and an expected resolution date,
48 |
49 | - If you have followed the instructions above, we will not take any legal action against you in regard to the report,
50 |
51 | - We will handle your report with strict confidentiality, and not pass on your personal details to third parties without your permission,
52 |
53 | - We will keep you informed of the progress towards resolving the problem,
54 |
55 | - In the public information concerning the problem reported, we will give your name as the discoverer of the problem (unless you desire otherwise), and
56 |
57 | - We strive to resolve all problems as quickly as possible, and we would like to play an active role in the ultimate publication on the problem after it is resolved.
58 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = { extends: ['@commitlint/config-conventional'] };
2 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Mintlify Starter Kit
2 |
3 | Click on `Use this template` to quickstarter your documentation setup with Mintlify. The starter kit contains examples including
4 |
5 | - Guide pages
6 | - Navigation
7 | - Customizations
8 | - API Reference pages
9 | - Use of popular components
10 |
11 | ### 👩💻 Development
12 |
13 | Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the documentation changes locally. To install, use the following command
14 |
15 | ```
16 | npm i mintlify -g
17 | ```
18 |
19 | Run the following command at the root of your documentation (where mint.json is)
20 |
21 | ```
22 | mintlify dev
23 | ```
24 |
25 | ### 😎 Publishing Changes
26 |
27 | Changes will be deployed to production automatically after pushing to the default branch.
28 |
29 | You can also preview changes using PRs, which generates a preview link of the docs.
30 |
31 | #### Troubleshooting
32 |
33 | - Mintlify dev isn't running - Run `mintlify install` it'll re-install dependencies.
34 | - Mintlify dev is updating really slowly - Run `mintlify clear` to clear the cache.
35 |
--------------------------------------------------------------------------------
/docs/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/traceloop/jest-opentelemetry/fea70d0719085318e9a4468a1d7616e51148dfab/docs/favicon.png
--------------------------------------------------------------------------------
/docs/introduction.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Introduction'
3 | description: 'Auto-generate code that improves your system reliability'
4 | ---
5 |
6 | Traceloop automatically generates integration tests, alerts, and canary monitoring code based on your OpenTelemetry tracing data.
7 | It uses those traces to learn how your system behaves in normal conditions and automatically generates assertions you
8 | can use to validate that your system working properly.
9 |
10 | These assertions can be used to generate integration tests, alerts, and canary monitoring code.
11 |
12 | Don't worry if you're not familiar with OpenTelemetry. Even if you don't have it currently in your system, you can easily set it up.
13 |
14 | And we can help you too! Ping us at dev@traceloop.dev
15 |
16 | ### Languages
17 |
18 |
19 |
24 | Available
25 |
26 |
27 | In Development
28 |
29 |
30 | In Development
31 |
32 |
33 |
--------------------------------------------------------------------------------
/docs/jest-otel/architecture.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Architecture'
3 | description: 'Understand how Jest OpenTelemetry works'
4 | ---
5 |
6 | When you run a test, you will usually send a request to one of the services in your cluster and then monitor how they behave.
7 | To do that, we use the data outputted by OpenTelemetry.
8 |
9 | This data is collected by an OpenTelemetry instance called [Collector](https://opentelemetry.io/docs/collector/).
10 | We connect to it by adding a new standard HTTP exporter (called `otlphttp`) within the collector.
11 |
12 |
13 | Collectors are built of 3 components:
14 |
15 | * Receivers: Receives the data from the instrumented application
16 | * Processors: Processes the data (e.g. filters, enriches, etc.)
17 | * Exporters: Exports the data to a backend (e.g. Jaeger, Zipkin, etc.)
18 |
19 | A standard container deployment of a collector exists where all of this is [configured in a YAML file](https://opentelemetry.io/docs/collector/configuration).
20 | Basically adding the HTTP exporter for test is merely a configuration update.
21 |
22 |
23 |
24 | When Jest starts up, we starts another service called `otel-receiver` that receives the telemetries outputted from the `otlphttp` exporter.
25 | It caches them all locally in memory. Then, when the actual test is ran, each of your assertions are tested against these cached traces.
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/docs/jest-otel/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/traceloop/jest-opentelemetry/fea70d0719085318e9a4468a1d7616e51148dfab/docs/jest-otel/architecture.png
--------------------------------------------------------------------------------
/docs/jest-otel/contributing/local-env.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Setting up a local developement environment'
3 | ---
4 |
5 | Jest OpenTelemetry is using a monorepo (powered by [Lerna](https://lerna.js.org/)).
6 |
7 | Follow these instructions to set up a local developement environment.
8 |
9 | 1. Fork out repository and clone it locally.
10 |
11 | ```bash
12 | git clone
13 | cd jest-opentelemetry
14 | nvm install
15 | npm install
16 | ```
17 |
18 | 2. Build the packages:
19 |
20 | ```bash
21 | npm run build
22 | ```
23 |
24 | 3. Do any changes you want to do. Make sure to add proper tests, especially if you add new assertions.
25 |
26 | 4. Re-build the project (this is needed because the assertion package is using itself in the tests):
27 |
28 | ```bash
29 | npm run build
30 | ```
31 |
32 | 5. Install a local Postgres database. On mac, you can use [Postgres.app](https://postgresapp.com/).
33 |
34 | 6. Run the [test servers](test-servers). You can either run them locally in a separate terminal or on a local Kubernetes cluster.
35 | These are the servers that we use to test the different assertions we have in Jest OpenTelemetry.
36 |
37 | 7. Run the tests to make sure everything still works:
38 |
39 | ```bash
40 | npm run test
41 | ```
42 |
43 | 8. Commit, push and open a pull request.
44 |
45 | If you have any questions, hit us up over at [Slack](https://join.slack.com/t/traceloopcommunity/shared_invite/zt-1plpfpm6r-zOHKI028VkpcWdobX65C~g)!
46 |
--------------------------------------------------------------------------------
/docs/jest-otel/contributing/overview.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Overview'
3 | description: 'We welcome any contributions to Jest OpenTelemetry, big or small.'
4 | ---
5 |
6 | ## Community
7 |
8 | It's the early days of our project and we're working hard to build an awesome, inclusive community. In order to grow this, all community members must adhere to our [Code of Conduct](https://github.com/traceloop/jest-opentelemetry/blob/main/CODE_OF_CONDUCT.md).
9 |
10 | ## Bugs and issues
11 |
12 | Bug reports help make Jest OpenTelemetry a better experience for everyone. When you report a bug, a template will be created automatically containing information we'd like to know.
13 |
14 | Before raising a new issue, please search existing ones to make sure you're not creating a duplicate.
15 |
16 |
17 | If the issue is related to security, please email us directly at
18 | dev@traceloop.dev.
19 |
20 |
21 | ## Deciding what to work on
22 |
23 | You can start by browsing through our list of issues or adding your own that improves on the test suite experience. Once you've decided on an issue, leave a comment and wait to get approved; this helps avoid multiple people working on the same issue.
24 |
25 | If you're ever in doubt about whether or not a proposed feature aligns with Enrolla as a whole, feel free to raise an issue about it and we'll get back to you promptly.
26 |
27 | ## Writing and submitting code
28 |
29 | Anyone can contribute code to Jest OpenTelemetry. To get started, check out the local development guide, make your changes, and submit a pull request to the main repository.
30 |
31 | ## Licensing
32 |
33 | All of Jest OpenTelemetry's code is under the Apache 2.0 license.
34 |
35 | Any third party components incorporated into our code are licensed under the original license provided by the applicable component owner.
36 |
--------------------------------------------------------------------------------
/docs/jest-otel/contributing/test-servers.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Running Test Servers'
3 | ---
4 |
5 | Currently, we have two test servers which are ran and tested together as part of Jest OpenTelemetry internal test suite:
6 |
7 | - **orders-service** - a simple service that creates orders and send emails to users when an order is created.
8 | - **emails-service** - a simple service that sends emails to users.
9 |
10 | ## Running locally
11 |
12 | Running our test servers locally is as simple as running the following command:
13 |
14 | ```bash
15 | npm run start:test-servers
16 | ```
17 |
18 | ## Running on a local Kubernetes cluster
19 |
20 | 1. Make sure you have a K8s cluster running locally.
21 | If you don't, you can use either [minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/)
22 | or [Docker Desktop](https://www.docker.com/products/docker-desktop) to run a local cluster.
23 |
24 | 1. Build the docker image.
25 |
26 | ```bash
27 | npm run docker:test-servers
28 | ```
29 |
30 | 1. Install cert-manager (needed by OpenTelemetry operator)
31 |
32 | ```bash
33 | kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml
34 | ```
35 |
36 | 1. Install the OpenTelemetry operator so that metrics will be generated and collected automatically.
37 |
38 | ```bash
39 | kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml
40 | ```
41 |
42 | 1. Apply the K8s spec.
43 |
44 | ```bash
45 | kubectl apply -f packages/test-servers/deployment.yaml
46 | ```
47 |
48 |
49 | If you ever re-built the docker image, make sure to delete the old
50 | deployment first. Run ```kubectl delete -f
51 | packages/test-servers/deployment.yaml ```
52 |
53 |
--------------------------------------------------------------------------------
/docs/jest-otel/getting-started.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Getting Started'
3 | ---
4 |
5 | Jest OpenTelemetry requires that you have OpenTelemetry installed and configured in your microservices.
6 | [Follow the instructions](no-otel) to get it done in 5 minutes without any code changes!
7 |
8 | Feel free to contact us at dev@traceloop.dev if you need any help.
9 |
10 | ## Installing Jest OpenTelemetry
11 |
12 | 1. In your repo, run:
13 |
14 | ```bash
15 | npm install --save-dev @traceloop/jest-opentelemetry jest
16 | ```
17 |
18 | 2. Update or create your jest configuration (`jest.config.js` or `jest.config.ts`):
19 |
20 | ```js
21 | module.exports = {
22 | preset: '@traceloop/jest-opentelemetry',
23 | };
24 | ```
25 |
26 | 3. Start writing tests following our [syntax](syntax/introduction).
27 |
28 | ## Typescript configuration
29 |
30 | Make sure you use `@jest/globals` for your types and not `@types/jest` which is out of date.
31 | See more info [here](https://jestjs.io/docs/getting-started#type-definitions).
32 |
--------------------------------------------------------------------------------
/docs/jest-otel/introduction.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Introduction'
3 | ---
4 |
5 | Jest OpenTelemetry is an engine for writing end to end tests for distributed systems with 10 lines of code.
6 |
7 | ```js
8 | const traceloop = new TraceLoop();
9 |
10 | await traceloop.axiosInstance.post('http://my.awesome.website/orders/create');
11 | await traceloop.fetchTraces();
12 |
13 | expectTrace(traceloop.serviceByName('emails-service'))
14 | .toReceiveHttpRequest()
15 | .withMethod('POST')
16 | .withBody({ emailTemplate: 'orderCreated', itemId: '123' });
17 | ```
18 |
19 | Everything is running locally on your system and can connect to services running locally on your machine,
20 | or any kubernetes cluster (locally or remotely).
21 |
22 |
23 | We're working on a cloud runner that will enable you to run tests on large
24 | clusters with significant load. [Sign up for our private beta
25 | here](https://c9nshtdss2i.typeform.com/to/mLMW70eV).
26 |
27 |
28 | ## Getting Started
29 |
30 | Select from the following guides to learn more about how to use Jest OpenTelemetry:
31 |
32 |
33 |
38 | Set up Jest OpenTelemetry in your project
39 |
40 |
41 | How to write tests with Jest OpenTelemetry
42 |
43 |
48 | Understand how Jest OpenTelemetry works
49 |
50 |
55 | See our GitHub repo
56 |
57 |
58 |
--------------------------------------------------------------------------------
/docs/jest-otel/no-otel.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "What if I don't have OpenTelemetry installed?"
3 | ---
4 |
5 | No worries! You can install it in any existing Kubernetes environment in under 5 minutes by running the following set of commands.
6 | **No code changes are needed**.
7 |
8 | Feel free to contact us at dev@traceloop.dev if you need any help.
9 |
10 |
11 | [Odigos](https://docs.odigos.io/intro) is a new open source project that can do this for you without a single line of code.
12 |
13 | You can also find more ways to instrument OpenTelemetry on their [documentation](https://opentelemetry.io/docs/js/getting-started/).
14 |
15 |
16 |
17 | 1. Install cert-manager. This is required for the OpenTelemetry operator to work.
18 |
19 | ```bash
20 | kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml
21 | ```
22 |
23 | 2. Install the [OpenTelemetry operator](https://opentelemetry.io/docs/k8s-operator/) so that metrics will be generated and collected automatically.
24 |
25 | ```bash
26 | kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml
27 | ```
28 |
29 | 3. Create a config file named `otel-collector.yaml` for your OpenTelemetry configurations:
30 |
31 | ```yaml
32 | apiVersion: opentelemetry.io/v1alpha1
33 | kind: Instrumentation
34 | metadata:
35 | name: otel-instrumentation
36 | spec:
37 | nodejs:
38 | image: traceloop/instrument-opentelemetry:node-0.3.0
39 | exporter:
40 | endpoint: http://otel-collector:4317
41 | propagators:
42 | - tracecontext
43 | - baggage
44 | - b3
45 |
46 | ---
47 | apiVersion: opentelemetry.io/v1alpha1
48 | kind: OpenTelemetryCollector
49 | metadata:
50 | name: otel
51 | spec:
52 | config: |
53 | receivers:
54 | otlp:
55 | protocols:
56 | grpc:
57 | http:
58 | processors:
59 | batch:
60 | timeout: 100ms
61 |
62 | exporters:
63 | otlphttp:
64 | endpoint: http://host.docker.internal:4123
65 |
66 | service:
67 | pipelines:
68 | traces:
69 | receivers: [otlp]
70 | processors: [batch]
71 | exporters: [otlphttp]
72 | ```
73 |
74 |
75 |
76 | We configure 2 separate things:
77 |
78 | 1. The Instrumentation, which is an init-container which will run on any pod you explictly mark (see step 5).
79 | We are using our own init-container to get more data about your application (like the body of HTTP requests).
80 | You can always remove these lines to use the standard open telemetry init-container:
81 |
82 | ```yaml
83 | nodejs:
84 | image: traceloop/instrument-opentelemetry:node-0.3.0
85 | ```
86 |
87 | 2. The OpenTelemetry collector, which will collect the metrics from the init-container and send them to the test runner.
88 | What's amazing here is that you can add other exporters to this config file to send the metrics to other services like Datadog and others.
89 |
90 |
91 |
92 | 4. Apply the config file:
93 |
94 | ```bash
95 | kubectl apply -f otel-collector.yaml
96 | ```
97 |
98 | 5. Update any service you want to instrument with the following annotations (Change the 2 occurances of `my-service` to the name of your service):
99 |
100 |
101 | We add an env var named `SERVICE_NAME` to your service so that you can
102 | later identify it in the tests.
103 |
104 |
105 | ```yaml
106 | apiVersion: apps/v1
107 | kind: Deployment
108 | metadata:
109 | name: my-service
110 | spec:
111 | replicas: 1
112 | template:
113 | annotations:
114 | instrumentation.opentelemetry.io/inject-nodejs: 'true'
115 | spec:
116 | containers:
117 | env:
118 | - name: SERVICE_NAME
119 | value: 'my-service'
120 | ```
121 |
122 | This will automatically instrument your service with OpenTelemetry and send the metrics to the OpenTelemetry collector.
123 | Apply those changes and you're done! You can start writing end to end tests.
124 |
--------------------------------------------------------------------------------
/docs/jest-otel/syntax/db-pg.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'PostgreSQL'
3 | ---
4 |
5 | Each assertion should start with defining the service that's sending requests to the Database you want to check. You do this by:
6 |
7 | ```js
8 | expectTrace(traceloop.serviceByName('service-name'))...
9 | ```
10 |
11 | These are the options you can use for service selection:
12 |
13 | - `serviceByName` - the name of the service as reported in the `service.name` attribute
14 | (automatically reported by [Traceloop's init container](/jest-otel/no-otel))
15 | - `serviceByK8sPodName` - the name of the pod as reported by Kubernetes
16 | - `serviceByCustomAttribute` - any custom attribute reported by your service to OpenTelemetry
17 |
18 | Following that, you can use any of the following assertions:
19 |
20 | - `toQueryPostgreSQL`
21 |
22 | Then, you can specifically assert for each of the properties of the request:
23 |
24 | - `withDatabaseName`
25 | - `withStatement`
26 |
27 | So, a complete assertion can look like:
28 |
29 | ```js
30 | expectTrace(traceloop.serviceByName('orders-service'))
31 | .toQueryPostgreSQL()
32 | .withDatabaseName('postgres')
33 | .withOperations('INSERT')
34 | .withTables('orders')
35 | .withStatement(
36 | /INSERT INTO orders \(id, price_in_cents\) VALUES \('[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}', [0-9]+\)/,
37 | { compareType: COMPARE_TYPE.REGEX },
38 | );
39 | ```
40 |
--------------------------------------------------------------------------------
/docs/jest-otel/syntax/db-redis.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Redis'
3 | ---
4 |
5 | Each assertion should start with defining the service that's sending requests to the Redis Database you want to check. You do this by:
6 |
7 | ```js
8 | expectTrace(traceloop.serviceByName('service-name'))...
9 | ```
10 |
11 | These are the options you can use for service selection:
12 |
13 | - `serviceByName` - the name of the service as reported in the `service.name` attribute
14 | (automatically reported by [Traceloop's init container](/jest-otel/no-otel))
15 | - `serviceByK8sPodName` - the name of the pod as reported by Kubernetes
16 | - `serviceByCustomAttribute` - any custom attribute reported by your service to OpenTelemetry
17 |
18 | Following that, you can use any of the following assertions:
19 |
20 | - `toSendRedisCommand`
21 |
22 | Then, you can specifically assert for each of the properties of the request:
23 |
24 | - `withDatabaseName`
25 | - `withStatement`
26 |
27 | So, a complete assertion can look like:
28 |
29 | ```js
30 | expectTrace(traceloop.serviceByName('redis-service'))
31 | .toSendRedisCommend({ times: 2 }) // optional times parameter (defaults to one)
32 | .withDatabaseName('redis-db')
33 | .withStatement(
34 | /^HGET/
35 | { compareType: COMPARE_TYPE.REGEX },
36 | );
37 | ```
38 |
--------------------------------------------------------------------------------
/docs/jest-otel/syntax/overview.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Overview'
3 | ---
4 |
5 | Jest OpenTelemetry syntax is simple and easy to use.
6 | Each test should begin by sending a request to any of your backends in your test environments, like this:
7 |
8 |
9 | Note the use of the `axiosInstance` returned from `traceloop` to send the
10 | request.
11 |
12 |
13 | ```js
14 | const traceloop = new TraceLoop();
15 |
16 | await traceloop.axiosInstance.post('http://my.awesome.website/orders/create');
17 | await traceloop.fetchTraces();
18 | ```
19 |
20 | Following that, you can run assertions on anything that happened in your system as part of this request.
21 |
22 | ## Service Assertions
23 |
24 |
25 |
26 | Available
27 |
28 |
33 | Available
34 |
35 |
36 | In Development
37 |
38 |
39 |
40 | ## Database Assertions
41 |
42 |
43 |
44 | Available
45 |
46 |
47 | Available
48 |
49 |
50 | In Development
51 |
52 |
53 | In Development
54 |
55 |
56 |
57 | ## Analytics Reporting Assertions
58 |
59 |
60 |
61 | In Development
62 |
63 |
64 | In Development
65 |
66 |
67 | In Development
68 |
69 |
70 | In Development
71 |
72 |
73 |
74 | ## External Systems Assertions
75 |
76 |
77 |
78 | In Development
79 |
80 |
81 | In Development
82 |
83 |
84 |
--------------------------------------------------------------------------------
/docs/jest-otel/syntax/services-grpc.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'GRPC Assertions'
3 | ---
4 |
5 | Each assertion should start with defining the service you want to check. You do this by:
6 |
7 | ```js
8 | expectTrace(traceloop.serviceByName('service-name'))...
9 | ```
10 |
11 | These are the options you can use for service selection:
12 |
13 | - `serviceByName` - the name of the service as reported in the `service.name` attribute
14 | (automatically reported by [Traceloop's init container](/jest-otel/no-otel))
15 | - `serviceByK8sPodName` - the name of the pod as reported by Kubernetes
16 | - `serviceByCustomAttribute` - any custom attribute reported by your service to OpenTelemetry
17 |
18 | Following that, you can use any of the following assertions:
19 |
20 | - `toSendGrpcRequest`
21 | - `toReceiveGrpcRequest`
22 |
23 | Then, you can specifically assert for each of the properties of the request:
24 |
25 | - `withRpcMethod`
26 | - `withRpcService`
27 | - `withRpcGrpcStatusCode`
28 | - `withNetPeerName`
29 | - `withHostName`
30 |
31 | So, a complete assertion can look like:
32 |
33 | ```js
34 | expectTrace(traceloop.serviceByName('users-service'))
35 | .toReceiveGrpcRequest()
36 | .withRpcMethod('CreateUser')
37 | .withRpcGrpcStatusCode(0);
38 | ```
39 |
--------------------------------------------------------------------------------
/docs/jest-otel/syntax/services-rest.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'REST Assertions'
3 | ---
4 |
5 | Each assertion should start with defining the service you want to check. You do this by:
6 |
7 | ```js
8 | expectTrace(traceloop.serviceByName('service-name'))...
9 | ```
10 |
11 | These are the options you can use for service selection:
12 |
13 | - `serviceByName` - the name of the service as reported in the `service.name` attribute
14 | (automatically reported by [Traceloop's init container](/jest-otel/no-otel))
15 | - `serviceByK8sPodName` - the name of the pod as reported by Kubernetes
16 | - `serviceByCustomAttribute` - any custom attribute reported by your service to OpenTelemetry
17 |
18 | Following that, you can use any of the following assertions:
19 |
20 | - `toSendHttpRequest`
21 | - `toReceiveHttpRequest`
22 |
23 | Then, you can specifically assert for each of the properties of the request:
24 |
25 | - `withBody`
26 | - `withHeader`
27 | - `withMethod`
28 | - `withStatusCode`
29 |
30 | So, a complete assertion can look like:
31 |
32 | ```js
33 | expectTrace(traceloop.serviceByName('users-service'))
34 | .toReceiveHttpRequest()
35 | .ofMethod('POST')
36 | .withHeader('Content-Type', 'application/json')
37 | .withRequestBody({
38 | id: 1,
39 | name: 'John Doe',
40 | address: '123 Main St',
41 | });
42 | ```
43 |
--------------------------------------------------------------------------------
/docs/mint.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "traceloop",
3 | "favicon": "/favicon.png",
4 | "logo": {
5 | "light": "/logo/light.svg",
6 | "dark": "/logo/dark.svg"
7 | },
8 | "colors": {
9 | "primary": "#FFB53D",
10 | "light": "#FFF238",
11 | "dark": "#FF3D5D",
12 | "anchors": {
13 | "from": "#FFF238",
14 | "to": "#FF3D5D"
15 | },
16 | "background": {
17 | "dark": "#121212"
18 | }
19 | },
20 | "topbarLinks": [
21 | {
22 | "name": "Website",
23 | "url": "https://www.traceloop.dev"
24 | }
25 | ],
26 | "topbarCtaButton": {
27 | "type": "github",
28 | "url": "https://github.com/traceloop/jest-opentelemetry"
29 | },
30 | "anchors": [
31 | {
32 | "name": "Jest OpenTelemetry",
33 | "icon": "node",
34 | "url": "jest-otel"
35 | },
36 | {
37 | "name": "Community",
38 | "icon": "slack",
39 | "url": "https://join.slack.com/t/traceloopcommunity/shared_invite/zt-1plpfpm6r-zOHKI028VkpcWdobX65C~g"
40 | },
41 | {
42 | "name": "GitHub",
43 | "icon": "github",
44 | "url": "https://github.com/traceloop"
45 | }
46 | ],
47 | "navigation": [
48 | {
49 | "group": "Learn",
50 | "pages": ["introduction"]
51 | },
52 | {
53 | "group": "Quick Start",
54 | "pages": [
55 | "jest-otel/introduction",
56 | "jest-otel/getting-started",
57 | "jest-otel/no-otel",
58 | "jest-otel/architecture"
59 | ]
60 | },
61 | {
62 | "group": "Syntax",
63 | "pages": [
64 | "jest-otel/syntax/overview",
65 | "jest-otel/syntax/services-rest",
66 | "jest-otel/syntax/services-grpc",
67 | "jest-otel/syntax/db-pg",
68 | "jest-otel/syntax/db-redis"
69 | ]
70 | },
71 | {
72 | "group": "Contributing",
73 | "pages": [
74 | "jest-otel/contributing/overview",
75 | "jest-otel/contributing/local-env",
76 | "jest-otel/contributing/test-servers"
77 | ]
78 | }
79 | ],
80 | "footerSocials": {
81 | "github": "https://github.com/traceloop",
82 | "twitter": "https://twitter.com/traceloopdev"
83 | },
84 | "analytics": {
85 | "posthog": {
86 | "apiKey": "phc_S07ERHfM7mBYvjzzW18gxfTjhly2dOD8y2WiRYajV5J"
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/traceloop/jest-opentelemetry/fea70d0719085318e9a4468a1d7616e51148dfab/img/logo.png
--------------------------------------------------------------------------------
/jest-opentelemetry.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | timeout: 2000,
3 | useLocalOtelReceiver: true, // default (false) is to use traceloop backend
4 | };
5 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | projects: ['/packages/*'],
3 | };
4 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json",
3 | "useWorkspaces": true,
4 | "version": "0.8.0"
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jest-opentelemetry",
3 | "private": true,
4 | "scripts": {
5 | "build": "lerna run build",
6 | "format": "prettier --write .",
7 | "lint": "prettier --check . && eslint .",
8 | "start:otel-receiver": "lerna run --scope @traceloop/otel-receiver start",
9 | "start:test-servers": "lerna run --scope @traceloop/test-servers start",
10 | "docker:instrument-opentelemetry": "docker build -f packages/instrument-opentelemetry/Dockerfile . -t instrument-opentelemetry",
11 | "docker:test-servers": "docker build -f packages/test-servers/Dockerfile . -t test-servers",
12 | "test": "TRACELOOP_URL='http://localhost:4123/v1/traces' jest",
13 | "test-ci": "TRACELOOP_URL='http://localhost:4123/v1/traces' concurrently -k --success \"command-1\" --hide 0 \"npm:start:test-servers\" \"npm:test\"",
14 | "release": "npm run build && lerna publish --conventional-commits --no-private"
15 | },
16 | "workspaces": [
17 | "packages/*"
18 | ],
19 | "devDependencies": {
20 | "@commitlint/cli": "^17.4.4",
21 | "@commitlint/config-conventional": "^17.4.4",
22 | "@rollup/plugin-typescript": "^11.0.0",
23 | "@tsconfig/recommended": "^1.0.2",
24 | "@typescript-eslint/eslint-plugin": "^5.54.1",
25 | "@typescript-eslint/parser": "^5.52.0",
26 | "concurrently": "^7.6.0",
27 | "eslint": "^8.35.0",
28 | "husky": "^8.0.3",
29 | "jest": "^29.4.3",
30 | "lerna": "^6.5.1",
31 | "typescript": "^4.9.5"
32 | },
33 | "dependencies": {
34 | "@swc/jest": "^0.2.24",
35 | "eslint-config-prettier": "^8.8.0",
36 | "prettier": "^2.8.4"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/.npmignore:
--------------------------------------------------------------------------------
1 | /*
2 | !/dist/**/*.js
3 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/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/expect-opentelemetry/README.md:
--------------------------------------------------------------------------------
1 | # expect-opentelemetry
2 |
3 | Assertion library for OpenTelemetry.
4 |
5 | ```
6 | npm install @traceloop/expect-opentelemetry
7 | ```
8 |
9 | ## Usage
10 |
11 | ## License
12 |
13 | Apache-2.0
14 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: '/../jest-opentelemetry',
3 | transform: {
4 | '^.+\\.(t|j)sx?$': ['@swc/jest'],
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@traceloop/expect-opentelemetry",
3 | "description": "Assertion toolkit for OpenTelemetry tests.",
4 | "version": "0.8.0",
5 | "license": "Apache-2.0",
6 | "type": "commonjs",
7 | "main": "./dist/index.js",
8 | "types": "./dist/index.d.ts",
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/traceloop/jest-opentelemetry.git",
12 | "directory": "packages/expect-opentelemetry"
13 | },
14 | "homepage": "https://github.com/traceloop/jest-opentelemetry/tree/main/packages/expect-opentelemetry#readme",
15 | "bugs": {
16 | "url": "https://github.com/traceloop/jest-opentelemetry/issues"
17 | },
18 | "engines": {
19 | "node": ">=14.0.0"
20 | },
21 | "keywords": [
22 | "jest",
23 | "opentelemetry",
24 | "jest-opentelemetry",
25 | "expect",
26 | "assert",
27 | "should",
28 | "assertion"
29 | ],
30 | "publishConfig": {
31 | "access": "public"
32 | },
33 | "scripts": {
34 | "prebuild": "rm -rf dist",
35 | "build": "rollup -c"
36 | },
37 | "dependencies": {
38 | "@opentelemetry/semantic-conventions": "^1.10.1",
39 | "@traceloop/otel-proto": "^0.8.0",
40 | "axios": "^1.3.4",
41 | "deep-equal": "^2.2.0"
42 | },
43 | "devDependencies": {
44 | "@types/deep-equal": "^1.0.1",
45 | "rollup": "^3.20.0",
46 | "rollup-plugin-swc3": "^0.8.0"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import { swc, defineRollupSwcOption } from 'rollup-plugin-swc3';
2 | import typescript from '@rollup/plugin-typescript';
3 |
4 | const bundle = (config) => ({
5 | external: (id) => {
6 | return !/^[./]/.test(id);
7 | },
8 | ...config,
9 | });
10 |
11 | const swcPlugin = swc(
12 | defineRollupSwcOption({
13 | jsc: { target: 'es2021' },
14 | }),
15 | );
16 |
17 | export default [
18 | bundle({
19 | input: 'src/index.ts',
20 | output: {
21 | file: 'dist/index.js',
22 | format: 'cjs',
23 | },
24 | plugins: [
25 | swcPlugin,
26 | typescript({
27 | tsconfig: '../../tsconfig.json',
28 | exclude: ['**/*.test.ts'],
29 | }),
30 | ],
31 | }),
32 | ];
33 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | toReceiveHttpRequest,
3 | toSendHttpRequest,
4 | toQueryPostgreSQL,
5 | toReceiveGrpcRequest,
6 | toSendGrpcRequest,
7 | toSendRedisCommand,
8 | } from './matchers/service';
9 | import { expect } from '@jest/globals';
10 | import {
11 | GrpcRequest,
12 | HttpRequest,
13 | PostgreSQLQuery,
14 | RedisCommand,
15 | Service,
16 | } from './resources';
17 | export { setDefaultOptions, getDefaultOptions } from './options';
18 |
19 | export * from './matchers';
20 | export * from './resources';
21 | export * from './trace-loop';
22 |
23 | const serviceMatchers = {
24 | toReceiveHttpRequest,
25 | toSendHttpRequest,
26 | toQueryPostgreSQL,
27 | toReceiveGrpcRequest,
28 | toSendGrpcRequest,
29 | toSendRedisCommand,
30 | };
31 |
32 | interface MatcherOptions {
33 | times: number;
34 | }
35 |
36 | interface TraceMatchers {
37 | toReceiveHttpRequest(): HttpRequest;
38 | toSendHttpRequest(): HttpRequest;
39 | toQueryPostgreSQL(options?: MatcherOptions): PostgreSQLQuery;
40 | toReceiveGrpcRequest(): GrpcRequest;
41 | toSendGrpcRequest(): GrpcRequest;
42 | toSendRedisCommand(options?: MatcherOptions): RedisCommand;
43 | }
44 |
45 | function createMatcher(matcher, type) {
46 | return function throwingMatcher(...args) {
47 | if (typeof expect !== 'undefined') {
48 | expect.getState().assertionCalls += 1;
49 | }
50 |
51 | try {
52 | return matcher(type, ...args);
53 | } catch (error: any) {
54 | Error.captureStackTrace(error, throwingMatcher);
55 | throw error;
56 | }
57 | };
58 | }
59 |
60 | export function expectTrace(actual: Service): TraceMatchers {
61 | const expectation: Partial = {};
62 | Object.keys(serviceMatchers).forEach((key) => {
63 | if (key === 'not') return;
64 | expectation[key] = createMatcher(serviceMatchers[key], actual);
65 | });
66 |
67 | return expectation as TraceMatchers;
68 | }
69 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/matchers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './service';
2 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/matchers/service/grpc-request.test.ts:
--------------------------------------------------------------------------------
1 | import { jest, describe, it, beforeAll } from '@jest/globals';
2 | import { expectTrace } from '../..';
3 | import { TraceLoop } from '../../trace-loop';
4 |
5 | jest.setTimeout(30000);
6 |
7 | describe('grpc request matchers', () => {
8 | describe('when orders-service makes a gRPC call to the grpc server', () => {
9 | let traceloop: TraceLoop;
10 | beforeAll(async () => {
11 | traceloop = new TraceLoop();
12 |
13 | await traceloop.axiosInstance.post('http://localhost:3000/orders/create');
14 | await traceloop.fetchTraces();
15 | });
16 |
17 | it('should contain outbound gRPC call from orders-service', async () => {
18 | expectTrace(
19 | traceloop.serviceByName('orders-service'),
20 | ).toSendGrpcRequest();
21 | });
22 |
23 | it('should contain inbound gRPC call to grpc service', async () => {
24 | expectTrace(
25 | traceloop.serviceByName('grpc-service'),
26 | ).toReceiveGrpcRequest();
27 | });
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/matchers/service/http-request.test.ts:
--------------------------------------------------------------------------------
1 | import { jest, describe, it, beforeAll } from '@jest/globals';
2 | import { expectTrace } from '../..';
3 | import { TraceLoop } from '../../trace-loop';
4 |
5 | jest.setTimeout(30000);
6 |
7 | describe('http request matchers', () => {
8 | describe('when orders-service makes an http call to emails-service', () => {
9 | let traceloop: TraceLoop;
10 | beforeAll(async () => {
11 | traceloop = new TraceLoop();
12 |
13 | await traceloop.axiosInstance.post('http://localhost:3000/orders/create');
14 | await traceloop.fetchTraces();
15 | });
16 |
17 | it('should contain outbound http call from orders-service', async () => {
18 | expectTrace(
19 | traceloop.serviceByName('orders-service'),
20 | ).toSendHttpRequest();
21 | });
22 |
23 | it('should contain inbound http call to emails-service', async () => {
24 | expectTrace(
25 | traceloop.serviceByName('emails-service'),
26 | ).toReceiveHttpRequest();
27 | });
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/matchers/service/index.ts:
--------------------------------------------------------------------------------
1 | export * from './to-receive-http-request';
2 | export * from './to-send-http-request';
3 | export * from './to-query-postgresql';
4 | export * from './to-receive-grpc-request';
5 | export * from './to-send-grpc-request';
6 | export * from './to-send-redis-command';
7 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/matchers/service/to-query-postgresql.test.ts:
--------------------------------------------------------------------------------
1 | import { jest, describe, it, beforeAll } from '@jest/globals';
2 | import { expectTrace } from '../..';
3 | import { TraceLoop } from '../../trace-loop';
4 | import { COMPARE_TYPE } from '../utils';
5 |
6 | jest.setTimeout(30000);
7 |
8 | describe('postgresql query', () => {
9 | let traceloop: TraceLoop;
10 | beforeAll(async () => {
11 | traceloop = new TraceLoop();
12 |
13 | await traceloop.axiosInstance.post('http://localhost:3000/orders/create');
14 | await traceloop.fetchTraces();
15 | });
16 |
17 | it('should see orders-service querying postgresql named postgres', async () => {
18 | expectTrace(traceloop.serviceByName('orders-service'))
19 | .toQueryPostgreSQL()
20 | .withDatabaseName('postgres', { compareType: COMPARE_TYPE.EQUALS });
21 | });
22 |
23 | it('should see orders-service querying postgresql named postgres and inserting an order with uuid as id and integer price_in_cents', async () => {
24 | expectTrace(traceloop.serviceByName('orders-service'))
25 | .toQueryPostgreSQL()
26 | .withDatabaseName('postgres', { compareType: COMPARE_TYPE.EQUALS })
27 | .withOperations('INSERT')
28 | .withTables('orders')
29 | .withStatement(
30 | /INSERT INTO orders \(id, price_in_cents\) VALUES \('[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}', [0-9]+\)/,
31 | { compareType: COMPARE_TYPE.REGEX },
32 | );
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/matchers/service/to-query-postgresql.ts:
--------------------------------------------------------------------------------
1 | import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
2 | import { Service } from '../../resources/service';
3 | import { opentelemetry } from '@traceloop/otel-proto';
4 | import { PostgreSQLQuery } from '../../resources/postgresql-query';
5 |
6 | export function toQueryPostgreSQL(
7 | service: Service,
8 | options = { times: 1 },
9 | ): PostgreSQLQuery {
10 | const { name: serviceName, spans } = service;
11 |
12 | const filteredSpans = spans.filter((span) => {
13 | return (
14 | span.kind ===
15 | opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_CLIENT &&
16 | span.attributes?.find(
17 | (attribute: opentelemetry.proto.common.v1.IKeyValue) => {
18 | return (
19 | attribute.key === SemanticAttributes.DB_SYSTEM &&
20 | attribute.value?.stringValue === 'postgresql'
21 | );
22 | },
23 | )
24 | );
25 | });
26 |
27 | if (filteredSpans.length < options.times) {
28 | throw new Error(
29 | `Expected ${options.times} queries by ${serviceName} to postgresql, but found ${filteredSpans.length}.`,
30 | );
31 | }
32 |
33 | return new PostgreSQLQuery(filteredSpans, serviceName, options.times);
34 | }
35 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/matchers/service/to-receive-grpc-request.ts:
--------------------------------------------------------------------------------
1 | import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
2 | import { Service } from '../../resources/service';
3 | import { opentelemetry } from '@traceloop/otel-proto';
4 | import { GrpcRequest } from '../../resources';
5 |
6 | export function toReceiveGrpcRequest(service: Service): GrpcRequest {
7 | const { name: serviceName, spans } = service;
8 | const spanKind = opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_SERVER;
9 |
10 | const filteredSpans = spans.filter(
11 | (span: opentelemetry.proto.trace.v1.ISpan) => {
12 | return (
13 | span.kind === spanKind &&
14 | span.attributes?.find(
15 | (attribute: opentelemetry.proto.common.v1.IKeyValue) => {
16 | return (
17 | attribute.key === SemanticAttributes.RPC_SYSTEM &&
18 | attribute.value?.stringValue === 'grpc'
19 | );
20 | },
21 | )
22 | );
23 | },
24 | );
25 |
26 | if (filteredSpans.length === 0) {
27 | throw new Error(`No gRPC call was received by ${serviceName}`);
28 | }
29 |
30 | return new GrpcRequest(filteredSpans, { serviceName, spanKind });
31 | }
32 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/matchers/service/to-receive-http-request.ts:
--------------------------------------------------------------------------------
1 | import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
2 | import { HttpRequest } from '../../resources/http-request';
3 | import { Service } from '../../resources/service';
4 | import { opentelemetry } from '@traceloop/otel-proto';
5 |
6 | export function toReceiveHttpRequest(service: Service): HttpRequest {
7 | const { name: serviceName, spans } = service;
8 | const spanKind = opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_SERVER;
9 |
10 | const filteredSpans = spans.filter((span) => {
11 | return (
12 | span.kind === spanKind &&
13 | span.attributes?.find((attribute) => {
14 | return attribute.key === SemanticAttributes.HTTP_METHOD;
15 | })
16 | );
17 | });
18 |
19 | if (filteredSpans.length === 0) {
20 | throw new Error(`No HTTP call received by ${serviceName}`);
21 | }
22 |
23 | return new HttpRequest(filteredSpans, { serviceName, spanKind });
24 | }
25 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/matchers/service/to-send-grpc-request.ts:
--------------------------------------------------------------------------------
1 | import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
2 | import { Service } from '../../resources/service';
3 | import { opentelemetry } from '@traceloop/otel-proto';
4 | import { GrpcRequest } from '../../resources';
5 |
6 | export function toSendGrpcRequest(service: Service): GrpcRequest {
7 | const { name: serviceName, spans } = service;
8 | const spanKind = opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_CLIENT;
9 |
10 | const filteredSpans = spans.filter(
11 | (span: opentelemetry.proto.trace.v1.ISpan) => {
12 | return (
13 | span.kind === spanKind &&
14 | span.attributes?.find(
15 | (attribute: opentelemetry.proto.common.v1.IKeyValue) => {
16 | return (
17 | attribute.key === SemanticAttributes.RPC_SYSTEM &&
18 | attribute.value?.stringValue === 'grpc'
19 | );
20 | },
21 | )
22 | );
23 | },
24 | );
25 |
26 | if (filteredSpans.length === 0) {
27 | throw new Error(`No gRPC call was sent by ${serviceName}`);
28 | }
29 |
30 | return new GrpcRequest(filteredSpans, { serviceName, spanKind });
31 | }
32 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/matchers/service/to-send-http-request.ts:
--------------------------------------------------------------------------------
1 | import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
2 | import { HttpRequest } from '../../resources/http-request';
3 | import { Service } from '../../resources/service';
4 | import { opentelemetry } from '@traceloop/otel-proto';
5 |
6 | export function toSendHttpRequest(service: Service): HttpRequest {
7 | const { name: serviceName, spans } = service;
8 | const spanKind = opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_CLIENT;
9 |
10 | const filteredSpans = spans.filter((span) => {
11 | return (
12 | span.kind === spanKind &&
13 | span.attributes?.find((attribute) => {
14 | return attribute.key === SemanticAttributes.HTTP_METHOD;
15 | })
16 | );
17 | });
18 |
19 | if (filteredSpans.length === 0) {
20 | throw new Error(`No HTTP call was sent by ${serviceName}`);
21 | }
22 |
23 | return new HttpRequest(filteredSpans, { serviceName, spanKind });
24 | }
25 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/matchers/service/to-send-redis-command.ts:
--------------------------------------------------------------------------------
1 | import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
2 | import { opentelemetry } from '@traceloop/otel-proto';
3 | import { RedisCommand, Service } from '../../resources';
4 |
5 | export function toSendRedisCommand(
6 | service: Service,
7 | options = { times: 1 },
8 | ): RedisCommand {
9 | const { name: serviceName, spans } = service;
10 |
11 | const filteredSpans = spans.filter((span) => {
12 | return (
13 | span.kind ===
14 | opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_CLIENT &&
15 | span.attributes?.find(
16 | (attribute: opentelemetry.proto.common.v1.IKeyValue) => {
17 | return (
18 | attribute.key === SemanticAttributes.DB_SYSTEM &&
19 | attribute.value?.stringValue === 'redis'
20 | );
21 | },
22 | )
23 | );
24 | });
25 |
26 | if (filteredSpans.length < options.times) {
27 | throw new Error(
28 | `Expected ${options.times} queries by ${serviceName} to redis, but found ${filteredSpans.length}.`,
29 | );
30 | }
31 |
32 | return new RedisCommand(filteredSpans, serviceName, options.times);
33 | }
34 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/matchers/utils/comparators.ts:
--------------------------------------------------------------------------------
1 | import deepEqual from 'deep-equal';
2 | import { expect } from '@jest/globals';
3 | import { CompareOptions, COMPARE_TYPE } from './compare-types';
4 |
5 | type MaybeString = string | undefined | null;
6 |
7 | export const stringEquals = (a: MaybeString, b: MaybeString): boolean =>
8 | a === b;
9 |
10 | export const stringContains = (a: MaybeString, b: MaybeString): boolean =>
11 | !!a && !!b && a.includes(b);
12 |
13 | export const stringByRegex = (
14 | a: MaybeString,
15 | b: RegExp | undefined | null,
16 | ): boolean => !!a && !!b && b.test(a);
17 |
18 | export const objectEquals = (
19 | a: Record,
20 | b: Record,
21 | ): boolean => deepEqual(a, b);
22 |
23 | export const objectContains = (
24 | a: Record,
25 | b: Record,
26 | ): boolean => {
27 | try {
28 | expect(a).toMatchObject(b);
29 | return true;
30 | } catch (e) {
31 | return false;
32 | }
33 | };
34 |
35 | export const stringCompare = (
36 | a: MaybeString,
37 | b: MaybeString | RegExp,
38 | options?: CompareOptions,
39 | ): boolean => {
40 | switch (options?.compareType) {
41 | case COMPARE_TYPE.CONTAINS:
42 | return stringContains(a, b as string);
43 | case COMPARE_TYPE.REGEX:
44 | return stringByRegex(a, b as RegExp);
45 | default:
46 | return stringEquals(a, b as string);
47 | }
48 | };
49 |
50 | export const objectCompare = (
51 | a: Record,
52 | b: Record,
53 | options?: CompareOptions,
54 | ): boolean => {
55 | switch (options?.compareType) {
56 | case COMPARE_TYPE.CONTAINS:
57 | return objectContains(a, b);
58 | default:
59 | return objectEquals(a, b);
60 | }
61 | };
62 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/matchers/utils/compare-types.ts:
--------------------------------------------------------------------------------
1 | export const COMPARE_TYPE = {
2 | EQUALS: 'equals',
3 | CONTAINS: 'contains',
4 | REGEX: 'regex',
5 | } as const;
6 |
7 | export type CompareType = (typeof COMPARE_TYPE)[keyof typeof COMPARE_TYPE];
8 |
9 | export type CompareOptions = { compareType: CompareType };
10 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/matchers/utils/filters.ts:
--------------------------------------------------------------------------------
1 | import { opentelemetry } from '@traceloop/otel-proto';
2 | import { objectCompare, stringCompare } from './comparators';
3 | import { CompareOptions } from './compare-types';
4 |
5 | export const extractAttributeStringValues = (
6 | spans: opentelemetry.proto.trace.v1.ISpan[],
7 | expected: string,
8 | ): string[] => {
9 | return spans
10 | .map((span) => {
11 | return span.attributes?.find(
12 | (attribute: opentelemetry.proto.common.v1.IKeyValue) =>
13 | attribute.key === expected,
14 | )?.value?.stringValue;
15 | })
16 | .filter((statement) => !!statement) as string[];
17 | };
18 |
19 | export const filterByAttributeKey = (
20 | spans: opentelemetry.proto.trace.v1.ISpan[],
21 | attName: string,
22 | ) =>
23 | spans.filter((span) => {
24 | return span.attributes?.find(
25 | (attribute: opentelemetry.proto.common.v1.IKeyValue) => {
26 | return attribute.key === attName;
27 | },
28 | );
29 | });
30 |
31 | export const filterBySpanKind = (
32 | spans: opentelemetry.proto.trace.v1.ISpan[],
33 | expected: opentelemetry.proto.trace.v1.Span.SpanKind,
34 | ) => spans.filter((span) => span.kind === expected);
35 |
36 | export const filterByAttributeStringValue = (
37 | spans: opentelemetry.proto.trace.v1.ISpan[],
38 | attName: string,
39 | attValue: string | RegExp,
40 | options?: CompareOptions,
41 | ) =>
42 | spans.filter((span) => {
43 | return span.attributes?.find(
44 | (attribute) =>
45 | attribute.key === attName &&
46 | stringCompare(attribute.value?.stringValue, attValue, options),
47 | );
48 | });
49 |
50 | export const filterByAttributeIntValue = (
51 | spans: opentelemetry.proto.trace.v1.ISpan[],
52 | attName: string,
53 | attValue: number,
54 | ) =>
55 | spans.filter((span) => {
56 | return span.attributes?.find(
57 | (attribute: opentelemetry.proto.common.v1.IKeyValue) =>
58 | attribute.key === attName &&
59 | attribute.value?.intValue.toNumber() === attValue,
60 | );
61 | });
62 |
63 | export const filterByAttributeJSON = (
64 | spans: opentelemetry.proto.trace.v1.ISpan[],
65 | attName: string,
66 | attValue: Record,
67 | options?: CompareOptions,
68 | ) =>
69 | spans.filter((span) => {
70 | try {
71 | const json = JSON.parse(
72 | span.attributes?.find((attribute) => attribute.key === attName)?.value
73 | ?.stringValue || '',
74 | );
75 |
76 | return objectCompare(json, attValue, options);
77 | } catch (e) {
78 | return false;
79 | }
80 | });
81 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/matchers/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './compare-types';
2 | export * from './comparators';
3 | export * from './filters';
4 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/options.ts:
--------------------------------------------------------------------------------
1 | let defaultOptionsValue = { timeout: 500 };
2 |
3 | export const setDefaultOptions = (options: any) => {
4 | defaultOptionsValue = options;
5 | };
6 |
7 | export const getDefaultOptions = () => {
8 | return defaultOptionsValue;
9 | };
10 |
11 | export const defaultOptions = (options: any) => ({
12 | ...getDefaultOptions(),
13 | ...options,
14 | });
15 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/resources/grpc-request.test.ts:
--------------------------------------------------------------------------------
1 | import { jest, describe, it } from '@jest/globals';
2 | import { expectTrace } from '../..';
3 | import { TraceLoop } from '../trace-loop';
4 | import { GrpcRequest } from '../resources/grpc-request';
5 |
6 | jest.setTimeout(30000);
7 |
8 | describe('resource grpc request matchers', () => {
9 | describe('when orders-service makes a gRPC call to grpc-service', () => {
10 | let traceloop: TraceLoop;
11 | beforeAll(async () => {
12 | traceloop = new TraceLoop();
13 |
14 | await traceloop.axiosInstance.post('http://localhost:3000/orders/create');
15 | await traceloop.fetchTraces();
16 | });
17 |
18 | it('should contain outbound grpc call from orders-service with all parameters', async () => {
19 | expectTrace(traceloop.serviceByName('orders-service'))
20 | .toSendGrpcRequest()
21 | .withRpcMethod('SayHello')
22 | .withRpcService('Greeter', { compareType: 'contains' })
23 | .withRpcGrpcStatusCode(GrpcRequest.GRPC_STATUS_CODE.OK);
24 | });
25 |
26 | it('should contain inbound gRPC call to grpc-service', async () => {
27 | expectTrace(traceloop.serviceByName('grpc-service'))
28 | .toReceiveGrpcRequest()
29 | .withRpcMethod('SayHello')
30 | .withRpcService('Greeter', { compareType: 'contains' })
31 | .withRpcGrpcStatusCode(GrpcRequest.GRPC_STATUS_CODE.OK);
32 | });
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/resources/grpc-request.ts:
--------------------------------------------------------------------------------
1 | import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
2 | import { opentelemetry } from '@traceloop/otel-proto';
3 | import {
4 | CompareOptions,
5 | filterByAttributeIntValue,
6 | filterByAttributeStringValue,
7 | } from '../matchers/utils';
8 |
9 | const STATUS_CODE = {
10 | OK: 0,
11 | CANCELLED: 1,
12 | UNKNOWN: 2,
13 | INVALID_ARGUMENT: 3,
14 | DEADLINE_EXCEEDED: 4,
15 | NOT_FOUND: 5,
16 | ALREADY_EXISTS: 6,
17 | PERMISSION_DENIED: 7,
18 | RESOURCE_EXHAUSTED: 8,
19 | FAILED_PRECONDITION: 9,
20 | ABORTED: 10,
21 | OUT_OF_RANGE: 11,
22 | UNIMPLEMENTED: 12,
23 | INTERNAL: 13,
24 | UNAVAILABLE: 14,
25 | DATA_LOSS: 15,
26 | UNAUTHENTICATED: 16,
27 | } as const;
28 |
29 | type StatusCode = (typeof STATUS_CODE)[keyof typeof STATUS_CODE];
30 |
31 | export class GrpcRequest {
32 | static readonly GRPC_STATUS_CODE = STATUS_CODE;
33 |
34 | constructor(
35 | readonly spans: opentelemetry.proto.trace.v1.ISpan[],
36 | readonly extra: {
37 | serviceName: string;
38 | spanKind: opentelemetry.proto.trace.v1.Span.SpanKind;
39 | },
40 | ) {}
41 |
42 | withRpcMethod(method: string, options?: CompareOptions) {
43 | const filteredSpans = filterByAttributeStringValue(
44 | this.spans,
45 | SemanticAttributes.RPC_METHOD,
46 | method,
47 | options,
48 | );
49 |
50 | if (filteredSpans.length === 0) {
51 | throw new Error(
52 | `No gRPC call of method ${method} ${this.serviceErrorBySpanKind()}`,
53 | );
54 | }
55 |
56 | return new GrpcRequest(filteredSpans, this.extra);
57 | }
58 |
59 | withRpcService(service: string, options?: CompareOptions) {
60 | const filteredSpans = filterByAttributeStringValue(
61 | this.spans,
62 | SemanticAttributes.RPC_SERVICE,
63 | service,
64 | options,
65 | );
66 |
67 | if (filteredSpans.length === 0) {
68 | throw new Error(
69 | `No gRPC call for service ${service} ${this.serviceErrorBySpanKind()}`,
70 | );
71 | }
72 |
73 | return new GrpcRequest(filteredSpans, this.extra);
74 | }
75 |
76 | withRpcGrpcStatusCode(code: StatusCode) {
77 | const filteredSpans = filterByAttributeIntValue(
78 | this.spans,
79 | SemanticAttributes.RPC_GRPC_STATUS_CODE,
80 | code,
81 | );
82 |
83 | // spec says it should be an int, but in practice we get strings
84 | const filteredSpansString = filterByAttributeStringValue(
85 | this.spans,
86 | SemanticAttributes.RPC_GRPC_STATUS_CODE,
87 | code.toString(),
88 | );
89 |
90 | if (filteredSpans.length === 0 && filteredSpansString.length === 0) {
91 | throw new Error(
92 | `No gRPC call with status code ${code} ${this.serviceErrorBySpanKind()}`,
93 | );
94 | }
95 |
96 | return new GrpcRequest(
97 | filteredSpans.length !== 0 ? filteredSpans : filteredSpansString,
98 | this.extra,
99 | );
100 | }
101 |
102 | withNetPeerName(netPeerName: string, options?: CompareOptions) {
103 | const filteredSpans = filterByAttributeStringValue(
104 | this.spans,
105 | SemanticAttributes.NET_PEER_NAME,
106 | netPeerName,
107 | options,
108 | );
109 |
110 | if (filteredSpans.length === 0) {
111 | throw new Error(
112 | `No gRPC call with net peer name (host name) ${netPeerName} ${this.serviceErrorBySpanKind()}`,
113 | );
114 | }
115 |
116 | return new GrpcRequest(filteredSpans, this.extra);
117 | }
118 |
119 | withHostName(hostName: string, options?: CompareOptions) {
120 | return this.withNetPeerName(hostName, options);
121 | }
122 |
123 | private serviceErrorBySpanKind() {
124 | const { SPAN_KIND_CLIENT, SPAN_KIND_SERVER } =
125 | opentelemetry.proto.trace.v1.Span.SpanKind;
126 | const { spanKind, serviceName } = this.extra;
127 |
128 | switch (spanKind) {
129 | case SPAN_KIND_CLIENT:
130 | return `was sent by ${serviceName}`;
131 | case SPAN_KIND_SERVER:
132 | return `was received by ${serviceName}`;
133 | default:
134 | return `was found for ${serviceName}`;
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/resources/http-request.test.ts:
--------------------------------------------------------------------------------
1 | import { jest, describe, it } from '@jest/globals';
2 | import { expectTrace } from '../..';
3 | import { TraceLoop } from '../trace-loop';
4 |
5 | jest.setTimeout(30000);
6 |
7 | describe('resource http request matchers', () => {
8 | describe('when orders-service makes an http call to emails-service', () => {
9 | let traceloop: TraceLoop;
10 | beforeAll(async () => {
11 | traceloop = new TraceLoop();
12 |
13 | await traceloop.axiosInstance.post('http://localhost:3000/orders/create');
14 | await traceloop.fetchTraces();
15 | });
16 |
17 | it('should contain outbound http call from orders-service with all parameters', async () => {
18 | expectTrace(traceloop.serviceByName('orders-service'))
19 | .toSendHttpRequest()
20 | .withMethod('POST')
21 | .withUrl('/emails/send', { compareType: 'contains' })
22 | .withRequestHeader('content-type', 'application/json')
23 | .withRequestBody({
24 | email: 'test',
25 | nestedObject: { test: 'test' },
26 | })
27 | .withRequestBody(
28 | { nestedObject: { test: 'test' } },
29 | { compareType: 'contains' },
30 | )
31 | .withStatusCode(200);
32 | });
33 |
34 | it('should contain inbound http call to emails-service', async () => {
35 | expectTrace(traceloop.serviceByName('emails-service'))
36 | .toReceiveHttpRequest()
37 | .withMethod('POST')
38 | .withUrl('/emails/send', { compareType: 'contains' })
39 | .withStatusCode(200);
40 | });
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/resources/http-request.ts:
--------------------------------------------------------------------------------
1 | import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
2 | import { opentelemetry } from '@traceloop/otel-proto';
3 | import {
4 | CompareOptions,
5 | filterByAttributeIntValue,
6 | filterByAttributeJSON,
7 | filterByAttributeStringValue,
8 | stringCompare,
9 | } from '../matchers/utils';
10 |
11 | export class HttpRequest {
12 | constructor(
13 | readonly spans: opentelemetry.proto.trace.v1.ISpan[],
14 | readonly extra: {
15 | serviceName: string;
16 | spanKind: opentelemetry.proto.trace.v1.Span.SpanKind;
17 | },
18 | ) {}
19 |
20 | withRequestBody(body: Record, options?: CompareOptions) {
21 | const filteredSpans = filterByAttributeJSON(
22 | this.spans,
23 | 'http.request.body',
24 | body,
25 | options,
26 | );
27 |
28 | if (filteredSpans.length === 0) {
29 | throw new Error(
30 | `No HTTP call with request body ${body} ${this.serviceErrorBySpanKind()}`,
31 | );
32 | }
33 |
34 | return new HttpRequest(filteredSpans, this.extra);
35 | }
36 |
37 | withResponseBody(body: Record, options?: CompareOptions) {
38 | const filteredSpans = filterByAttributeJSON(
39 | this.spans,
40 | 'http.response.body',
41 | body,
42 | options,
43 | );
44 |
45 | if (filteredSpans.length === 0) {
46 | throw new Error(
47 | `No HTTP call with response body ${body} ${this.serviceErrorBySpanKind()}`,
48 | );
49 | }
50 |
51 | return new HttpRequest(filteredSpans, this.extra);
52 | }
53 |
54 | withRequestHeader(key: string, value: string, options?: CompareOptions) {
55 | const filteredSpansBySingle = filterByAttributeStringValue(
56 | this.spans,
57 | `http.request.header.${key}`,
58 | value,
59 | options,
60 | );
61 |
62 | const headerObjectSpans = this.spans.filter((span) => {
63 | const attr = span.attributes?.find(
64 | (attribute) => attribute.key === 'http.request.headers',
65 | );
66 | try {
67 | const headerObject = JSON.parse(attr?.value?.stringValue ?? '');
68 | return stringCompare(headerObject[key], value);
69 | } catch (e) {
70 | return false;
71 | }
72 | });
73 |
74 | const filteredSpans = [...filteredSpansBySingle, ...headerObjectSpans];
75 |
76 | if (filteredSpans.length === 0) {
77 | throw new Error(
78 | `No HTTP call with request header ${key} assigned with ${value} ${this.serviceErrorBySpanKind()}`,
79 | );
80 | }
81 |
82 | return new HttpRequest(filteredSpans, this.extra);
83 | }
84 |
85 | withResponseHeader(key: string, value: string, options?: CompareOptions) {
86 | const filteredSpansBySingle = filterByAttributeStringValue(
87 | this.spans,
88 | `http.response.header.${key}`,
89 | value,
90 | options,
91 | );
92 |
93 | const headerObjectSpans = this.spans.filter((span) => {
94 | const attr = span.attributes?.find(
95 | (attribute) => attribute.key === 'http.response.headers',
96 | );
97 | try {
98 | const headerObject = JSON.parse(attr?.value?.stringValue ?? '');
99 | return stringCompare(headerObject[key], value);
100 | } catch (e) {
101 | return false;
102 | }
103 | });
104 |
105 | const filteredSpans = [...filteredSpansBySingle, ...headerObjectSpans];
106 |
107 | if (filteredSpans.length === 0) {
108 | throw new Error(
109 | `No HTTP call with response header ${key} assigned with ${value} ${this.serviceErrorBySpanKind()}`,
110 | );
111 | }
112 |
113 | return new HttpRequest(filteredSpans, this.extra);
114 | }
115 |
116 | withMethod(method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH') {
117 | const filteredSpans = filterByAttributeStringValue(
118 | this.spans,
119 | SemanticAttributes.HTTP_METHOD,
120 | method,
121 | );
122 |
123 | if (filteredSpans.length === 0) {
124 | throw new Error(
125 | `No HTTP call of method ${method} ${this.serviceErrorBySpanKind()}`,
126 | );
127 | }
128 |
129 | return new HttpRequest(filteredSpans, this.extra);
130 | }
131 |
132 | withStatusCode(code: number) {
133 | const filteredSpans = filterByAttributeIntValue(
134 | this.spans,
135 | SemanticAttributes.HTTP_STATUS_CODE,
136 | code,
137 | );
138 |
139 | if (filteredSpans.length === 0) {
140 | throw new Error(
141 | `No HTTP call with status code ${code} ${this.serviceErrorBySpanKind()}`,
142 | );
143 | }
144 |
145 | return new HttpRequest(filteredSpans, this.extra);
146 | }
147 |
148 | withUrl(url: string, options?: CompareOptions) {
149 | const filteredSpans = filterByAttributeStringValue(
150 | this.spans,
151 | SemanticAttributes.HTTP_URL,
152 | url,
153 | options,
154 | );
155 |
156 | if (filteredSpans.length === 0) {
157 | throw new Error(
158 | `No HTTP call with url ${url} ${this.serviceErrorBySpanKind()}`,
159 | );
160 | }
161 |
162 | return new HttpRequest(filteredSpans, this.extra);
163 | }
164 |
165 | private serviceErrorBySpanKind() {
166 | switch (this.extra.spanKind) {
167 | case opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_CLIENT:
168 | return `was sent by ${this.extra.serviceName}`;
169 | case opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_SERVER:
170 | return `was received by ${this.extra.serviceName}`;
171 | default:
172 | return `was found for ${this.extra.serviceName}`;
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/resources/index.ts:
--------------------------------------------------------------------------------
1 | export * from './http-request';
2 | export * from './service';
3 | export * from './postgresql-query';
4 | export * from './grpc-request';
5 | export * from './redis-command';
6 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/resources/postgresql-query.ts:
--------------------------------------------------------------------------------
1 | import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
2 | import { opentelemetry } from '@traceloop/otel-proto';
3 | import {
4 | CompareOptions,
5 | extractAttributeStringValues,
6 | filterByAttributeStringValue,
7 | } from '../matchers/utils';
8 |
9 | const tablesRegex = /(from|join|into|update|alter)\s+(?\S+)/gi;
10 |
11 | export class PostgreSQLQuery {
12 | constructor(
13 | readonly spans: opentelemetry.proto.trace.v1.ISpan[],
14 | private readonly serviceName: string,
15 | private readonly times = 1,
16 | ) {}
17 |
18 | withDatabaseName(name: string | RegExp, options?: CompareOptions) {
19 | const filteredSpans = filterByAttributeStringValue(
20 | this.spans,
21 | SemanticAttributes.DB_NAME,
22 | name,
23 | options,
24 | );
25 |
26 | if (filteredSpans.length < this.times) {
27 | throw new Error(`Expected ${this.times} queries by ${
28 | this.serviceName
29 | } to postgresql with database name ${name}, but found ${
30 | filteredSpans.length
31 | }.\n
32 | Found db names:\n ${extractAttributeStringValues(
33 | this.spans,
34 | SemanticAttributes.DB_NAME,
35 | )}`);
36 | }
37 |
38 | return new PostgreSQLQuery(filteredSpans, this.serviceName, this.times);
39 | }
40 |
41 | withStatement(statement: string | RegExp, options?: CompareOptions) {
42 | const filteredSpans = filterByAttributeStringValue(
43 | this.spans,
44 | SemanticAttributes.DB_STATEMENT,
45 | statement,
46 | options,
47 | );
48 |
49 | if (filteredSpans.length < this.times) {
50 | throw new Error(`Expected ${this.times} queries by ${
51 | this.serviceName
52 | } to postgresql with statement ${statement}, but found ${
53 | filteredSpans.length
54 | }.\n
55 | Found statements:\n${printStatements(this.spans)}`);
56 | }
57 |
58 | return new PostgreSQLQuery(filteredSpans, this.serviceName, this.times);
59 | }
60 |
61 | withOperations(...operations: string[]) {
62 | const filteredSpans = this.spans.filter((span) => {
63 | const statement = span.attributes?.find(
64 | (attribute: opentelemetry.proto.common.v1.IKeyValue) =>
65 | attribute.key === SemanticAttributes.DB_STATEMENT,
66 | )?.value?.stringValue;
67 |
68 | if (!statement) {
69 | return false;
70 | }
71 |
72 | const lowerCaseStatement = statement.toLowerCase();
73 |
74 | return operations.every((operation) =>
75 | lowerCaseStatement.includes(operation.toLowerCase()),
76 | );
77 | });
78 |
79 | if (filteredSpans.length < this.times) {
80 | throw new Error(
81 | `Expected ${this.times} queries by ${this.serviceName} to postgresql with operations ${operations}, but found ${filteredSpans.length}.\n` +
82 | `Found statements:\n${printStatements(this.spans)}`,
83 | );
84 | }
85 |
86 | return new PostgreSQLQuery(filteredSpans, this.serviceName, this.times);
87 | }
88 |
89 | withTables(...tables: string[]) {
90 | const filteredSpans = this.spans.filter((span) => {
91 | const statement = span.attributes?.find(
92 | (attribute: opentelemetry.proto.common.v1.IKeyValue) =>
93 | attribute.key === SemanticAttributes.DB_STATEMENT,
94 | )?.value?.stringValue;
95 |
96 | if (!statement) {
97 | return false;
98 | }
99 |
100 | const matches = statement.match(tablesRegex);
101 | const cleaned = matches?.map((elem: string) => {
102 | const [_, second] = elem.split(' ');
103 | return second
104 | .replaceAll('"', '')
105 | .replaceAll('(', '')
106 | .replaceAll(')', '')
107 | .replaceAll('\n', '')
108 | .toLocaleLowerCase();
109 | });
110 |
111 | return tables.every((table) =>
112 | cleaned?.includes(table.toLocaleLowerCase()),
113 | );
114 | });
115 |
116 | if (filteredSpans.length < this.times) {
117 | throw new Error(
118 | `Expected ${this.times} queries by ${this.serviceName} to postgresql with tables ${tables}, but found ${filteredSpans.length}.\n` +
119 | `Found statements:\n${printStatements(this.spans)}`,
120 | );
121 | }
122 |
123 | return new PostgreSQLQuery(filteredSpans, this.serviceName, this.times);
124 | }
125 | }
126 |
127 | const printStatements = (spans: opentelemetry.proto.trace.v1.ISpan[]) => {
128 | const MAX_LEN = 100;
129 | return extractAttributeStringValues(spans, SemanticAttributes.DB_STATEMENT)
130 | .map((statement) => {
131 | if (statement.length > MAX_LEN) {
132 | return `${statement.slice(0, MAX_LEN)}...`;
133 | }
134 | return statement;
135 | })
136 | .join('\n');
137 | };
138 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/resources/redis-command.ts:
--------------------------------------------------------------------------------
1 | import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
2 | import { opentelemetry } from '@traceloop/otel-proto';
3 | import {
4 | CompareOptions,
5 | filterByAttributeStringValue,
6 | } from '../matchers/utils';
7 |
8 | export class RedisCommand {
9 | constructor(
10 | readonly spans: opentelemetry.proto.trace.v1.ISpan[],
11 | private readonly serviceName: string,
12 | private readonly times = 1,
13 | ) {}
14 |
15 | withDatabaseName(name: string | RegExp, options: CompareOptions) {
16 | const filteredSpans = filterByAttributeStringValue(
17 | this.spans,
18 | SemanticAttributes.DB_NAME,
19 | name,
20 | options,
21 | );
22 |
23 | if (filteredSpans.length < this.times) {
24 | throw new Error(
25 | `Expected ${this.times} queries by ${this.serviceName} to redis with database name ${name}, but found ${filteredSpans.length}.`,
26 | );
27 | }
28 |
29 | return new RedisCommand(filteredSpans, this.serviceName);
30 | }
31 |
32 | withStatement(statement: string | RegExp, options: CompareOptions) {
33 | const filteredSpans = filterByAttributeStringValue(
34 | this.spans,
35 | SemanticAttributes.DB_STATEMENT,
36 | statement,
37 | options,
38 | );
39 |
40 | if (filteredSpans.length < this.times) {
41 | throw new Error(
42 | `Expected ${this.times} queries by ${this.serviceName} to redis with statement ${statement}, but found ${filteredSpans.length}.`,
43 | );
44 | }
45 |
46 | return new RedisCommand(filteredSpans, this.serviceName);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/resources/service.ts:
--------------------------------------------------------------------------------
1 | import { opentelemetry } from '@traceloop/otel-proto';
2 |
3 | export class Service {
4 | constructor(
5 | public readonly name: string,
6 | public readonly spans: opentelemetry.proto.trace.v1.ISpan[],
7 | ) {}
8 | }
9 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/trace-loop/fetch-traces.ts:
--------------------------------------------------------------------------------
1 | import { opentelemetry } from '@traceloop/otel-proto';
2 | import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
3 | import { setTimeout } from 'timers/promises';
4 | import { httpGetBinary } from '../utils';
5 |
6 | export interface FetchTracesConfig {
7 | maxPollTime: number;
8 | pollInterval: number;
9 | awaitAllSpansInTraceTimeout: number;
10 | url: string;
11 | apiKey: string;
12 | }
13 |
14 | export const fetchTracesConfigBase: FetchTracesConfig = {
15 | maxPollTime: 9000,
16 | pollInterval: 1000,
17 | awaitAllSpansInTraceTimeout: 2000,
18 | url: process.env.TRACELOOP_URL || 'https://api.traceloop.dev/v1/traces',
19 | apiKey: process.env.TRACELOOP_API_KEY || 'none',
20 | };
21 |
22 | /**
23 | * Searches in the traces for a trace with the given traceLoopId contained in the attribute http.user_agent
24 | *
25 | * @param traces all traces from the otel receiver
26 | * @param traceLoopId traceLoopId to search for
27 | * @returns traceId of the span with the given traceLoopId or undefined if no match was found
28 | */
29 | export const findTraceLoopIdMatch = (
30 | traces: opentelemetry.proto.trace.v1.TracesData,
31 | traceLoopId: string,
32 | ): string | undefined => {
33 | for (const resourceSpan of traces.resourceSpans) {
34 | for (const scopeSpan of resourceSpan.scopeSpans || []) {
35 | for (const span of scopeSpan.spans || []) {
36 | if (span.attributes) {
37 | for (const attribute of span.attributes) {
38 | if (attribute.key === SemanticAttributes.HTTP_USER_AGENT) {
39 | const matches =
40 | attribute.value?.stringValue?.match(/traceloop_id=(.*)/);
41 | if (matches?.length > 1) {
42 | if (matches[1] === traceLoopId) {
43 | return span.traceId
44 | ? Buffer.from(span.traceId).toString('hex')
45 | : undefined;
46 | }
47 | }
48 | }
49 | }
50 | }
51 | }
52 | }
53 | }
54 | };
55 |
56 | export const pollForTraceLoopIdMatch = async (
57 | config: FetchTracesConfig,
58 | traceLoopId: string,
59 | ): Promise => {
60 | let numOfPolls = Math.floor(config.maxPollTime / config.pollInterval);
61 | let foundMatch = false;
62 |
63 | while (!foundMatch && numOfPolls-- > 0) {
64 | await setTimeout(config.pollInterval);
65 | try {
66 | const response = await httpGetBinary(config, traceLoopId);
67 | const traces = opentelemetry.proto.trace.v1.TracesData.decode(response);
68 |
69 | const traceId = findTraceLoopIdMatch(traces, traceLoopId);
70 | if (traceId) {
71 | foundMatch = true;
72 | return traceId;
73 | }
74 | } catch (e) {
75 | // retry on 400, else throw
76 | if ((e as Error)?.message !== '400') {
77 | throw e;
78 | }
79 | }
80 | }
81 |
82 | return undefined;
83 | };
84 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/trace-loop/filter-service-spans.ts:
--------------------------------------------------------------------------------
1 | import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
2 | import { opentelemetry } from '@traceloop/otel-proto';
3 | import { stringCompare, CompareOptions } from '../matchers/utils';
4 |
5 | const flatten = (
6 | serviceResourceSpans:
7 | | opentelemetry.proto.trace.v1.IResourceSpans[]
8 | | undefined,
9 | ) =>
10 | (
11 | serviceResourceSpans?.flatMap((srs) => {
12 | return srs.scopeSpans?.flatMap((ss) => ss.spans || []);
13 | }) || []
14 | ).filter((s) => !!s) as opentelemetry.proto.trace.v1.ISpan[];
15 |
16 | const filterByTraceId = (
17 | spans: opentelemetry.proto.trace.v1.ISpan[] | undefined = [],
18 | traceId: string | undefined,
19 | ) => {
20 | if (!traceId) {
21 | return spans;
22 | }
23 |
24 | return (
25 | spans.filter(
26 | (span) =>
27 | span.traceId && Buffer.from(span.traceId).toString('hex') === traceId,
28 | ) || []
29 | );
30 | };
31 |
32 | export const byCustomAttribute = (
33 | attName: string,
34 | attValue: string | RegExp,
35 | traceData: opentelemetry.proto.trace.v1.TracesData | undefined,
36 | traceId: string | undefined,
37 | options?: CompareOptions,
38 | ) => {
39 | const serviceResourceSpans = traceData?.resourceSpans?.filter((rs) =>
40 | rs.resource?.attributes?.find(
41 | (a) =>
42 | a.key === attName &&
43 | stringCompare(a.value?.stringValue, attValue, options),
44 | ),
45 | );
46 |
47 | return filterByTraceId(flatten(serviceResourceSpans), traceId);
48 | };
49 |
50 | export const byServiceName = (
51 | name: string,
52 | traceData: opentelemetry.proto.trace.v1.TracesData | undefined,
53 | traceId: string | undefined,
54 | options?: CompareOptions,
55 | ) =>
56 | byCustomAttribute(
57 | SemanticResourceAttributes.SERVICE_NAME,
58 | name,
59 | traceData,
60 | traceId,
61 | options,
62 | );
63 |
64 | export const byK8sPodName = (
65 | name: string,
66 | traceData: opentelemetry.proto.trace.v1.TracesData | undefined,
67 | traceId: string | undefined,
68 | options?: CompareOptions,
69 | ) =>
70 | byCustomAttribute(
71 | SemanticResourceAttributes.K8S_POD_NAME,
72 | name,
73 | traceData,
74 | traceId,
75 | options,
76 | );
77 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/trace-loop/index.ts:
--------------------------------------------------------------------------------
1 | import { randomUUID } from 'crypto';
2 | import axios from 'axios';
3 | import { opentelemetry } from '@traceloop/otel-proto';
4 | import { httpGetBinary } from '../utils';
5 | import { setTimeout } from 'timers/promises';
6 | import {
7 | fetchTracesConfigBase,
8 | FetchTracesConfig,
9 | pollForTraceLoopIdMatch,
10 | } from './fetch-traces';
11 | import { Service } from '../resources/service';
12 | import { CompareOptions } from '../matchers/utils/compare-types';
13 | import {
14 | byK8sPodName,
15 | byServiceName,
16 | byCustomAttribute,
17 | } from './filter-service-spans';
18 |
19 | export class TraceLoop {
20 | private readonly _traceLoopId: string;
21 | private _fetchedTrace = false;
22 | private _traceId: string | undefined;
23 | private _traceData: opentelemetry.proto.trace.v1.TracesData | undefined;
24 |
25 | constructor() {
26 | this._traceLoopId = randomUUID();
27 | }
28 |
29 | get traceLoopId() {
30 | return this._traceLoopId;
31 | }
32 |
33 | get axiosInstance() {
34 | return axios.create({
35 | headers: { 'User-Agent': `traceloop_id=${this._traceLoopId}` },
36 | });
37 | }
38 |
39 | /**
40 | * Fetches all traces for the current trace loop id from the otel receiver
41 | * @param config
42 | */
43 | public async fetchTraces(
44 | inputConfig: Partial = {},
45 | ): Promise {
46 | const config = { ...fetchTracesConfigBase, ...inputConfig };
47 | if (this._fetchedTrace) {
48 | return;
49 | }
50 |
51 | this._traceId = await pollForTraceLoopIdMatch(config, this._traceLoopId);
52 |
53 | // allow time for all spans for the current trace to be received
54 | await setTimeout(config.awaitAllSpansInTraceTimeout);
55 |
56 | const response = await httpGetBinary(config, this._traceLoopId);
57 | this._traceData = opentelemetry.proto.trace.v1.TracesData.decode(response);
58 | this._fetchedTrace = true;
59 | }
60 |
61 | public serviceByName(name: string, options?: CompareOptions) {
62 | return new Service(
63 | name,
64 | byServiceName(name, this._traceData, this._traceId, options),
65 | );
66 | }
67 |
68 | public serviceByK8sPodName(name: string, options?: CompareOptions) {
69 | return new Service(
70 | name,
71 | byK8sPodName(name, this._traceData, this._traceId, options),
72 | );
73 | }
74 |
75 | public serviceByCustomAttribute(
76 | key: string,
77 | value: string | RegExp,
78 | options?: CompareOptions,
79 | ) {
80 | return new Service(
81 | value as string,
82 | byCustomAttribute(key, value, this._traceData, this._traceId, options),
83 | );
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/packages/expect-opentelemetry/src/utils.ts:
--------------------------------------------------------------------------------
1 | import http from 'http';
2 | import https from 'https';
3 | import { opentelemetry } from '@traceloop/otel-proto';
4 | import { FetchTracesConfig } from './trace-loop/fetch-traces';
5 |
6 | export const getInstanceType = (instance: any) => {
7 | if (
8 | instance?.constructor?.name &&
9 | ['Span', 'Service'].includes(instance.constructor.name)
10 | ) {
11 | return instance.constructor.name;
12 | }
13 |
14 | return null;
15 | };
16 |
17 | export const enhanceError = (error: any, message: string) => {
18 | error.message = `${message}\n${error.message}`;
19 | return error;
20 | };
21 |
22 | export function parseServerResponse(
23 | data: any,
24 | ): opentelemetry.proto.trace.v1.TracesData {
25 | const tracesBinary = Buffer.from(data.traces, 'base64');
26 |
27 | return opentelemetry.proto.trace.v1.TracesData.decode(tracesBinary);
28 | }
29 |
30 | export const generateStubData = () => {
31 | const tracesData = opentelemetry.proto.trace.v1.TracesData.create();
32 |
33 | tracesData.resourceSpans.push(
34 | opentelemetry.proto.trace.v1.ResourceSpans.create({
35 | resource: opentelemetry.proto.resource.v1.Resource.create({
36 | attributes: [
37 | opentelemetry.proto.common.v1.KeyValue.create({
38 | key: 'service.name',
39 | value: opentelemetry.proto.common.v1.AnyValue.create({
40 | stringValue: 'orders-service',
41 | }),
42 | }),
43 | ],
44 | }),
45 | scopeSpans: [
46 | opentelemetry.proto.trace.v1.ScopeSpans.create({
47 | spans: [
48 | opentelemetry.proto.trace.v1.Span.create({
49 | traceId: Uint8Array.from(Buffer.from('AAAAAAAAAAAAAAAA', 'hex')),
50 | spanId: Uint8Array.from(Buffer.from('BBBBBBBBBBBBBBBB', 'hex')),
51 | name: 'orders-service',
52 | kind: opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_CLIENT,
53 | startTimeUnixNano: 1630000000000000000,
54 | endTimeUnixNano: 1630000000000000000,
55 | attributes: [
56 | opentelemetry.proto.common.v1.KeyValue.create({
57 | key: 'http.method',
58 | value: opentelemetry.proto.common.v1.AnyValue.create({
59 | stringValue: 'GET',
60 | }),
61 | }),
62 | opentelemetry.proto.common.v1.KeyValue.create({
63 | key: 'http.url',
64 | value: opentelemetry.proto.common.v1.AnyValue.create({
65 | stringValue: 'http://localhost:3000/orders',
66 | }),
67 | }),
68 | ],
69 | }),
70 | ],
71 | }),
72 | ],
73 | }),
74 | );
75 |
76 | return opentelemetry.proto.trace.v1.TracesData.encode(tracesData).finish();
77 | };
78 |
79 | /**
80 | * Promise wrapper for http.get
81 | * @see https://github.com/protobufjs/protobuf.js/wiki/How-to-read-binary-data-in-the-browser-or-under-node.js%3F
82 | *
83 | * @param url - url to make get request to (server that responds with Buffer)
84 | * @returns Buffer result
85 | */
86 | export function httpGetBinary(
87 | config: FetchTracesConfig,
88 | traceloopId: string,
89 | ): Promise {
90 | const url = `${config.url}/${traceloopId}`;
91 | return new Promise((resolve, reject) => {
92 | const method = config.url.includes('https') ? https : http;
93 | method.get(url, { headers: { Authorization: config.apiKey } }, (res) => {
94 | const { statusCode } = res;
95 |
96 | if (!statusCode || statusCode < 200 || statusCode >= 300) {
97 | return reject(new Error(`${res.statusCode}`));
98 | }
99 |
100 | const data: Uint8Array[] = [];
101 | res.on('data', function (chunk) {
102 | data.push(chunk as never);
103 | });
104 | res.on('end', function () {
105 | const result = Buffer.concat(data);
106 | resolve(result);
107 | });
108 | res.on('error', function () {
109 | reject('Error while fetching data');
110 | });
111 | });
112 | });
113 | }
114 |
--------------------------------------------------------------------------------
/packages/instrument-opentelemetry/.dockerignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
--------------------------------------------------------------------------------
/packages/instrument-opentelemetry/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/traceloop/jest-opentelemetry/compare/v0.7.0...v0.8.0) (2023-06-20)
7 |
8 | **Note:** Version bump only for package @traceloop/instrument-opentelemetry
9 |
10 | # [0.7.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.6.0...v0.7.0) (2023-06-13)
11 |
12 | **Note:** Version bump only for package @traceloop/instrument-opentelemetry
13 |
14 | # [0.6.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.5.0...v0.6.0) (2023-06-04)
15 |
16 | **Note:** Version bump only for package @traceloop/instrument-opentelemetry
17 |
18 | # [0.5.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.4.1...v0.5.0) (2023-03-17)
19 |
20 | ### Features
21 |
22 | - go server backend ([#42](https://github.com/traceloop/jest-opentelemetry/issues/42)) ([a543845](https://github.com/traceloop/jest-opentelemetry/commit/a543845445617bd321c7cee793e23caf2c651844))
23 |
24 | ## [0.4.1](https://github.com/traceloop/jest-opentelemetry/compare/v0.4.0...v0.4.1) (2023-02-23)
25 |
26 | ### Bug Fixes
27 |
28 | - publish on public packages ([7ecb00a](https://github.com/traceloop/jest-opentelemetry/commit/7ecb00aff9b376195c3b04a2f65dcd26321ba186))
29 | - tracing enabled only if OTEL_EXPORTER_OTLP_ENDPOINT is specified ([80f74ce](https://github.com/traceloop/jest-opentelemetry/commit/80f74ce6c06ea1b6740faa5cf8499ea70d284cdf))
30 |
31 | # 0.4.0 (2023-02-22)
32 |
33 | ### Features
34 |
35 | - **database-matchers:** support postgres matchers ([#24](https://github.com/traceloop/jest-opentelemetry/issues/24)) ([8b86a4b](https://github.com/traceloop/jest-opentelemetry/commit/8b86a4b7c926498c00a3eaa3da326d45eeda8d77))
36 | - k8s init-container ([c05bda4](https://github.com/traceloop/jest-opentelemetry/commit/c05bda437b9b11e5097e482f6a7885e58789cc5b))
37 | - separate otel instrumentation into a new package ([dfe7586](https://github.com/traceloop/jest-opentelemetry/commit/dfe758613c5e17c4e92144023c82d91526aca786))
38 |
--------------------------------------------------------------------------------
/packages/instrument-opentelemetry/Dockerfile:
--------------------------------------------------------------------------------
1 | ## Following instructions found in:
2 | ## https://github.com/open-telemetry/opentelemetry-operator/blob/main/autoinstrumentation/nodejs/Dockerfile
3 |
4 | FROM node:16 AS build
5 |
6 | WORKDIR /operator-build
7 |
8 | COPY package*.json ./
9 | COPY packages/instrument-opentelemetry/package*.json ./packages/instrument-opentelemetry/
10 |
11 | RUN npm install
12 |
13 | COPY lerna.json ./
14 | COPY tsconfig.json ./
15 | COPY packages/instrument-opentelemetry/. ./packages/instrument-opentelemetry/
16 |
17 | RUN npm run build
18 |
19 | FROM busybox AS prod
20 |
21 | COPY --from=build /operator-build/node_modules /autoinstrumentation/node_modules
22 | COPY --from=build /operator-build/packages/instrument-opentelemetry/dist/tracing.js /autoinstrumentation/autoinstrumentation.js
23 |
24 | RUN chmod -R go+r /autoinstrumentation
--------------------------------------------------------------------------------
/packages/instrument-opentelemetry/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@traceloop/instrument-opentelemetry",
3 | "version": "0.8.0",
4 | "main": "./dist/tracing.js",
5 | "engines": {
6 | "node": ">=14.0.0"
7 | },
8 | "publishConfig": {
9 | "access": "public"
10 | },
11 | "scripts": {
12 | "prebuild": "rm -rf dist",
13 | "build": "rollup -c"
14 | },
15 | "devDependencies": {
16 | "@types/express": "^4.17.17",
17 | "rollup": "^3.20.0",
18 | "rollup-plugin-swc3": "^0.8.0"
19 | },
20 | "dependencies": {
21 | "@opentelemetry/api": "^1.4.1",
22 | "@opentelemetry/auto-instrumentations-node": "^0.36.4",
23 | "@opentelemetry/exporter-trace-otlp-grpc": "0.35.1",
24 | "@opentelemetry/exporter-trace-otlp-proto": "^0.36.1",
25 | "@opentelemetry/instrumentation-http": "^0.35.1",
26 | "@opentelemetry/resource-detector-container": "^0.2.2",
27 | "@opentelemetry/sdk-node": "^0.35.1",
28 | "@opentelemetry/semantic-conventions": "^1.10.1",
29 | "@opentelemetry/tracing": "^0.24.0",
30 | "opentelemetry-instrumentation-express": "^0.34.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/instrument-opentelemetry/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import { swc, defineRollupSwcOption } from 'rollup-plugin-swc3';
2 | import typescript from '@rollup/plugin-typescript';
3 |
4 | const bundle = (config) => ({
5 | external: (id) => {
6 | return !/^[./]/.test(id);
7 | },
8 | ...config,
9 | });
10 |
11 | const swcPlugin = swc(
12 | defineRollupSwcOption({
13 | jsc: { target: 'es2021' },
14 | }),
15 | );
16 |
17 | export default [
18 | bundle({
19 | input: ['src/tracing.ts'],
20 | output: {
21 | file: 'dist/tracing.js',
22 | format: 'cjs',
23 | },
24 | plugins: [swcPlugin, typescript({ tsconfig: '../../tsconfig.json' })],
25 | }),
26 | ];
27 |
--------------------------------------------------------------------------------
/packages/instrument-opentelemetry/src/otel-custom/constants.js:
--------------------------------------------------------------------------------
1 | export const HttpExtendedAttribute = {
2 | HTTP_REQUEST_HEADERS: 'http.request.headers',
3 | HTTP_REQUEST_BODY: 'http.request.body',
4 | HTTP_RESPONSE_HEADERS: 'http.response.headers',
5 | HTTP_RESPONSE_BODY: 'http.response.body',
6 | HTTP_PATH: 'http.path',
7 | };
8 |
--------------------------------------------------------------------------------
/packages/instrument-opentelemetry/src/otel-custom/express.js:
--------------------------------------------------------------------------------
1 | import { HttpExtendedAttribute } from './constants';
2 | import { shouldCaptureBodyByMimeType } from './mime-type';
3 | import { StreamChunks } from './stream-chunks';
4 |
5 | const requestHook = (span, req, res) => {
6 | span.setAttributes({
7 | [HttpExtendedAttribute.HTTP_PATH]: req.path,
8 | [HttpExtendedAttribute.HTTP_REQUEST_HEADERS]: JSON.stringify(req.headers),
9 | });
10 |
11 | const requestMimeType = req.get('content-type');
12 | const captureRequestBody = shouldCaptureBodyByMimeType(requestMimeType);
13 | const requestStreamChunks = new StreamChunks();
14 |
15 | if (captureRequestBody) {
16 | req.on('data', (chunk) => requestStreamChunks.addChunk(chunk));
17 | }
18 |
19 | const responseStreamChunks = new StreamChunks();
20 |
21 | const originalResWrite = res.write;
22 |
23 | res.write = function (chunk) {
24 | responseStreamChunks.addChunk(chunk);
25 | originalResWrite.apply(res, arguments);
26 | };
27 |
28 | const oldResEnd = res.end;
29 | res.end = function (chunk) {
30 | oldResEnd.apply(res, arguments);
31 |
32 | const responseMimeType = res.get('content-type');
33 | const captureResponseBody = shouldCaptureBodyByMimeType(responseMimeType);
34 | if (captureResponseBody) responseStreamChunks.addChunk(chunk);
35 |
36 | span.setAttributes({
37 | [HttpExtendedAttribute.HTTP_REQUEST_BODY]: captureRequestBody
38 | ? requestStreamChunks.getBody()
39 | : `Request body not collected due to unsupported mime type: ${requestMimeType}`,
40 | [HttpExtendedAttribute.HTTP_RESPONSE_BODY]: captureResponseBody
41 | ? responseStreamChunks.getBody()
42 | : `Response body not collected due to unsupported mime type: ${responseMimeType}`,
43 | });
44 |
45 | span.setAttributes({
46 | [HttpExtendedAttribute.HTTP_RESPONSE_HEADERS]: JSON.stringify(
47 | res.getHeaders(),
48 | ),
49 | });
50 | };
51 | };
52 |
53 | export const expressInstrumentationConfig = {
54 | requestHook,
55 | };
56 |
--------------------------------------------------------------------------------
/packages/instrument-opentelemetry/src/otel-custom/http.js:
--------------------------------------------------------------------------------
1 | import { IncomingMessage, ClientRequest } from 'http';
2 | import { HttpExtendedAttribute } from './constants';
3 | import { shouldCaptureBodyByMimeType } from './mime-type';
4 | import { StreamChunks } from './stream-chunks';
5 |
6 | const streamChunksKey = Symbol(
7 | 'opentelemetry.instrumentation.http.StreamChunks',
8 | );
9 |
10 | const httpCustomAttributes = (span, request, response) => {
11 | if (request instanceof ClientRequest) {
12 | const reqPath = request.path.split('?')[0];
13 | span.setAttribute(HttpExtendedAttribute.HTTP_PATH, reqPath);
14 | span.setAttribute(
15 | HttpExtendedAttribute.HTTP_REQUEST_HEADERS,
16 | JSON.stringify(request.getHeaders()),
17 | );
18 | }
19 | if (response instanceof IncomingMessage) {
20 | span.setAttribute(
21 | HttpExtendedAttribute.HTTP_RESPONSE_HEADERS,
22 | JSON.stringify(response.headers),
23 | );
24 | }
25 |
26 | const requestBody = request[streamChunksKey];
27 | if (requestBody) {
28 | span.setAttribute(
29 | HttpExtendedAttribute.HTTP_REQUEST_BODY,
30 | requestBody.getBody(),
31 | );
32 | }
33 |
34 | const responseBody = response[streamChunksKey];
35 | if (responseBody) {
36 | span.setAttribute(
37 | HttpExtendedAttribute.HTTP_RESPONSE_BODY,
38 | responseBody.getBody(),
39 | );
40 | }
41 | };
42 |
43 | const httpCustomAttributesOnRequest = (span, request) => {
44 | if (request instanceof ClientRequest) {
45 | const requestMimeType = request.getHeader('content-type');
46 | if (!shouldCaptureBodyByMimeType(requestMimeType)) {
47 | span.setAttribute(
48 | HttpExtendedAttribute.HTTP_REQUEST_BODY,
49 | `Request body not collected due to unsupported mime type: ${requestMimeType}`,
50 | );
51 | return;
52 | }
53 |
54 | let oldWrite = request.write;
55 | request[streamChunksKey] = new StreamChunks();
56 | request.write = function (data) {
57 | const expectDevData = request[streamChunksKey];
58 | expectDevData?.addChunk(data);
59 | return oldWrite.call(request, data);
60 | };
61 | }
62 | };
63 |
64 | const httpCustomAttributesOnResponse = (span, response) => {
65 | if (response instanceof IncomingMessage) {
66 | const responseMimeType = response.headers?.['content-type'];
67 | if (!shouldCaptureBodyByMimeType(responseMimeType)) {
68 | span.setAttribute(
69 | HttpExtendedAttribute.HTTP_RESPONSE_BODY,
70 | `Response body not collected due to unsupported mime type: ${responseMimeType}`,
71 | );
72 | return;
73 | }
74 |
75 | response[streamChunksKey] = new StreamChunks();
76 | const origPush = response.push;
77 | response.push = function (chunk) {
78 | if (chunk) {
79 | const expectDevData = response[streamChunksKey];
80 | expectDevData?.addChunk(chunk);
81 | }
82 | return origPush.apply(this, arguments);
83 | };
84 | }
85 | };
86 |
87 | export const httpInstrumentationConfig = {
88 | applyCustomAttributesOnSpan: httpCustomAttributes,
89 | requestHook: httpCustomAttributesOnRequest,
90 | responseHook: httpCustomAttributesOnResponse,
91 | headersToSpanAttributes: {
92 | client: {
93 | requestHeaders: ['traceloop_id'],
94 | responseHeaders: ['traceloop_id'],
95 | },
96 | server: {
97 | requestHeaders: ['traceloop_id'],
98 | responseHeaders: ['traceloop_id'],
99 | },
100 | },
101 | };
102 |
--------------------------------------------------------------------------------
/packages/instrument-opentelemetry/src/otel-custom/mime-type.js:
--------------------------------------------------------------------------------
1 | const allowedMimeTypePrefix = [
2 | 'text',
3 | 'multipart/form-data',
4 | 'application/json',
5 | 'application/ld+json',
6 | 'application/rtf',
7 | 'application/x-www-form-urlencoded',
8 | 'application/xml',
9 | 'application/xhtml',
10 | ];
11 |
12 | export const shouldCaptureBodyByMimeType = (mimeType) => {
13 | try {
14 | return (
15 | !mimeType ||
16 | allowedMimeTypePrefix.some((prefix) => mimeType.startsWith(prefix))
17 | );
18 | } catch {
19 | return true;
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/packages/instrument-opentelemetry/src/otel-custom/stream-chunks.js:
--------------------------------------------------------------------------------
1 | // for body with at most this length, full body will be captured.
2 | // for large body with more than this amount of bytes, we will
3 | // collect at least this amount of bytes, but might truncate after it
4 | export const MIN_COLLECTED_BODY_LENGTH = 524288;
5 |
6 | export class StreamChunks {
7 | chunks;
8 | length;
9 |
10 | constructor() {
11 | this.chunks = [];
12 | this.length = 0;
13 | }
14 |
15 | addChunk(chunk) {
16 | if (this.length >= MIN_COLLECTED_BODY_LENGTH) return;
17 |
18 | const chunkLength = chunk?.length;
19 | if (!chunkLength) return;
20 |
21 | this.chunks.push(chunk);
22 | this.length += chunkLength;
23 | }
24 |
25 | getBody() {
26 | return this.chunks.join('');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/instrument-opentelemetry/src/tracing.ts:
--------------------------------------------------------------------------------
1 | import opentelemetry from '@opentelemetry/sdk-node';
2 | import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
3 | import { Resource } from '@opentelemetry/resources';
4 | import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
5 | import { httpInstrumentationConfig } from './otel-custom/http';
6 | import { expressInstrumentationConfig } from './otel-custom/express';
7 | import { OTLPTraceExporter as ProtoExporter } from '@opentelemetry/exporter-trace-otlp-proto';
8 | import { OTLPTraceExporter as GRPCExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
9 | import { containerDetector } from '@opentelemetry/resource-detector-container';
10 |
11 | if (process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
12 | const traceExporter =
13 | process.env.OTEL_EXPORTER_TYPE === 'PROTO'
14 | ? new ProtoExporter({
15 | url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
16 | timeoutMillis: 100,
17 | })
18 | : new GRPCExporter({
19 | url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
20 | timeoutMillis: 100,
21 | });
22 |
23 | const sdk = new opentelemetry.NodeSDK({
24 | resource: new Resource(
25 | process.env.SERVICE_NAME
26 | ? {
27 | [SemanticResourceAttributes.SERVICE_NAME]: process.env.SERVICE_NAME,
28 | }
29 | : {},
30 | ),
31 | traceExporter,
32 | instrumentations: [
33 | getNodeAutoInstrumentations({
34 | '@opentelemetry/instrumentation-http': httpInstrumentationConfig,
35 | '@opentelemetry/instrumentation-express': expressInstrumentationConfig,
36 | '@opentelemetry/instrumentation-pg': {
37 | enhancedDatabaseReporting: true,
38 | },
39 | }),
40 | ],
41 | resourceDetectors: [containerDetector],
42 | });
43 |
44 | // initialize the SDK and register with the OpenTelemetry API
45 | // this enables the API to record telemetry
46 | sdk.start();
47 |
48 | // gracefully shut down the SDK on process exit
49 | process.on('SIGTERM', () => {
50 | sdk
51 | .shutdown()
52 | .then(() => console.log('Tracing terminated'))
53 | .catch((error) => console.log('Error terminating tracing', error))
54 | .finally(() => process.exit(0));
55 | });
56 | }
57 |
--------------------------------------------------------------------------------
/packages/jest-environment-otel/.npmignore:
--------------------------------------------------------------------------------
1 | /*
2 | !/dist/**/*.js
3 | !index.js
4 | !setup.js
5 | !teardown.js
6 |
--------------------------------------------------------------------------------
/packages/jest-environment-otel/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/traceloop/jest-opentelemetry/compare/v0.7.0...v0.8.0) (2023-06-20)
7 |
8 | **Note:** Version bump only for package @traceloop/jest-environment-otel
9 |
10 | # [0.7.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.6.0...v0.7.0) (2023-06-13)
11 |
12 | **Note:** Version bump only for package @traceloop/jest-environment-otel
13 |
14 | # [0.6.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.5.0...v0.6.0) (2023-06-04)
15 |
16 | ### Bug Fixes
17 |
18 | - work with remote server ([#69](https://github.com/traceloop/jest-opentelemetry/issues/69)) ([d219a40](https://github.com/traceloop/jest-opentelemetry/commit/d219a402ea52b7cc7aa4c8f31a4e9071e016a9cf))
19 |
20 | # [0.5.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.4.1...v0.5.0) (2023-03-17)
21 |
22 | ### Features
23 |
24 | - go server backend ([#42](https://github.com/traceloop/jest-opentelemetry/issues/42)) ([a543845](https://github.com/traceloop/jest-opentelemetry/commit/a543845445617bd321c7cee793e23caf2c651844))
25 |
26 | # 0.4.0 (2023-02-22)
27 |
28 | ### Bug Fixes
29 |
30 | - adaptations for minimal working js project ([57c94a4](https://github.com/traceloop/jest-opentelemetry/commit/57c94a448045ac00a1c01788a8439489a2ca6fe2))
31 | - adaptations for usage with minimal js project ([7a7771e](https://github.com/traceloop/jest-opentelemetry/commit/7a7771e165b8924778e82758504ee38b1cdd93bc))
32 | - compilation & tests ([4e8698f](https://github.com/traceloop/jest-opentelemetry/commit/4e8698f254bad209becf2bd260679831fb25c0a6))
33 | - created a separate package for protobuf code ([b445f6f](https://github.com/traceloop/jest-opentelemetry/commit/b445f6fb7a32989b44253d813c4f92ad5dfffd2f))
34 | - imports from dist ([#16](https://github.com/traceloop/jest-opentelemetry/issues/16)) ([230849d](https://github.com/traceloop/jest-opentelemetry/commit/230849d617887d53b85a012cd1878dbdfca19e7b))
35 | - **integration:** integration test dsl with test servers ([#4](https://github.com/traceloop/jest-opentelemetry/issues/4)) ([6b4b7f7](https://github.com/traceloop/jest-opentelemetry/commit/6b4b7f7c43f7f450b615bf46635a817df708517e))
36 | - **lint:** linting issues ([#11](https://github.com/traceloop/jest-opentelemetry/issues/11)) ([c74ca77](https://github.com/traceloop/jest-opentelemetry/commit/c74ca77896c35a8ff72500451d41cc0c53d1c8b1))
37 | - npm publish files ([02afb0b](https://github.com/traceloop/jest-opentelemetry/commit/02afb0b6f6054d3570d2160d667f05033243401d))
38 | - otel receiver timeouts ([#17](https://github.com/traceloop/jest-opentelemetry/issues/17)) ([8c99815](https://github.com/traceloop/jest-opentelemetry/commit/8c998151a7a30c1a401aa1403587f35026d4814b))
39 | - separate expect() function ([e704f43](https://github.com/traceloop/jest-opentelemetry/commit/e704f43b5c5877c254c901825c446eeaea2f1258))
40 | - timeout on test setup ([2b3fc15](https://github.com/traceloop/jest-opentelemetry/commit/2b3fc15b2cfae8e46471c3d11531bff40bf7c834))
41 | - typescript adaptations ([ec0ad8e](https://github.com/traceloop/jest-opentelemetry/commit/ec0ad8e40c799f70881ae7ac3b441c7f6eb6c9cd))
42 | - wrong export for setup/teardown of jest ([3979c3f](https://github.com/traceloop/jest-opentelemetry/commit/3979c3f01a0286175c2f1a52bd117b1fee9b6d76))
43 |
44 | ### Features
45 |
46 | - basic integration with otel-receiver ([10f4a4e](https://github.com/traceloop/jest-opentelemetry/commit/10f4a4e274079620f8461eb64b09c73e9a609077))
47 | - TraceLoop ([#19](https://github.com/traceloop/jest-opentelemetry/issues/19)) ([ab17a91](https://github.com/traceloop/jest-opentelemetry/commit/ab17a91601ff8643ed99a6e2adaced491ab0743d))
48 |
49 | # [0.3.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.7...v0.3.0) (2023-02-21)
50 |
51 | **Note:** Version bump only for package @traceloop/jest-environment-otel
52 |
53 | ## [0.2.7](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.6...v0.2.7) (2023-02-21)
54 |
55 | **Note:** Version bump only for package @traceloop/jest-environment-otel
56 |
57 | ## [0.2.6](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.5...v0.2.6) (2023-02-21)
58 |
59 | ### Bug Fixes
60 |
61 | - created a separate package for protobuf code ([b445f6f](https://github.com/traceloop/jest-opentelemetry/commit/b445f6fb7a32989b44253d813c4f92ad5dfffd2f))
62 |
63 | ## [0.2.5](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.4...v0.2.5) (2023-02-21)
64 |
65 | **Note:** Version bump only for package @traceloop/jest-environment-otel
66 |
67 | ## [0.2.4](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.3...v0.2.4) (2023-02-21)
68 |
69 | ### Bug Fixes
70 |
71 | - separate expect() function ([e704f43](https://github.com/traceloop/jest-opentelemetry/commit/e704f43b5c5877c254c901825c446eeaea2f1258))
72 |
73 | ## [0.2.3](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.2...v0.2.3) (2023-02-21)
74 |
75 | ### Bug Fixes
76 |
77 | - wrong export for setup/teardown of jest ([3979c3f](https://github.com/traceloop/jest-opentelemetry/commit/3979c3f01a0286175c2f1a52bd117b1fee9b6d76))
78 |
79 | ## [0.2.2](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.1...v0.2.2) (2023-02-21)
80 |
81 | ### Bug Fixes
82 |
83 | - adaptations for usage with minimal js project ([7a7771e](https://github.com/traceloop/jest-opentelemetry/commit/7a7771e165b8924778e82758504ee38b1cdd93bc))
84 |
85 | ## [0.2.1](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.0...v0.2.1) (2023-02-21)
86 |
87 | ### Bug Fixes
88 |
89 | - adaptations for minimal working js project ([57c94a4](https://github.com/traceloop/jest-opentelemetry/commit/57c94a448045ac00a1c01788a8439489a2ca6fe2))
90 |
91 | # [0.2.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.8...v0.2.0) (2023-02-21)
92 |
93 | ### Features
94 |
95 | - TraceLoop ([#19](https://github.com/traceloop/jest-opentelemetry/issues/19)) ([ab17a91](https://github.com/traceloop/jest-opentelemetry/commit/ab17a91601ff8643ed99a6e2adaced491ab0743d))
96 |
97 | ## [0.1.6](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.5...v0.1.6) (2023-02-21)
98 |
99 | ### Bug Fixes
100 |
101 | - npm publish files ([02afb0b](https://github.com/traceloop/jest-opentelemetry/commit/02afb0b6f6054d3570d2160d667f05033243401d))
102 |
103 | ## [0.1.2](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.1...v0.1.2) (2023-02-21)
104 |
105 | **Note:** Version bump only for package @traceloop/jest-environment-otel
106 |
107 | ## [0.1.1](https://github.com/expect-dev/jest-opentelemetry/compare/v0.1.0...v0.1.1) (2023-02-21)
108 |
109 | **Note:** Version bump only for package @traceloop/jest-environment-otel
110 |
111 | # 0.1.0 (2023-02-21)
112 |
113 | ### Bug Fixes
114 |
115 | - compilation & tests ([4e8698f](https://github.com/expect-dev/jest-opentelemetry/commit/4e8698f254bad209becf2bd260679831fb25c0a6))
116 | - imports from dist ([#16](https://github.com/expect-dev/jest-opentelemetry/issues/16)) ([230849d](https://github.com/expect-dev/jest-opentelemetry/commit/230849d617887d53b85a012cd1878dbdfca19e7b))
117 | - **integration:** integration test dsl with test servers ([#4](https://github.com/expect-dev/jest-opentelemetry/issues/4)) ([6b4b7f7](https://github.com/expect-dev/jest-opentelemetry/commit/6b4b7f7c43f7f450b615bf46635a817df708517e))
118 | - **lint:** linting issues ([#11](https://github.com/expect-dev/jest-opentelemetry/issues/11)) ([c74ca77](https://github.com/expect-dev/jest-opentelemetry/commit/c74ca77896c35a8ff72500451d41cc0c53d1c8b1))
119 | - otel receiver timeouts ([#17](https://github.com/expect-dev/jest-opentelemetry/issues/17)) ([8c99815](https://github.com/expect-dev/jest-opentelemetry/commit/8c998151a7a30c1a401aa1403587f35026d4814b))
120 | - typescript adaptations ([ec0ad8e](https://github.com/expect-dev/jest-opentelemetry/commit/ec0ad8e40c799f70881ae7ac3b441c7f6eb6c9cd))
121 |
122 | ### Features
123 |
124 | - basic integration with otel-receiver ([10f4a4e](https://github.com/expect-dev/jest-opentelemetry/commit/10f4a4e274079620f8461eb64b09c73e9a609077))
125 |
--------------------------------------------------------------------------------
/packages/jest-environment-otel/README.md:
--------------------------------------------------------------------------------
1 | # jest-environment-otel
2 |
3 | Run your tests using Jest & OpenTelemetry 🎪✨
4 |
5 | ```
6 | npm install @traceloop/jest-environment-otel
7 | ```
8 |
9 | ## Usage
10 |
11 | Update your Jest configuration:
12 |
13 | ```json
14 | {
15 | "globalSetup": "@traceloop/jest-environment-otel/setup",
16 | "globalTeardown": "@traceloop/jest-environment-otel/teardown",
17 | "testEnvironment": "@traceloop/jest-environment-otel"
18 | }
19 | ```
20 |
21 | ## License
22 |
23 | Apache-2.0
24 |
--------------------------------------------------------------------------------
/packages/jest-environment-otel/index.js:
--------------------------------------------------------------------------------
1 | import { teardown, setup } from './dist/global';
2 | import { OpenTelemetryEnvironment } from './dist/env';
3 |
4 | export default OpenTelemetryEnvironment;
5 | export { setup, teardown };
6 |
--------------------------------------------------------------------------------
/packages/jest-environment-otel/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: '/../jest-opentelemetry',
3 | transform: {
4 | '^.+\\.(t|j)sx?$': ['@swc/jest'],
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/packages/jest-environment-otel/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@traceloop/jest-environment-otel",
3 | "description": "OpenTelemetry environment for Jest.",
4 | "version": "0.8.0",
5 | "license": "Apache-2.0",
6 | "type": "commonjs",
7 | "main": "./index.js",
8 | "exports": {
9 | ".": "./index.js",
10 | "./setup": "./setup.js",
11 | "./teardown": "./teardown.js",
12 | "./package.json": "./package.json"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/traceloop/jest-opentelemetry.git",
17 | "directory": "packages/jest-environment-otel"
18 | },
19 | "homepage": "https://github.com/traceloop/jest-opentelemetry/tree/main/packages/jest-environment-otel#readme",
20 | "bugs": {
21 | "url": "https://github.com/traceloop/jest-opentelemetry/issues"
22 | },
23 | "engines": {
24 | "node": ">=14.0.0"
25 | },
26 | "keywords": [
27 | "jest",
28 | "jest-environment",
29 | "opentelemetry"
30 | ],
31 | "publishConfig": {
32 | "access": "public"
33 | },
34 | "scripts": {
35 | "prebuild": "rm -rf dist",
36 | "build": "rollup -c"
37 | },
38 | "devDependencies": {
39 | "@types/cwd": "^0.10.0",
40 | "@types/jest-dev-server": "^5.0.0",
41 | "@types/merge-deep": "^3.0.0",
42 | "rollup": "^3.20.0",
43 | "rollup-plugin-swc3": "^0.8.0"
44 | },
45 | "dependencies": {
46 | "@traceloop/otel-receiver": "^0.8.0",
47 | "chalk": "^4.1.2",
48 | "cwd": "^0.10.0",
49 | "jest-dev-server": "^7.0.1",
50 | "jest-environment-node": "^29.5.0",
51 | "merge-deep": "^3.0.3"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/jest-environment-otel/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import { swc, defineRollupSwcOption } from 'rollup-plugin-swc3';
2 | import typescript from '@rollup/plugin-typescript';
3 |
4 | const bundle = (config) => ({
5 | external: (id) => {
6 | return !/^[./]/.test(id);
7 | },
8 | ...config,
9 | });
10 |
11 | const swcPlugin = swc(
12 | defineRollupSwcOption({
13 | jsc: { target: 'es2021' },
14 | }),
15 | );
16 |
17 | export default [
18 | bundle({
19 | input: 'src/global.ts',
20 | output: {
21 | file: 'dist/global.js',
22 | format: 'cjs',
23 | interop: 'compat',
24 | },
25 | plugins: [swcPlugin, typescript({ tsconfig: '../../tsconfig.json' })],
26 | }),
27 | bundle({
28 | input: 'src/env.js',
29 | output: {
30 | file: 'dist/env.js',
31 | format: 'cjs',
32 | interop: 'compat',
33 | },
34 | plugins: [swcPlugin, typescript({ tsconfig: '../../tsconfig.json' })],
35 | }),
36 | ];
37 |
--------------------------------------------------------------------------------
/packages/jest-environment-otel/setup.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-var-requires
2 | module.exports = require('./dist/global').setup;
3 |
--------------------------------------------------------------------------------
/packages/jest-environment-otel/src/env.js:
--------------------------------------------------------------------------------
1 | import NodeEnvironment from 'jest-environment-node';
2 | import { readConfig } from './readConfig';
3 |
4 | const handleError = (error) => {
5 | process.emit('uncaughtException', error);
6 | };
7 |
8 | const KEYS = {
9 | CONTROL_C: '\u0003',
10 | CONTROL_D: '\u0004',
11 | ENTER: '\r',
12 | };
13 | // JEST_WORKER_ID starts at 1
14 | const getWorkerIndex = () => process.env.JEST_WORKER_ID - 1;
15 |
16 | // const screenshotsDirectory = join(process.cwd(), "screenshots");
17 |
18 | class OpenTelemetryEnvironment extends NodeEnvironment {
19 | // Jest is not available here, so we have to reverse engineer
20 | // the setTimeout function, see https://github.com/facebook/jest/blob/v23.1.0/packages/jest-runtime/src/index.js#L823
21 | setTimeout(timeout) {
22 | if (this.global.jasmine) {
23 | // eslint-disable-next-line no-underscore-dangle
24 | this.global.jasmine.DEFAULT_TIMEOUT_INTERVAL = timeout;
25 | } else {
26 | this.global[Symbol.for('TEST_TIMEOUT_SYMBOL')] = timeout;
27 | }
28 | }
29 |
30 | async setup() {
31 | const config = await readConfig();
32 | this.global.openTelemetryConfig = config;
33 |
34 | this.global.jestOpenTelemetry = {};
35 | }
36 |
37 | // eslint-disable-next-line @typescript-eslint/no-empty-function
38 | async teardown() {}
39 | }
40 |
41 | export { OpenTelemetryEnvironment };
42 |
--------------------------------------------------------------------------------
/packages/jest-environment-otel/src/global.ts:
--------------------------------------------------------------------------------
1 | import {
2 | setup as setupServer,
3 | teardown as teardownServer,
4 | ERROR_TIMEOUT,
5 | } from 'jest-dev-server';
6 | import chalk from 'chalk';
7 | import { readConfig } from './readConfig';
8 | import type { Config as JestConfig } from 'jest';
9 |
10 | let didAlreadyRunInWatchMode = false;
11 |
12 | export async function setup(jestConfig: JestConfig = {}) {
13 | const config = await readConfig();
14 |
15 | // If we are in watch mode, - only setupServer() once.
16 | if (jestConfig.watch || jestConfig.watchAll) {
17 | if (didAlreadyRunInWatchMode) return;
18 | didAlreadyRunInWatchMode = true;
19 | }
20 |
21 | if (!config?.useLocalOtelReceiver) {
22 | return;
23 | }
24 |
25 | try {
26 | await setupServer({
27 | command: 'node ./node_modules/@traceloop/otel-receiver/dist/index.js',
28 | // debug: true,
29 | host: 'localhost',
30 | port: 4123,
31 | protocol: 'http',
32 | path: 'ping',
33 | usedPortAction: 'kill', // todo: improve this
34 | });
35 | } catch (error: any) {
36 | if (error.code === ERROR_TIMEOUT) {
37 | console.log('');
38 | console.error(chalk.red(error.message));
39 | process.exit(1);
40 | }
41 | throw error;
42 | }
43 | }
44 |
45 | export async function teardown(jestConfig: JestConfig = {}) {
46 | if (!jestConfig.watch && !jestConfig.watchAll) {
47 | await teardownServer();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/jest-environment-otel/src/readConfig.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import { promisify } from 'util';
4 | import cwd from 'cwd';
5 | import merge from 'merge-deep';
6 | import { setTimeout } from 'timers/promises';
7 |
8 | const exists = promisify(fs.exists);
9 |
10 | const DEFAULT_CONFIG = {
11 | launch: {},
12 | };
13 | const DEFAULT_CONFIG_CI = merge(DEFAULT_CONFIG, {
14 | launch: {
15 | args: [],
16 | },
17 | });
18 |
19 | export async function readConfig() {
20 | const defaultConfig =
21 | process.env.CI === 'true' ? DEFAULT_CONFIG_CI : DEFAULT_CONFIG;
22 |
23 | const hasCustomConfigPath = !!process.env.JEST_OPENTELEMETRY_CONFIG;
24 | const configPath =
25 | process.env.JEST_OPENTELEMETRY_CONFIG || 'jest-opentelemetry.config.js';
26 | const absConfigPath = path.resolve(cwd(), configPath);
27 | const configExists = await exists(absConfigPath);
28 |
29 | if (hasCustomConfigPath && !configExists) {
30 | throw new Error(
31 | `Error: Can't find a root directory while resolving a config file path.\nProvided path to resolve: ${configPath}`,
32 | );
33 | }
34 |
35 | if (!hasCustomConfigPath && !configExists) {
36 | return defaultConfig;
37 | }
38 |
39 | const localConfig = await require(absConfigPath);
40 |
41 | if (localConfig.timeout) {
42 | await setTimeout(localConfig.timeout);
43 | }
44 |
45 | return merge({}, defaultConfig, localConfig);
46 | }
47 |
--------------------------------------------------------------------------------
/packages/jest-environment-otel/teardown.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-var-requires
2 | module.exports = require('./dist/global').teardown;
3 |
--------------------------------------------------------------------------------
/packages/jest-opentelemetry/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/traceloop/jest-opentelemetry/compare/v0.7.0...v0.8.0) (2023-06-20)
7 |
8 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
9 |
10 | # [0.7.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.6.0...v0.7.0) (2023-06-13)
11 |
12 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
13 |
14 | # [0.6.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.5.0...v0.6.0) (2023-06-04)
15 |
16 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
17 |
18 | # [0.5.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.4.1...v0.5.0) (2023-03-17)
19 |
20 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
21 |
22 | # 0.4.0 (2023-02-22)
23 |
24 | ### Bug Fixes
25 |
26 | - compilation & tests ([4e8698f](https://github.com/traceloop/jest-opentelemetry/commit/4e8698f254bad209becf2bd260679831fb25c0a6))
27 | - **expect:** type exports ([#21](https://github.com/traceloop/jest-opentelemetry/issues/21)) ([85f6481](https://github.com/traceloop/jest-opentelemetry/commit/85f6481501c5018a4e1a717f6cd8221deb2ab1f1))
28 | - jest preset references ([2665c1a](https://github.com/traceloop/jest-opentelemetry/commit/2665c1a0f301d0f0df8979a64c8fdfeea367dbc4))
29 | - package references ([029cfc9](https://github.com/traceloop/jest-opentelemetry/commit/029cfc9815403487a25dd780e26d92900378b66d))
30 |
31 | # [0.3.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.7...v0.3.0) (2023-02-21)
32 |
33 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
34 |
35 | ## [0.2.7](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.6...v0.2.7) (2023-02-21)
36 |
37 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
38 |
39 | ## [0.2.6](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.5...v0.2.6) (2023-02-21)
40 |
41 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
42 |
43 | ## [0.2.5](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.4...v0.2.5) (2023-02-21)
44 |
45 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
46 |
47 | ## [0.2.4](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.3...v0.2.4) (2023-02-21)
48 |
49 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
50 |
51 | ## [0.2.3](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.2...v0.2.3) (2023-02-21)
52 |
53 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
54 |
55 | ## [0.2.2](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.1...v0.2.2) (2023-02-21)
56 |
57 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
58 |
59 | ## [0.2.1](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.0...v0.2.1) (2023-02-21)
60 |
61 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
62 |
63 | # [0.2.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.8...v0.2.0) (2023-02-21)
64 |
65 | ### Bug Fixes
66 |
67 | - **expect:** type exports ([#21](https://github.com/traceloop/jest-opentelemetry/issues/21)) ([85f6481](https://github.com/traceloop/jest-opentelemetry/commit/85f6481501c5018a4e1a717f6cd8221deb2ab1f1))
68 |
69 | ## [0.1.10](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.9...v0.1.10) (2023-02-21)
70 |
71 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
72 |
73 | ## [0.1.9](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.8...v0.1.9) (2023-02-21)
74 |
75 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
76 |
77 | ## [0.1.8](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.7...v0.1.8) (2023-02-21)
78 |
79 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
80 |
81 | ## [0.1.7](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.6...v0.1.7) (2023-02-21)
82 |
83 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
84 |
85 | ## [0.1.6](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.5...v0.1.6) (2023-02-21)
86 |
87 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
88 |
89 | ## [0.1.5](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.4...v0.1.5) (2023-02-21)
90 |
91 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
92 |
93 | ## [0.1.4](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.3...v0.1.4) (2023-02-21)
94 |
95 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
96 |
97 | ## [0.1.3](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.2...v0.1.3) (2023-02-21)
98 |
99 | ### Bug Fixes
100 |
101 | - package references ([029cfc9](https://github.com/traceloop/jest-opentelemetry/commit/029cfc9815403487a25dd780e26d92900378b66d))
102 |
103 | ## [0.1.2](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.1...v0.1.2) (2023-02-21)
104 |
105 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
106 |
107 | ## [0.1.1](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.0...v0.1.1) (2023-02-21)
108 |
109 | **Note:** Version bump only for package @traceloop/jest-opentelemetry
110 |
111 | # 0.1.0 (2023-02-21)
112 |
113 | ### Bug Fixes
114 |
115 | - compilation & tests ([4e8698f](https://github.com/traceloop/jest-opentelemetry/commit/4e8698f254bad209becf2bd260679831fb25c0a6))
116 | - jest preset references ([2665c1a](https://github.com/traceloop/jest-opentelemetry/commit/2665c1a0f301d0f0df8979a64c8fdfeea367dbc4))
117 |
--------------------------------------------------------------------------------
/packages/jest-opentelemetry/README.md:
--------------------------------------------------------------------------------
1 | # jest-otel
2 |
3 | Jest preset containing all required configuration for writing integration tests using OpenTelemetry.
4 |
5 | ```
6 | npm install @traceloop/jest-opentelemetry
7 | ```
8 |
9 | ## Usage
10 |
11 | ```js
12 | // jest.config.js
13 | module.exports = {
14 | preset: '@traceloop/jest-opentelemetry',
15 | };
16 | ```
17 |
18 | ## License
19 |
20 | Apache-2.0
21 |
--------------------------------------------------------------------------------
/packages/jest-opentelemetry/jest-preset.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | globalSetup: require.resolve('@traceloop/jest-environment-otel/setup'),
3 | globalTeardown: require.resolve('@traceloop/jest-environment-otel/teardown'),
4 | testEnvironment: require.resolve('@traceloop/jest-environment-otel'),
5 | setupFilesAfterEnv: [require.resolve('@traceloop/expect-opentelemetry')],
6 | };
7 |
--------------------------------------------------------------------------------
/packages/jest-opentelemetry/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@traceloop/jest-opentelemetry",
3 | "description": "Run your tests using Jest & OpenTelemetry.",
4 | "version": "0.8.0",
5 | "license": "Apache-2.0",
6 | "type": "commonjs",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/traceloop/jest-opentelemetry.git",
10 | "directory": "packages/jest-opentelemetry"
11 | },
12 | "homepage": "https://github.com/traceloop/jest-opentelemetry/tree/main/packages/jest-opentelemetry#readme",
13 | "bugs": {
14 | "url": "https://github.com/traceloop/jest-opentelemetry/issues"
15 | },
16 | "engines": {
17 | "node": ">=14.0.0"
18 | },
19 | "publishConfig": {
20 | "access": "public"
21 | },
22 | "keywords": [
23 | "jest",
24 | "opentelemetry",
25 | "jest-opentelemetry"
26 | ],
27 | "dependencies": {
28 | "@traceloop/expect-opentelemetry": "^0.8.0",
29 | "@traceloop/jest-environment-otel": "^0.8.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/otel-proto/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/traceloop/jest-opentelemetry/compare/v0.7.0...v0.8.0) (2023-06-20)
7 |
8 | **Note:** Version bump only for package @traceloop/otel-proto
9 |
10 |
11 |
12 |
13 |
14 | # [0.7.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.6.0...v0.7.0) (2023-06-13)
15 |
16 | **Note:** Version bump only for package @traceloop/otel-proto
17 |
18 |
19 |
20 |
21 |
22 | # [0.6.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.5.0...v0.6.0) (2023-06-04)
23 |
24 | **Note:** Version bump only for package @traceloop/otel-proto
25 |
26 |
27 |
28 |
29 |
30 | # [0.5.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.4.1...v0.5.0) (2023-03-17)
31 |
32 |
33 | ### Features
34 |
35 | * traceloop proto ([#52](https://github.com/traceloop/jest-opentelemetry/issues/52)) ([53e9e68](https://github.com/traceloop/jest-opentelemetry/commit/53e9e68ab34bf79ab52adb82be219d48fef64acb))
36 |
37 |
38 |
39 |
40 |
41 | # 0.4.0 (2023-02-22)
42 |
43 | ### Bug Fixes
44 |
45 | - created a separate package for protobuf code ([b445f6f](https://github.com/traceloop/jest-opentelemetry/commit/b445f6fb7a32989b44253d813c4f92ad5dfffd2f))
46 | - moved dependncies to packages that need them ([60dc5ae](https://github.com/traceloop/jest-opentelemetry/commit/60dc5ae524920f9efea717034ae64152a180c69b))
47 |
48 | # [0.3.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.7...v0.3.0) (2023-02-21)
49 |
50 | **Note:** Version bump only for package @traceloop/otel-proto
51 |
52 | ## [0.2.7](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.6...v0.2.7) (2023-02-21)
53 |
54 | ### Bug Fixes
55 |
56 | - moved dependncies to packages that need them ([60dc5ae](https://github.com/traceloop/jest-opentelemetry/commit/60dc5ae524920f9efea717034ae64152a180c69b))
57 |
58 | ## [0.2.6](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.5...v0.2.6) (2023-02-21)
59 |
60 | ### Bug Fixes
61 |
62 | - created a separate package for protobuf code ([b445f6f](https://github.com/traceloop/jest-opentelemetry/commit/b445f6fb7a32989b44253d813c4f92ad5dfffd2f))
63 |
--------------------------------------------------------------------------------
/packages/otel-proto/README.md:
--------------------------------------------------------------------------------
1 | # otel-proto
2 |
3 | This repository contains both otel proto + traceloop proto files compiled to JS with type definitions
4 |
5 | ## Compiling proto files:
6 | Compile JS:
7 | npx pbjs -t static-module -w commonjs -o src/index.js --es6 src/opentelemetry/proto/trace/v1/trace.proto src/opentelemetry/proto/resource/v1/resource.proto src/opentelemetry/proto/common/v1/common.proto src/traceloop/proto/v1/traceloop.proto
8 |
9 | Create type definitions:
10 | npx pbts -o src/index.d.ts src/index.js
--------------------------------------------------------------------------------
/packages/otel-proto/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@traceloop/otel-proto",
3 | "description": "Otel & Traceloop proto compiled to JS with type definitions",
4 | "version": "0.8.0",
5 | "license": "Apache-2.0",
6 | "type": "commonjs",
7 | "main": "./dist/index.js",
8 | "types": "./dist/index.d.ts",
9 | "exports": {
10 | ".": {
11 | "require": "./dist/index.js",
12 | "types": "./dist/index.d.ts"
13 | },
14 | "./package.json": "./package.json"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/traceloop/jest-opentelemetry.git",
19 | "directory": "packages/otel-proto"
20 | },
21 | "homepage": "https://github.com/traceloop/jest-opentelemetry/tree/main/packages/otel-proto#readme",
22 | "bugs": {
23 | "url": "https://github.com/traceloop/jest-opentelemetry/issues"
24 | },
25 | "engines": {
26 | "node": ">=14.0.0"
27 | },
28 | "keywords": [
29 | "jest",
30 | "opentelemetry",
31 | "jest-opentelemetry",
32 | "expect",
33 | "assert",
34 | "should",
35 | "assertion"
36 | ],
37 | "publishConfig": {
38 | "access": "public"
39 | },
40 | "scripts": {
41 | "prebuild": "rm -rf dist",
42 | "build": "rollup -c"
43 | },
44 | "dependencies": {
45 | "express": "^4.18.2",
46 | "protobufjs": "^7.2.2"
47 | },
48 | "devDependencies": {
49 | "rollup-plugin-copy": "^3.4.0"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/packages/otel-proto/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import { swc, defineRollupSwcOption } from 'rollup-plugin-swc3';
2 | import copy from 'rollup-plugin-copy';
3 |
4 | const bundle = (config) => ({
5 | external: (id) => {
6 | return !/^[./]/.test(id);
7 | },
8 | ...config,
9 | });
10 |
11 | const swcPlugin = swc(
12 | defineRollupSwcOption({
13 | jsc: { target: 'es2021' },
14 | }),
15 | );
16 |
17 | export default [
18 | bundle({
19 | input: 'src/index.js',
20 | output: {
21 | file: 'dist/index.js',
22 | format: 'cjs',
23 | },
24 | plugins: [
25 | swcPlugin,
26 | copy({ targets: [{ src: 'src/index.d.ts', dest: 'dist' }] }),
27 | ],
28 | }),
29 | ];
30 |
--------------------------------------------------------------------------------
/packages/otel-proto/src/opentelemetry/proto/common/v1/common.proto:
--------------------------------------------------------------------------------
1 | // Copyright 2019, OpenTelemetry Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | syntax = "proto3";
16 |
17 | package opentelemetry.proto.common.v1;
18 |
19 | option csharp_namespace = "OpenTelemetry.Proto.Common.V1";
20 | option java_multiple_files = true;
21 | option java_package = "io.opentelemetry.proto.common.v1";
22 | option java_outer_classname = "CommonProto";
23 | option go_package = "go.opentelemetry.io/proto/otlp/common/v1";
24 |
25 | // AnyValue is used to represent any type of attribute value. AnyValue may contain a
26 | // primitive value such as a string or integer or it may contain an arbitrary nested
27 | // object containing arrays, key-value lists and primitives.
28 | message AnyValue {
29 | // The value is one of the listed fields. It is valid for all values to be unspecified
30 | // in which case this AnyValue is considered to be "empty".
31 | oneof value {
32 | string string_value = 1;
33 | bool bool_value = 2;
34 | int64 int_value = 3;
35 | double double_value = 4;
36 | ArrayValue array_value = 5;
37 | KeyValueList kvlist_value = 6;
38 | bytes bytes_value = 7;
39 | }
40 | }
41 |
42 | // ArrayValue is a list of AnyValue messages. We need ArrayValue as a message
43 | // since oneof in AnyValue does not allow repeated fields.
44 | message ArrayValue {
45 | // Array of values. The array may be empty (contain 0 elements).
46 | repeated AnyValue values = 1;
47 | }
48 |
49 | // KeyValueList is a list of KeyValue messages. We need KeyValueList as a message
50 | // since `oneof` in AnyValue does not allow repeated fields. Everywhere else where we need
51 | // a list of KeyValue messages (e.g. in Span) we use `repeated KeyValue` directly to
52 | // avoid unnecessary extra wrapping (which slows down the protocol). The 2 approaches
53 | // are semantically equivalent.
54 | message KeyValueList {
55 | // A collection of key/value pairs of key-value pairs. The list may be empty (may
56 | // contain 0 elements).
57 | // The keys MUST be unique (it is not allowed to have more than one
58 | // value with the same key).
59 | repeated KeyValue values = 1;
60 | }
61 |
62 | // KeyValue is a key-value pair that is used to store Span attributes, Link
63 | // attributes, etc.
64 | message KeyValue {
65 | string key = 1;
66 | AnyValue value = 2;
67 | }
68 |
69 | // InstrumentationScope is a message representing the instrumentation scope information
70 | // such as the fully qualified name and version.
71 | message InstrumentationScope {
72 | // An empty instrumentation scope name means the name is unknown.
73 | string name = 1;
74 | string version = 2;
75 | repeated KeyValue attributes = 3;
76 | uint32 dropped_attributes_count = 4;
77 | }
78 |
--------------------------------------------------------------------------------
/packages/otel-proto/src/opentelemetry/proto/resource/v1/resource.proto:
--------------------------------------------------------------------------------
1 | // Copyright 2019, OpenTelemetry Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | syntax = "proto3";
16 |
17 | package opentelemetry.proto.resource.v1;
18 |
19 | import "opentelemetry/proto/common/v1/common.proto";
20 |
21 | option csharp_namespace = "OpenTelemetry.Proto.Resource.V1";
22 | option java_multiple_files = true;
23 | option java_package = "io.opentelemetry.proto.resource.v1";
24 | option java_outer_classname = "ResourceProto";
25 | option go_package = "go.opentelemetry.io/proto/otlp/resource/v1";
26 |
27 | // Resource information.
28 | message Resource {
29 | // Set of attributes that describe the resource.
30 | // Attribute keys MUST be unique (it is not allowed to have more than one
31 | // attribute with the same key).
32 | repeated opentelemetry.proto.common.v1.KeyValue attributes = 1;
33 |
34 | // dropped_attributes_count is the number of dropped attributes. If the value is 0, then
35 | // no attributes were dropped.
36 | uint32 dropped_attributes_count = 2;
37 | }
38 |
--------------------------------------------------------------------------------
/packages/otel-proto/src/traceloop/proto/v1/traceloop.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package traceloop.proto.traceloop.v1;
4 |
5 | import "opentelemetry/proto/common/v1/common.proto";
6 | import "opentelemetry/proto/resource/v1/resource.proto";
7 | import "opentelemetry/proto/trace/v1/trace.proto";
8 |
9 | option go_package = "./";
10 |
11 | message TraceloopSpan {
12 | // The resource for the span.
13 | opentelemetry.proto.resource.v1.Resource resource = 1;
14 |
15 | // The instrumentation scope for the span.
16 | opentelemetry.proto.common.v1.InstrumentationScope scope = 2;
17 |
18 | // The span.
19 | opentelemetry.proto.trace.v1.Span span = 3;
20 |
21 | string trace_id = 4;
22 |
23 | string span_id = 5;
24 |
25 | string parent_span_id = 6;
26 |
27 | string customer_id = 7;
28 |
29 | string traceloop_id = 8;
30 | }
31 |
--------------------------------------------------------------------------------
/packages/otel-receiver/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/traceloop/jest-opentelemetry/compare/v0.7.0...v0.8.0) (2023-06-20)
7 |
8 | **Note:** Version bump only for package @traceloop/otel-receiver
9 |
10 | # [0.7.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.6.0...v0.7.0) (2023-06-13)
11 |
12 | **Note:** Version bump only for package @traceloop/otel-receiver
13 |
14 | # [0.6.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.5.0...v0.6.0) (2023-06-04)
15 |
16 | **Note:** Version bump only for package @traceloop/otel-receiver
17 |
18 | # [0.5.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.4.1...v0.5.0) (2023-03-17)
19 |
20 | ### Features
21 |
22 | - go server backend ([#42](https://github.com/traceloop/jest-opentelemetry/issues/42)) ([a543845](https://github.com/traceloop/jest-opentelemetry/commit/a543845445617bd321c7cee793e23caf2c651844))
23 |
24 | # 0.4.0 (2023-02-22)
25 |
26 | ### Bug Fixes
27 |
28 | - compilation & tests ([4e8698f](https://github.com/traceloop/jest-opentelemetry/commit/4e8698f254bad209becf2bd260679831fb25c0a6))
29 | - created a separate package for protobuf code ([b445f6f](https://github.com/traceloop/jest-opentelemetry/commit/b445f6fb7a32989b44253d813c4f92ad5dfffd2f))
30 | - import paths of proto package ([466e8b1](https://github.com/traceloop/jest-opentelemetry/commit/466e8b13521f77d4d1893b86b438f488a8ade0e7))
31 | - imports from dist ([#16](https://github.com/traceloop/jest-opentelemetry/issues/16)) ([230849d](https://github.com/traceloop/jest-opentelemetry/commit/230849d617887d53b85a012cd1878dbdfca19e7b))
32 | - **lint:** linting issues ([#11](https://github.com/traceloop/jest-opentelemetry/issues/11)) ([c74ca77](https://github.com/traceloop/jest-opentelemetry/commit/c74ca77896c35a8ff72500451d41cc0c53d1c8b1))
33 | - otel receiver timeouts ([#17](https://github.com/traceloop/jest-opentelemetry/issues/17)) ([8c99815](https://github.com/traceloop/jest-opentelemetry/commit/8c998151a7a30c1a401aa1403587f35026d4814b))
34 | - otel-receiver can be executed ([4360cdb](https://github.com/traceloop/jest-opentelemetry/commit/4360cdb3970835142c35f3eb28d7355f909a165b))
35 | - typescript adaptations ([ec0ad8e](https://github.com/traceloop/jest-opentelemetry/commit/ec0ad8e40c799f70881ae7ac3b441c7f6eb6c9cd))
36 |
37 | ### Features
38 |
39 | - basic integration with otel-receiver ([10f4a4e](https://github.com/traceloop/jest-opentelemetry/commit/10f4a4e274079620f8461eb64b09c73e9a609077))
40 |
41 | # [0.3.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.7...v0.3.0) (2023-02-21)
42 |
43 | ### Bug Fixes
44 |
45 | - import paths of proto package ([466e8b1](https://github.com/traceloop/jest-opentelemetry/commit/466e8b13521f77d4d1893b86b438f488a8ade0e7))
46 |
47 | ## [0.2.7](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.6...v0.2.7) (2023-02-21)
48 |
49 | **Note:** Version bump only for package @traceloop/otel-receiver
50 |
51 | ## [0.2.6](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.5...v0.2.6) (2023-02-21)
52 |
53 | ### Bug Fixes
54 |
55 | - created a separate package for protobuf code ([b445f6f](https://github.com/traceloop/jest-opentelemetry/commit/b445f6fb7a32989b44253d813c4f92ad5dfffd2f))
56 |
57 | ## [0.2.5](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.4...v0.2.5) (2023-02-21)
58 |
59 | ### Bug Fixes
60 |
61 | - otel-receiver can be executed ([4360cdb](https://github.com/traceloop/jest-opentelemetry/commit/4360cdb3970835142c35f3eb28d7355f909a165b))
62 |
63 | # [0.2.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.8...v0.2.0) (2023-02-21)
64 |
65 | **Note:** Version bump only for package @traceloop/otel-receiver
66 |
67 | ## [0.1.2](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.1...v0.1.2) (2023-02-21)
68 |
69 | **Note:** Version bump only for package @traceloop/otel-receiver
70 |
71 | ## [0.1.1](https://github.com/expect-dev/jest-opentelemetry/compare/v0.1.0...v0.1.1) (2023-02-21)
72 |
73 | **Note:** Version bump only for package @traceloop/otel-receiver
74 |
75 | # 0.1.0 (2023-02-21)
76 |
77 | ### Bug Fixes
78 |
79 | - compilation & tests ([4e8698f](https://github.com/expect-dev/jest-opentelemetry/commit/4e8698f254bad209becf2bd260679831fb25c0a6))
80 | - imports from dist ([#16](https://github.com/expect-dev/jest-opentelemetry/issues/16)) ([230849d](https://github.com/expect-dev/jest-opentelemetry/commit/230849d617887d53b85a012cd1878dbdfca19e7b))
81 | - **lint:** linting issues ([#11](https://github.com/expect-dev/jest-opentelemetry/issues/11)) ([c74ca77](https://github.com/expect-dev/jest-opentelemetry/commit/c74ca77896c35a8ff72500451d41cc0c53d1c8b1))
82 | - otel receiver timeouts ([#17](https://github.com/expect-dev/jest-opentelemetry/issues/17)) ([8c99815](https://github.com/expect-dev/jest-opentelemetry/commit/8c998151a7a30c1a401aa1403587f35026d4814b))
83 | - typescript adaptations ([ec0ad8e](https://github.com/expect-dev/jest-opentelemetry/commit/ec0ad8e40c799f70881ae7ac3b441c7f6eb6c9cd))
84 |
85 | ### Features
86 |
87 | - basic integration with otel-receiver ([10f4a4e](https://github.com/expect-dev/jest-opentelemetry/commit/10f4a4e274079620f8461eb64b09c73e9a609077))
88 |
--------------------------------------------------------------------------------
/packages/otel-receiver/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/otel-receiver/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@traceloop/otel-receiver",
3 | "description": "A node server to receive and cache incoming opentelemetry spans.",
4 | "version": "0.8.0",
5 | "license": "Apache-2.0",
6 | "type": "commonjs",
7 | "main": "./dist/index.js",
8 | "bin": "./dist/index.js",
9 | "exports": {
10 | ".": "./dist/index.js",
11 | "./package.json": "./package.json"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/traceloop/jest-opentelemetry.git",
16 | "directory": "packages/otel-receiver"
17 | },
18 | "homepage": "https://github.com/traceloop/jest-opentelemetry/tree/main/packages/otel-receiver#readme",
19 | "bugs": {
20 | "url": "https://github.com/traceloop/jest-opentelemetry/issues"
21 | },
22 | "engines": {
23 | "node": ">=14.0.0"
24 | },
25 | "keywords": [
26 | "jest",
27 | "opentelemetry",
28 | "jest-opentelemetry",
29 | "expect",
30 | "assert",
31 | "should",
32 | "assertion"
33 | ],
34 | "publishConfig": {
35 | "access": "public"
36 | },
37 | "scripts": {
38 | "prebuild": "rm -rf dist",
39 | "build": "rollup -c",
40 | "start": "node ./dist/index.js"
41 | },
42 | "devDependencies": {
43 | "@types/express": "^4.17.17",
44 | "rollup": "^3.20.0",
45 | "rollup-plugin-swc3": "^0.8.0"
46 | },
47 | "dependencies": {
48 | "@traceloop/otel-proto": "^0.8.0",
49 | "express": "^4.18.2"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/packages/otel-receiver/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import { swc, defineRollupSwcOption } from 'rollup-plugin-swc3';
2 | import typescript from '@rollup/plugin-typescript';
3 |
4 | const bundle = (config) => ({
5 | external: (id) => {
6 | return !/^[./]/.test(id);
7 | },
8 | ...config,
9 | });
10 |
11 | const swcPlugin = swc(
12 | defineRollupSwcOption({
13 | jsc: { target: 'es2021' },
14 | }),
15 | );
16 |
17 | export default [
18 | bundle({
19 | input: 'src/index.ts',
20 | output: {
21 | file: 'dist/index.js',
22 | format: 'cjs',
23 | },
24 | plugins: [swcPlugin, typescript({ tsconfig: '../../tsconfig.json' })],
25 | }),
26 | ];
27 |
--------------------------------------------------------------------------------
/packages/otel-receiver/src/index.ts:
--------------------------------------------------------------------------------
1 | import express, { Request, Response } from 'express';
2 | import { addToStore, getAll } from './store';
3 | import { opentelemetry } from '@traceloop/otel-proto';
4 |
5 | const app = express();
6 | const port = 4123;
7 | let _server;
8 |
9 | const startServer = async () => {
10 | app.post(
11 | '/v1/traces',
12 | express.raw({ type: '*/*' }),
13 | (req: Request, res: Response) => {
14 | const tracesData = opentelemetry.proto.trace.v1.TracesData.decode(
15 | req.body,
16 | );
17 |
18 | addToStore(tracesData.resourceSpans);
19 |
20 | res.send();
21 | },
22 | );
23 |
24 | app.get('/v1/traces/:traceloopId', (_: Request, res: Response) => {
25 | res.send(getAll());
26 | });
27 |
28 | app.get('/ping', (_: Request, res: Response) => {
29 | res.send('pong');
30 | });
31 |
32 | _server = app.listen(port, () => {
33 | console.log(`otel-receiver listening at port ${port}`);
34 | });
35 | };
36 |
37 | const gracefulShutdownHandler = function gracefulShutdownHandler(signal) {
38 | console.log(`otel-receiver caught ${signal}, gracefully shutting down`);
39 |
40 | setTimeout(() => {
41 | _server.close(function () {
42 | process.exit();
43 | });
44 | }, 0);
45 | };
46 |
47 | process.on('SIGINT', gracefulShutdownHandler);
48 | process.on('SIGTERM', gracefulShutdownHandler);
49 |
50 | startServer();
51 |
--------------------------------------------------------------------------------
/packages/otel-receiver/src/store.ts:
--------------------------------------------------------------------------------
1 | import { opentelemetry } from '@traceloop/otel-proto';
2 |
3 | const _resourceSpans: opentelemetry.proto.trace.v1.IResourceSpans[] = [];
4 |
5 | export const addToStore = (
6 | resourceSpans: opentelemetry.proto.trace.v1.IResourceSpans[],
7 | ) => {
8 | _resourceSpans.push(...resourceSpans);
9 | };
10 |
11 | export const getAll = () => {
12 | const tracesData = new opentelemetry.proto.trace.v1.TracesData({
13 | resourceSpans: _resourceSpans,
14 | });
15 |
16 | return opentelemetry.proto.trace.v1.TracesData.encode(tracesData).finish();
17 | };
18 |
--------------------------------------------------------------------------------
/packages/test-servers/.dockerignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
--------------------------------------------------------------------------------
/packages/test-servers/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 (2023-02-22)
7 |
8 | ### Bug Fixes
9 |
10 | - imports from dist ([#16](https://github.com/traceloop/jest-opentelemetry/issues/16)) ([230849d](https://github.com/traceloop/jest-opentelemetry/commit/230849d617887d53b85a012cd1878dbdfca19e7b))
11 | - **integration:** integration test dsl with test servers ([#4](https://github.com/traceloop/jest-opentelemetry/issues/4)) ([6b4b7f7](https://github.com/traceloop/jest-opentelemetry/commit/6b4b7f7c43f7f450b615bf46635a817df708517e))
12 | - moved dependncies to packages that need them ([60dc5ae](https://github.com/traceloop/jest-opentelemetry/commit/60dc5ae524920f9efea717034ae64152a180c69b))
13 | - type definitions ([7fb5242](https://github.com/traceloop/jest-opentelemetry/commit/7fb5242d47a97c8c1e5f496b82492989bb1ef008))
14 |
15 | ### Features
16 |
17 | - 2 test servers ([0191781](https://github.com/traceloop/jest-opentelemetry/commit/01917812668fd8d31c1962fdefbb539ae3cf7774))
18 | - basic integration with otel-receiver ([10f4a4e](https://github.com/traceloop/jest-opentelemetry/commit/10f4a4e274079620f8461eb64b09c73e9a609077))
19 | - **database-matchers:** support postgres matchers ([#24](https://github.com/traceloop/jest-opentelemetry/issues/24)) ([8b86a4b](https://github.com/traceloop/jest-opentelemetry/commit/8b86a4b7c926498c00a3eaa3da326d45eeda8d77))
20 | - http matchers ([#29](https://github.com/traceloop/jest-opentelemetry/issues/29)) ([9c436c8](https://github.com/traceloop/jest-opentelemetry/commit/9c436c894e08c443404e842c5dbdf40845cd664c))
21 | - k8s init-container ([c05bda4](https://github.com/traceloop/jest-opentelemetry/commit/c05bda437b9b11e5097e482f6a7885e58789cc5b))
22 | - kuberneteized test-servers ([5d3283d](https://github.com/traceloop/jest-opentelemetry/commit/5d3283d98779a9fba4321fdafda5bdd5ac5f71c7))
23 | - otel orchestartion on k8s ([dc19417](https://github.com/traceloop/jest-opentelemetry/commit/dc194173a2c2d7c7a892d437ee0d2101c6ac1605))
24 | - separate otel instrumentation into a new package ([dfe7586](https://github.com/traceloop/jest-opentelemetry/commit/dfe758613c5e17c4e92144023c82d91526aca786))
25 | - TraceLoop ([#19](https://github.com/traceloop/jest-opentelemetry/issues/19)) ([ab17a91](https://github.com/traceloop/jest-opentelemetry/commit/ab17a91601ff8643ed99a6e2adaced491ab0743d))
26 |
27 | # [0.3.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.7...v0.3.0) (2023-02-21)
28 |
29 | **Note:** Version bump only for package @traceloop/test-servers
30 |
31 | ## [0.2.7](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.6...v0.2.7) (2023-02-21)
32 |
33 | ### Bug Fixes
34 |
35 | - moved dependncies to packages that need them ([60dc5ae](https://github.com/traceloop/jest-opentelemetry/commit/60dc5ae524920f9efea717034ae64152a180c69b))
36 |
37 | ## [0.2.1](https://github.com/traceloop/jest-opentelemetry/compare/v0.2.0...v0.2.1) (2023-02-21)
38 |
39 | ### Bug Fixes
40 |
41 | - type definitions ([7fb5242](https://github.com/traceloop/jest-opentelemetry/commit/7fb5242d47a97c8c1e5f496b82492989bb1ef008))
42 |
43 | # [0.2.0](https://github.com/traceloop/jest-opentelemetry/compare/v0.1.8...v0.2.0) (2023-02-21)
44 |
45 | ### Features
46 |
47 | - TraceLoop ([#19](https://github.com/traceloop/jest-opentelemetry/issues/19)) ([ab17a91](https://github.com/traceloop/jest-opentelemetry/commit/ab17a91601ff8643ed99a6e2adaced491ab0743d))
48 |
49 | # 0.1.0 (2023-02-21)
50 |
51 | ### Bug Fixes
52 |
53 | - imports from dist ([#16](https://github.com/traceloop/jest-opentelemetry/issues/16)) ([230849d](https://github.com/traceloop/jest-opentelemetry/commit/230849d617887d53b85a012cd1878dbdfca19e7b))
54 | - **integration:** integration test dsl with test servers ([#4](https://github.com/traceloop/jest-opentelemetry/issues/4)) ([6b4b7f7](https://github.com/traceloop/jest-opentelemetry/commit/6b4b7f7c43f7f450b615bf46635a817df708517e))
55 |
56 | ### Features
57 |
58 | - 2 test servers ([0191781](https://github.com/traceloop/jest-opentelemetry/commit/01917812668fd8d31c1962fdefbb539ae3cf7774))
59 | - basic integration with otel-receiver ([10f4a4e](https://github.com/traceloop/jest-opentelemetry/commit/10f4a4e274079620f8461eb64b09c73e9a609077))
60 | - kuberneteized test-servers ([5d3283d](https://github.com/traceloop/jest-opentelemetry/commit/5d3283d98779a9fba4321fdafda5bdd5ac5f71c7))
61 | - otel orchestartion on k8s ([dc19417](https://github.com/traceloop/jest-opentelemetry/commit/dc194173a2c2d7c7a892d437ee0d2101c6ac1605))
62 |
--------------------------------------------------------------------------------
/packages/test-servers/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:16
2 |
3 | WORKDIR /usr/src/app
4 |
5 | COPY package*.json ./
6 | COPY packages/test-servers/package*.json ./packages/test-servers/
7 |
8 | RUN npm install
9 |
10 | COPY lerna.json ./
11 | COPY tsconfig.json ./
12 | COPY packages/test-servers/. ./packages/test-servers/
13 |
14 | RUN npm run build
15 |
16 | CMD node ./packages/test-servers/dist/index.js
--------------------------------------------------------------------------------
/packages/test-servers/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: opentelemetry.io/v1alpha1
2 | kind: Instrumentation
3 | metadata:
4 | name: otel-instrumentation
5 | spec:
6 | nodejs:
7 | image: traceloop/instrument-opentelemetry:node-0.3.0
8 | exporter:
9 | endpoint: http://otel-collector:4317
10 | propagators:
11 | - tracecontext
12 | - baggage
13 | - b3
14 | ---
15 | apiVersion: apps/v1
16 | kind: Deployment
17 | metadata:
18 | name: orders-service
19 | spec:
20 | replicas: 1
21 | selector:
22 | matchLabels:
23 | app: orders-service
24 | template:
25 | metadata:
26 | labels:
27 | app: orders-service
28 | annotations:
29 | instrumentation.opentelemetry.io/inject-nodejs: 'true'
30 | spec:
31 | containers:
32 | - name: orders-service
33 | image: test-servers
34 | ports:
35 | - containerPort: 3000
36 | env:
37 | - name: SERVICE_NAME
38 | value: 'orders-service'
39 | - name: ORDERS_SERVICE
40 | value: 'TRUE'
41 | - name: EMAILS_SERVICE_URL
42 | value: 'http://emails-service:3000'
43 | - name: PORT
44 | value: '3000'
45 | imagePullPolicy: Never
46 |
47 | ---
48 | apiVersion: apps/v1
49 | kind: Deployment
50 | metadata:
51 | name: emails-service
52 | spec:
53 | replicas: 1
54 | selector:
55 | matchLabels:
56 | app: emails-service
57 | template:
58 | metadata:
59 | labels:
60 | app: emails-service
61 | annotations:
62 | instrumentation.opentelemetry.io/inject-nodejs: 'true'
63 | spec:
64 | containers:
65 | - name: emails-service
66 | image: test-servers
67 | ports:
68 | - containerPort: 3000
69 | env:
70 | - name: SERVICE_NAME
71 | value: 'emails-service'
72 | - name: EMAILS_SERVICE
73 | value: 'TRUE'
74 | - name: PORT
75 | value: '3000'
76 | imagePullPolicy: Never
77 |
78 | ---
79 | apiVersion: apps/v1
80 | kind: Deployment
81 | metadata:
82 | name: grpc-service
83 | spec:
84 | replicas: 1
85 | selector:
86 | matchLabels:
87 | app: grpc-service
88 | template:
89 | metadata:
90 | labels:
91 | app: grpc-service
92 | annotations:
93 | instrumentation.opentelemetry.io/inject-nodejs: 'true'
94 | spec:
95 | containers:
96 | - name: grpc-service
97 | image: test-servers
98 | ports:
99 | - containerPort: 50051
100 | env:
101 | - name: SERVICE_NAME
102 | value: 'grpc-service'
103 | - name: GRPC_SERVICE
104 | value: 'TRUE'
105 | - name: PORT
106 | value: '50051'
107 | imagePullPolicy: Never
108 |
109 | ---
110 | apiVersion: v1
111 | kind: Service
112 | metadata:
113 | name: orders-service
114 | spec:
115 | selector:
116 | app: orders-service
117 | ports:
118 | - protocol: TCP
119 | port: 3000
120 | targetPort: 3000
121 | type: LoadBalancer
122 |
123 | ---
124 | apiVersion: v1
125 | kind: Service
126 | metadata:
127 | name: grpc-service
128 | spec:
129 | selector:
130 | app: grpc-service
131 | ports:
132 | - protocol: TCP
133 | port: 50051
134 | targetPort: 50051
135 |
136 | ---
137 | apiVersion: v1
138 | kind: Service
139 | metadata:
140 | name: emails-service
141 | spec:
142 | selector:
143 | app: emails-service
144 | ports:
145 | - protocol: TCP
146 | port: 3000
147 | targetPort: 3000
148 |
149 | ---
150 | apiVersion: opentelemetry.io/v1alpha1
151 | kind: OpenTelemetryCollector
152 | metadata:
153 | name: otel
154 | spec:
155 | config: |
156 | receivers:
157 | otlp:
158 | protocols:
159 | grpc:
160 | http:
161 | processors:
162 | batch:
163 | timeout: 100ms
164 |
165 | exporters:
166 | logging:
167 | verbosity: detailed
168 | otlphttp:
169 | endpoint: http://host.docker.internal:4123
170 |
171 | service:
172 | pipelines:
173 | traces:
174 | receivers: [otlp]
175 | processors: [batch]
176 | exporters: [logging, otlphttp]
177 |
--------------------------------------------------------------------------------
/packages/test-servers/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@traceloop/test-servers",
3 | "version": "0.4.0",
4 | "private": true,
5 | "main": "./dist/index.js",
6 | "engines": {
7 | "node": ">=14.0.0"
8 | },
9 | "scripts": {
10 | "prebuild": "rm -rf dist",
11 | "build": "rollup -c && cp ./src/helloworld.proto ./dist/helloworld.proto",
12 | "start:orders": "ORDERS_SERVICE=TRUE SERVICE_NAME=orders-service PORT=3000 OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4123/v1/traces OTEL_EXPORTER_TYPE=PROTO node -r @traceloop/instrument-opentelemetry dist/index.js",
13 | "start:emails": "EMAILS_SERVICE=TRUE SERVICE_NAME=emails-service PORT=3001 OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4123/v1/traces OTEL_EXPORTER_TYPE=PROTO node -r @traceloop/instrument-opentelemetry dist/index.js",
14 | "start:grpc": "GRPC_SERVICE=TRUE SERVICE_NAME=grpc-service PORT=50051 OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4123/v1/traces OTEL_EXPORTER_TYPE=PROTO node -r @traceloop/instrument-opentelemetry dist/index.js",
15 | "start": "concurrently \"npm:start:orders\" \"npm:start:emails\" \"npm:start:grpc\""
16 | },
17 | "devDependencies": {
18 | "@types/express": "^4.17.17",
19 | "@types/uuid": "^9.0.0",
20 | "rollup": "^3.20.0",
21 | "rollup-plugin-swc3": "^0.8.0"
22 | },
23 | "dependencies": {
24 | "@grpc/proto-loader": "^0.7.5",
25 | "@traceloop/instrument-opentelemetry": "^0.5.0",
26 | "express": "^4.18.2",
27 | "google-protobuf": "^3.0.0",
28 | "pg": "^8.9.0",
29 | "typeorm": "^0.3.12",
30 | "uuid": "^9.0.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/test-servers/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import { swc, defineRollupSwcOption } from 'rollup-plugin-swc3';
2 | import typescript from '@rollup/plugin-typescript';
3 |
4 | const bundle = (config) => ({
5 | external: (id) => {
6 | return !/^[./]/.test(id);
7 | },
8 | ...config,
9 | });
10 |
11 | const swcPlugin = swc(
12 | defineRollupSwcOption({
13 | jsc: { target: 'es2021' },
14 | }),
15 | );
16 |
17 | export default [
18 | bundle({
19 | input: ['src/index.ts'],
20 | output: {
21 | file: 'dist/index.js',
22 | format: 'cjs',
23 | },
24 | plugins: [swcPlugin, typescript({ tsconfig: '../../tsconfig.json' })],
25 | }),
26 | ];
27 |
--------------------------------------------------------------------------------
/packages/test-servers/src/helloworld.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | option java_multiple_files = true;
4 | option java_package = "io.grpc.examples.helloworld";
5 | option java_outer_classname = "HelloWorldProto";
6 | option objc_class_prefix = "HLW";
7 |
8 | package helloworld;
9 |
10 | // The greeting service definition.
11 | service Greeter {
12 | // Sends a greeting
13 | rpc SayHello (HelloRequest) returns (HelloReply) {}
14 | }
15 |
16 | // The request message containing the user's name.
17 | message HelloRequest {
18 | string name = 1;
19 | }
20 |
21 | // The response message containing the greetings
22 | message HelloReply {
23 | string message = 1;
24 | }
25 |
--------------------------------------------------------------------------------
/packages/test-servers/src/index.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import express from 'express';
3 | import { DataSource } from 'typeorm';
4 | import { v4 as uuidv4 } from 'uuid';
5 | import grpc from '@grpc/grpc-js';
6 | import protoLoader from '@grpc/proto-loader';
7 |
8 | // --- Protos ---
9 | const PROTO_PATH = __dirname + '/helloworld.proto';
10 |
11 | const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
12 | keepCase: true,
13 | longs: String,
14 | enums: String,
15 | defaults: true,
16 | oneofs: true,
17 | });
18 | const hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
19 |
20 | // --- Postgres ---
21 | const ordersDataSource = new DataSource({
22 | type: 'postgres',
23 | host: process.env.POSTGRES_HOST || 'localhost',
24 | port: process.env.POSTGRES_PORT ? parseInt(process.env.POSTGRES_PORT) : 5432,
25 | username: process.env.POSTGRES_USERNAME || 'postgres',
26 | password: process.env.POSTGRES_PASSWORD || 'postgres',
27 | database: process.env.POSTGRES_DATABASE || 'postgres',
28 | });
29 |
30 | const postgresSchema = process.env.POSTGRES_SCHEMA || 'public';
31 | let ordersDataSourceInitialized = false;
32 |
33 | const initializeOrdersDatasource = async () =>
34 | ordersDataSource
35 | .initialize()
36 | .then(async () => {
37 | console.log('Orders data Source has been initialized!');
38 | await ordersDataSource.query(
39 | `CREATE TABLE IF NOT EXISTS ${postgresSchema}.orders (id varchar(50), price_in_cents int)`,
40 | );
41 | ordersDataSourceInitialized = true;
42 | console.log('Orders table has been created!');
43 | })
44 | .catch((err) => {
45 | console.error('Error during orders data source initialization', err);
46 | });
47 |
48 | if (process.env.ORDERS_SERVICE) {
49 | initializeOrdersDatasource();
50 | }
51 |
52 | // --- Orders Service ---
53 | const ordersService = express();
54 |
55 | ordersService.post('/orders/create', async (req, res) => {
56 | if (!ordersDataSourceInitialized) {
57 | await initializeOrdersDatasource();
58 | }
59 |
60 | const orderId = uuidv4();
61 | console.log('Creating order...');
62 |
63 | try {
64 | ordersDataSource.query(
65 | `INSERT INTO orders (id, price_in_cents) VALUES ('${orderId}', 1000)`,
66 | );
67 | } catch (err) {
68 | console.error('Error creating order', err);
69 | console.log(
70 | 'Omitting order creation, please make sure the database is running',
71 | );
72 | }
73 |
74 | // make http call
75 | console.log('Order created! Sending email...');
76 | const EMAILS_SERVICE_URL =
77 | process.env.EMAILS_SERVICE_URL || 'http://localhost:3001';
78 | await axios.post(`${EMAILS_SERVICE_URL}/emails/send`, {
79 | email: 'test',
80 | nestedObject: { test: 'test' },
81 | });
82 |
83 | // make grpc call
84 | console.log('Making gRPC call');
85 | const GRPC_SERVICE_URL = process.env.GRPC_SERVICE_URL || 'localhost:50051';
86 | const client = new (hello_proto as any).Greeter(
87 | GRPC_SERVICE_URL,
88 | grpc.credentials.createInsecure(),
89 | );
90 |
91 | client.sayHello({ name: 'name' }, function (_: any, response: any) {
92 | console.log('Greeting:', response.message);
93 | });
94 |
95 | res.send('Order created!');
96 | });
97 |
98 | // --- Emails Service ---
99 | const emailsService = express();
100 |
101 | emailsService.post('/emails/send', (req, res) => {
102 | console.log('Email sent!');
103 | res.send('Email sent!');
104 | });
105 |
106 | // --- gRPC Service ---
107 | const grpcServer = new grpc.Server();
108 |
109 | function sayHello(call: any, callback: any) {
110 | callback(null, { message: 'Hello ' + call.request.name });
111 | }
112 | grpcServer.addService((hello_proto as any).Greeter.service, {
113 | sayHello: sayHello,
114 | });
115 |
116 | // --- Initialize Service ---
117 | const PORT = process.env.PORT || 3000;
118 |
119 | if (process.env.ORDERS_SERVICE) {
120 | ordersService.listen(PORT, () => {
121 | console.log(`Orders service listening at http://localhost:${PORT}`);
122 | });
123 | }
124 |
125 | if (process.env.EMAILS_SERVICE) {
126 | emailsService.listen(PORT, () => {
127 | console.log(`Emails service listening at http://localhost:${PORT}`);
128 | });
129 | }
130 |
131 | if (process.env.GRPC_SERVICE) {
132 | grpcServer.bindAsync(
133 | `0.0.0.0:${PORT}`,
134 | grpc.ServerCredentials.createInsecure(),
135 | () => {
136 | console.log(`gRPC service listening on port ${PORT}`);
137 | grpcServer.start();
138 | },
139 | );
140 | }
141 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/recommended/tsconfig.json",
3 | "compilerOptions": {
4 | "preserveConstEnums": true,
5 | "allowSyntheticDefaultImports": true,
6 | "allowJs": true,
7 | "declaration": true,
8 | "outDir": "dist"
9 | },
10 | "include": ["packages/*/src/**/*"],
11 | "exclude": ["node_modules", "dist"]
12 | }
13 |
--------------------------------------------------------------------------------