├── .eslintrc.json ├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE.md ├── stale.yml └── workflows │ ├── continuous-monitoring.yml │ ├── deprecate_version.yml │ ├── lint.yml │ ├── pr-build.yml │ ├── release.yml │ └── smoke-test.yml ├── .gitignore ├── .nycrc.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── images └── example_servicemap.png ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── core │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .npmignore │ ├── CHANGELOG.md │ ├── Gruntfile.js │ ├── LICENSE │ ├── NOTICE.txt │ ├── README.md │ ├── doc-src │ │ └── templates │ │ │ └── layout.tmpl │ ├── jsdoc_conf.json │ ├── lib │ │ ├── aws-xray.d.ts │ │ ├── aws-xray.js │ │ ├── capture.d.ts │ │ ├── capture.js │ │ ├── context_utils.d.ts │ │ ├── context_utils.js │ │ ├── daemon_config.d.ts │ │ ├── daemon_config.js │ │ ├── database │ │ │ ├── sql_data.d.ts │ │ │ └── sql_data.js │ │ ├── env │ │ │ ├── aws_lambda.js │ │ │ └── sqs_message_helper.js │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── logger.d.ts │ │ ├── logger.js │ │ ├── middleware │ │ │ ├── incoming_request_data.d.ts │ │ │ ├── incoming_request_data.js │ │ │ ├── mw_utils.d.ts │ │ │ ├── mw_utils.js │ │ │ └── sampling │ │ │ │ ├── default_sampler.js │ │ │ │ ├── local_reservoir.js │ │ │ │ ├── local_sampler.js │ │ │ │ ├── reservoir.js │ │ │ │ ├── rule_cache.js │ │ │ │ ├── rule_poller.js │ │ │ │ ├── sampling_rule.js │ │ │ │ ├── service_connector.js │ │ │ │ └── target_poller.js │ │ ├── patchers │ │ │ ├── aws3_p.d.ts │ │ │ ├── aws3_p.ts │ │ │ ├── aws_p.d.ts │ │ │ ├── aws_p.js │ │ │ ├── call_capturer.js │ │ │ ├── http_p.d.ts │ │ │ ├── http_p.js │ │ │ ├── promise_p.d.ts │ │ │ └── promise_p.js │ │ ├── resources │ │ │ ├── aws_whitelist.json │ │ │ └── default_sampling_rules.json │ │ ├── segment_emitter.d.ts │ │ ├── segment_emitter.js │ │ ├── segments │ │ │ ├── attributes │ │ │ │ ├── aws.d.ts │ │ │ │ ├── aws.js │ │ │ │ ├── captured_exception.js │ │ │ │ ├── remote_request_data.js │ │ │ │ ├── subsegment.d.ts │ │ │ │ ├── subsegment.js │ │ │ │ ├── trace_id.d.ts │ │ │ │ └── trace_id.js │ │ │ ├── plugins │ │ │ │ ├── ec2_plugin.d.ts │ │ │ │ ├── ec2_plugin.js │ │ │ │ ├── ecs_plugin.d.ts │ │ │ │ ├── ecs_plugin.js │ │ │ │ ├── elastic_beanstalk_plugin.d.ts │ │ │ │ ├── elastic_beanstalk_plugin.js │ │ │ │ └── plugin.js │ │ │ ├── segment.d.ts │ │ │ ├── segment.js │ │ │ ├── segment_utils.d.ts │ │ │ └── segment_utils.js │ │ ├── utils.d.ts │ │ └── utils.js │ ├── package.json │ ├── test-d │ │ └── index.test-d.ts │ ├── test │ │ ├── integration │ │ │ └── segment_maintained_across_shared_promise.test.js │ │ ├── resources │ │ │ ├── custom_sampling.json │ │ │ └── custom_whitelist.json │ │ └── unit │ │ │ ├── aws-xray.test.js │ │ │ ├── capture.test.js │ │ │ ├── context_utils.test.js │ │ │ ├── daemon_config.test.js │ │ │ ├── env │ │ │ ├── aws_lambda.test.js │ │ │ └── sqs_message_helper.test.js │ │ │ ├── logger.test.js │ │ │ ├── middleware │ │ │ ├── incoming_request_data.test.js │ │ │ └── mw_utils.test.js │ │ │ ├── patchers │ │ │ ├── aws3_p.test.js │ │ │ ├── aws_p.test.js │ │ │ ├── call_capturer.test.js │ │ │ ├── http_p.test.js │ │ │ └── promise_p.test.js │ │ │ ├── sampling │ │ │ ├── local_reservoir.test.js │ │ │ ├── local_sampler.test.js │ │ │ ├── rule_cache.test.js │ │ │ └── service_connector.test.js │ │ │ ├── segment_emitter.test.js │ │ │ ├── segments │ │ │ ├── attributes │ │ │ │ ├── aws.test.js │ │ │ │ ├── captured_exception.test.js │ │ │ │ ├── remote_request_data.test.js │ │ │ │ ├── subsegment.test.js │ │ │ │ └── trace_id.test.js │ │ │ ├── plugins │ │ │ │ ├── ec2_plugin.test.js │ │ │ │ ├── ecs_plugin.test.js │ │ │ │ ├── elastic_beanstalk_plugin.test.js │ │ │ │ └── plugin.test.js │ │ │ ├── segment.test.js │ │ │ └── segment_utils.test.js │ │ │ ├── test_utils.js │ │ │ └── utils.test.js │ ├── test_async │ │ └── integration │ │ │ └── segment_maintained_across_awaited.test.js │ └── tsconfig.json ├── express │ ├── .eslintrc.json │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── NOTICE.txt │ ├── README.md │ ├── lib │ │ ├── express_mw.d.ts │ │ ├── express_mw.js │ │ ├── index.d.ts │ │ └── index.js │ ├── package.json │ ├── test-d │ │ └── index.test-d.ts │ ├── test │ │ ├── test_utils.js │ │ └── unit │ │ │ └── express_mw.test.js │ └── tsconfig.json ├── full_sdk │ ├── .eslintrc.json │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── NOTICE.txt │ ├── README.md │ ├── lib │ │ ├── index.d.ts │ │ └── index.js │ ├── package.json │ ├── test-d │ │ └── index.test-d.ts │ └── tsconfig.json ├── mysql │ ├── .eslintrc.json │ ├── .npmignore │ ├── LICENSE │ ├── NOTICE.txt │ ├── README.md │ ├── lib │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── mysql_p.d.ts │ │ └── mysql_p.js │ ├── package.json │ ├── test-d │ │ └── index.test-d.ts │ ├── test │ │ ├── test_utils.js │ │ └── unit │ │ │ └── mysql_p.test.js │ └── tsconfig.json ├── postgres │ ├── .eslintrc.json │ ├── .npmignore │ ├── LICENSE │ ├── NOTICE.txt │ ├── README.md │ ├── lib │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── postgres_p.d.ts │ │ └── postgres_p.js │ ├── package.json │ ├── test-d │ │ └── index.test-d.ts │ ├── test │ │ ├── test_utils.js │ │ └── unit │ │ │ └── postgres_p.test.js │ └── tsconfig.json ├── restify │ ├── .eslintrc.json │ ├── .npmignore │ ├── LICENSE │ ├── NOTICE.txt │ ├── README.md │ ├── lib │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── restify_mw.d.ts │ │ └── restify_mw.js │ ├── package.json │ ├── test-d │ │ └── index.test-d.ts │ ├── test │ │ ├── test_utils.js │ │ └── unit │ │ │ └── restify_mw.test.js │ └── tsconfig.json └── test_express │ ├── .eslintrc.json │ ├── package.json │ └── test │ ├── aws.js │ ├── express.js │ ├── helpers.js │ └── shaky_stream.js ├── scripts └── cp-with-structure.sh ├── sdk_contrib ├── fastify │ ├── .eslintignore │ ├── .eslintrc.json │ ├── LICENSE │ ├── NOTICE.txt │ ├── README.md │ ├── lib │ │ ├── hooks │ │ │ ├── on-error.hook.js │ │ │ ├── on-request.hook.js │ │ │ └── on-response.hook.js │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── plugin.d.ts │ │ ├── plugin.js │ │ └── private │ │ │ └── configure-aws-x-ray-sync.js │ ├── package.json │ ├── sample │ │ ├── index.js │ │ └── xray-plugin.js │ ├── test-d │ │ └── index.test-d.ts.js │ └── test │ │ ├── .eslintrc │ │ └── unit │ │ └── xray.test.js ├── fetch │ ├── .eslintignore │ ├── .eslintrc.json │ ├── LICENSE │ ├── NOTICE.txt │ ├── README.md │ ├── lib │ │ ├── fetch_p.d.ts │ │ ├── fetch_p.js │ │ ├── subsegment_fetch.d.ts │ │ └── subsegment_fetch.js │ ├── package.json │ ├── test-d │ │ └── index.test-d.ts │ ├── test │ │ ├── global.js │ │ ├── integration │ │ │ └── fetch_p.test.js │ │ └── unit │ │ │ └── fetch_p.test.js │ └── tsconfig.json ├── hapi │ ├── .eslintignore │ ├── .eslintrc.json │ ├── LICENSE │ ├── NOTICE.txt │ ├── README.md │ ├── lib │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── plugin.js │ │ └── xray.js │ ├── package.json │ ├── sample │ │ ├── index.js │ │ └── xray-plugin.js │ ├── test-d │ │ └── index.test-d.ts.js │ └── test │ │ ├── .eslintrc │ │ └── unit │ │ └── xray.test.js └── koa │ ├── .eslintrc.json │ ├── LICENSE │ ├── NOTICE.txt │ ├── README.md │ ├── lib │ ├── index.d.ts │ ├── index.js │ ├── koa_mw.d.ts │ └── koa_mw.js │ ├── package.json │ ├── test-d │ └── index.test-d.ts.js │ └── test │ ├── .eslintrc │ ├── test_utils.js │ └── unit │ └── koa_mw.test.js └── smoke_test ├── package.json └── test └── smoke.test.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "mocha": true, 5 | "es6": true 6 | }, 7 | "parser": "@typescript-eslint/parser", 8 | "extends": [ 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:import/recommended", 11 | "plugin:import/typescript" 12 | ], 13 | "rules": { 14 | "indent": [ 15 | "error", 16 | 2 17 | ], 18 | "curly": [ 19 | "error", 20 | "all" 21 | ], 22 | "space-before-blocks": "error", 23 | "no-console": "off", 24 | "brace-style": "error", 25 | "keyword-spacing": "error", 26 | "import/no-unresolved": "off", 27 | "linebreak-style": [ 28 | "error", 29 | "unix" 30 | ], 31 | "quotes": [ 32 | "error", 33 | "single" 34 | ], 35 | "semi": [ 36 | "error", 37 | "always" 38 | ], 39 | "eol-last": [ 40 | "error", 41 | "always" 42 | ], 43 | "no-trailing-spaces": "error", 44 | 45 | /** Turn off enforcement */ 46 | "@typescript-eslint/ban-types": "off", 47 | "@typescript-eslint/ban-ts-comment": "off", 48 | "@typescript-eslint/no-var-requires": "off", 49 | "@typescript-eslint/no-empty-function": "off", 50 | "@typescript-eslint/no-empty-interface": "off", 51 | "@typescript-eslint/no-explicit-any": "off", 52 | "@typescript-eslint/explicit-module-boundary-types": "off", 53 | "prefer-rest-params": "off", 54 | "@typescript-eslint/no-non-null-assertion": "off" 55 | }, 56 | "parserOptions": { 57 | "ecmaVersion": 2020 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | ##################################################### 2 | # 3 | # List of approvers for this repository 4 | # 5 | ##################################################### 6 | # 7 | # Learn about CODEOWNERS file format: 8 | # https://help.github.com/en/articles/about-code-owners 9 | # 10 | 11 | * @aws/aws-x-ray 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 30 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Limit to only `issues` or `pulls` 6 | only: issues 7 | # Issues with these labels will never be considered stale 8 | exemptLabels: 9 | - pinned 10 | - bug 11 | - enhancement 12 | - feature-request 13 | - help wanted 14 | - work-in-progress 15 | - pending release 16 | # Label to use when marking an issue as stale 17 | staleLabel: stale 18 | # Comment to post when marking an issue as stale. Set to `false` to disable 19 | markComment: > 20 | This issue has been automatically marked as stale because it has not had 21 | recent activity. It will be closed if no further activity occurs in next 7 days. Thank you 22 | for your contributions. 23 | # Comment to post when closing a stale issue. Set to `false` to disable 24 | closeComment: false 25 | -------------------------------------------------------------------------------- /.github/workflows/continuous-monitoring.yml: -------------------------------------------------------------------------------- 1 | name: Continuous monitoring of distribution channels 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0/10 * * * *' 7 | 8 | permissions: 9 | id-token: write 10 | contents: read 11 | 12 | jobs: 13 | smoke-tests: 14 | name: Run smoke tests 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout AWS XRay SDK Node Repository @ default branch latest 18 | uses: actions/checkout@v2 19 | 20 | - name: Configure AWS Credentials 21 | uses: aws-actions/configure-aws-credentials@v4 22 | with: 23 | role-to-assume: ${{ secrets.AWS_INTEG_TEST_ROLE_ARN }} 24 | aws-region: us-east-1 25 | 26 | - name: Setup Node 27 | uses: actions/setup-node@v1 28 | with: 29 | node-version: '16.x' 30 | 31 | - name: Run smoke test 32 | id: distribution-availability 33 | run: | 34 | cd smoke_test 35 | npm install 36 | npm run test-smoke 37 | 38 | - name: Publish metric on X-Ray Node SDK distribution availability 39 | if: ${{ always() }} 40 | run: | 41 | if [[ "${{ steps.distribution-availability.outcome }}" == "failure" ]]; then 42 | aws cloudwatch put-metric-data --metric-name XRayNodeSDKDistributionUnavailability --dimensions failure=rate --namespace MonitorSDK --value 1 --timestamp $(date +%s) 43 | else 44 | aws cloudwatch put-metric-data --metric-name XRayNodeSDKDistributionUnavailability --dimensions failure=rate --namespace MonitorSDK --value 0 --timestamp $(date +%s) 45 | fi 46 | -------------------------------------------------------------------------------- /.github/workflows/deprecate_version.yml: -------------------------------------------------------------------------------- 1 | name: Deprecate X-Ray Node SDK Version 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | deprecate_xray_node_sdk_version: 7 | name: Deprecate X-Ray Node SDK version in NPM registry 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Setup Node 11 | uses: actions/setup-node@v1 12 | with: 13 | node-version: '16.x' 14 | registry-url: 'https://registry.npmjs.org' 15 | 16 | - run: npm install -g npm@8.19.4 17 | 18 | - name: Deprecate Version 3.7.0 19 | run: | 20 | npm deprecate aws-xray-sdk@3.7.0 "3.7.0 is deprecated due to known issue in Lambda" 21 | npm deprecate aws-xray-sdk-core@3.7.0 "3.7.0 is deprecated due to known issue in Lambda" 22 | npm deprecate aws-xray-sdk-express@3.7.0 "3.7.0 is deprecated due to known issue in Lambda" 23 | npm deprecate aws-xray-sdk-postgres@3.7.0 "3.7.0 is deprecated due to known issue in Lambda" 24 | npm deprecate aws-xray-sdk-mysql@3.7.0 "3.7.0 is deprecated due to known issue in Lambda" 25 | npm deprecate aws-xray-sdk-restify@3.7.0 "3.7.0 is deprecated due to known issue in Lambda" 26 | npm deprecate aws-xray-sdk-hapi@3.7.0 "3.7.0 is deprecated due to known issue in Lambda" 27 | npm deprecate aws-xray-sdk-koa2@3.7.0 "3.7.0 is deprecated due to known issue in Lambda" 28 | npm deprecate aws-xray-sdk-fastify@3.7.0 "3.7.0 is deprecated due to known issue in Lambda" 29 | npm deprecate aws-xray-sdk-fetch@3.7.0 "3.7.0 is deprecated due to known issue in Lambda" 30 | env: 31 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | lint: 11 | name: Check code style 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout AWS XRay SDK Node Repository @ default branch latest 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup Node 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 16.x 21 | 22 | - run: npm install -g npm@8.19.4 23 | 24 | - name: Cache NPM modules 25 | uses: actions/cache@v3 26 | with: 27 | path: | 28 | node_modules 29 | package-lock.json 30 | packages/*/node_modules 31 | packages/*/package-lock.json 32 | key: lint-${{ runner.os }}-${{ hashFiles('package.json', 'packages/*/package.json') }}-06142023 33 | 34 | - name: Bootstrap 35 | run: | 36 | npm ci 37 | npx lerna bootstrap --no-ci --hoist 38 | 39 | - name: Lint 40 | run: npx lerna run lint 41 | -------------------------------------------------------------------------------- /.github/workflows/pr-build.yml: -------------------------------------------------------------------------------- 1 | name: Node.js SDK Continuous Build 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | name: Build Node ${{ matrix.node-version }} on ${{ matrix.os }} 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: 17 | - macos-latest 18 | - ubuntu-latest 19 | - windows-latest 20 | node-version: 21 | - 14.x 22 | - 16.x 23 | - 18.x 24 | - 20.x 25 | include: 26 | - os: ubuntu-latest 27 | node-version: 16.x 28 | coverage: true 29 | - os: macos-13 30 | node-version: 14.x 31 | exclude: 32 | # Issue with npm6 on windows resulting in failing workflows: 33 | # https://github.com/npm/cli/issues/4341#issuecomment-1040608101 34 | # Since node14 is EOL, we can drop this set from our tests. 35 | # We still test node14 on other platforms. 36 | - os: windows-latest 37 | node-version: 14.x 38 | # https://github.com/actions/runner-images/issues/9741 39 | # macos-latest provides only ARM hosts 40 | # https://github.com/nodejs/node/issues/36161 41 | # https://github.com/nodejs/node/issues/40126 42 | # Without workarounds, Node.js 14 isn't supported on ARM macos 43 | # As workaround, test on macos-13 version instead 44 | - os: macos-latest 45 | node-version: 14.x 46 | 47 | steps: 48 | - name: Checkout AWS XRay SDK Node Repository @ default branch latest 49 | uses: actions/checkout@v3 50 | 51 | - name: Setup Node ${{ matrix.node-version }} 52 | uses: actions/setup-node@v3 53 | with: 54 | node-version: ${{ matrix.node-version }} 55 | check-latest: true 56 | 57 | - run: npm install -g npm@8.19.4 58 | 59 | - name: Cache NPM modules 60 | uses: actions/cache@v3 61 | with: 62 | path: | 63 | node_modules 64 | package-lock.json 65 | packages/*/node_modules 66 | packages/*/package-lock.json 67 | key: ${{ matrix.os }}-${{ matrix.node-version }}-${{ hashFiles('package.json', 'packages/*/package.json') }}-06142023 68 | 69 | - name: Bootstrap 70 | run: | 71 | npm ci 72 | npx lerna bootstrap --no-ci --hoist 73 | 74 | - name: Build 75 | run: | 76 | npx lerna run compile 77 | shell: bash 78 | 79 | - name: Execute tests with Lerna 80 | if: '!matrix.coverage' 81 | run: | 82 | npx lerna run test 83 | shell: bash 84 | 85 | # Only need to report coverage once, so only run instrumented tests on one Node version/OS 86 | # Use lerna to get reports from all packages 87 | - name: Report coverage 88 | if: matrix.coverage 89 | run: | 90 | npx lerna run testcov 91 | npx lerna run reportcov 92 | env: 93 | CI: true 94 | - name: Upload coverage to Codecov 95 | uses: codecov/codecov-action@v4 96 | if: matrix.coverage 97 | with: 98 | directory: ./packages/core/ 99 | token: ${{ secrets.CODECOV_TOKEN }} 100 | files: ./coverage.lcov 101 | verbose: true 102 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release X-Ray Node SDK to Github and NPM 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: The version to tag the release with, e.g., 1.2.0, 1.2.1-alpha.1 7 | required: true 8 | 9 | jobs: 10 | publish_xray_node_sdk: 11 | name: Test and publish X-Ray Node SDK to NPM registry and Github 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout Repository 15 | uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Setup Node 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: '16.x' 23 | registry-url: 'https://registry.npmjs.org' 24 | 25 | - run: npm install -g npm@8.19.4 26 | 27 | - name: Cache NPM modules 28 | uses: actions/cache@v3 29 | with: 30 | path: | 31 | node_modules 32 | package-lock.json 33 | packages/*/node_modules 34 | packages/*/package-lock.json 35 | key: release-ubuntu-latest-${{ hashFiles('package.json', 'packages/*/package.json') }}-06142023 36 | 37 | - name: Bootstrap 38 | run: | 39 | npm ci 40 | npx lerna bootstrap --no-ci --hoist 41 | 42 | - name: Build 43 | run: | 44 | npx lerna run compile 45 | shell: bash 46 | 47 | - name: Execute tests with Lerna 48 | run: | 49 | npx lerna run test 50 | shell: bash 51 | 52 | - name: Publish package to npm 53 | run: npx lerna publish from-package --yes --exact --no-verify-access 54 | env: 55 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 56 | 57 | - name: Create Release 58 | id: create_release 59 | uses: actions/create-release@v1 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | with: 63 | tag_name: 'aws-xray-sdk-node@${{ github.event.inputs.version }}' 64 | release_name: 'Release ${{ github.event.inputs.version }}' 65 | body: 'Please see [CHANGELOG](https://github.com/aws/aws-xray-sdk-node/blob/master/CHANGELOG.md) for details.' 66 | draft: true 67 | prerelease: false 68 | -------------------------------------------------------------------------------- /.github/workflows/smoke-test.yml: -------------------------------------------------------------------------------- 1 | name: Node.js Continuous Integration Smoke Test 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | 7 | jobs: 8 | smoke-test: 9 | name: Run smoke test 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout AWS XRay SDK Node Repository @ default branch latest 13 | uses: actions/checkout@v2 14 | 15 | - name: Setup Node 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: '16.x' 19 | 20 | # Use npm pack to bundle individual packages into their final distribution tarball form 21 | # Then aggregate all of those tarballs in the full_sdk package, to be used by smoke test 22 | - name: Pack SDK packages 23 | id: package 24 | run: | 25 | npm install typescript 26 | core_tar=$(cd packages/core && npm pack | tail -1) 27 | express_tar=$(cd packages/express && npm pack | tail -1) 28 | mysql_tar=$(cd packages/mysql && npm pack | tail -1) 29 | postgres_tar=$(cd packages/postgres && npm pack | tail -1) 30 | cd packages/full_sdk 31 | npm install ../core/$core_tar 32 | npm install ../express/$express_tar 33 | npm install ../mysql/$mysql_tar 34 | npm install ../postgres/$postgres_tar 35 | full_tar=$(npm pack | tail -1) 36 | echo "::set-output name=full_sdk::$full_tar" 37 | 38 | # Manually install the locally bundled packages first, then pull in other test dependencies from NPM 39 | # Remove the entire source code directory to force smoke test to depend on packed tarball 40 | - name: Run smoke test 41 | run: | 42 | cd smoke_test 43 | npm install ../packages/full_sdk/${{ steps.package.outputs.full_sdk }} 44 | npm install 45 | rm -rf ../packages/ 46 | npm run test-smoke 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | docs 4 | .idea/ 5 | .nyc_output/ 6 | *.lcov 7 | dist 8 | -------------------------------------------------------------------------------- /.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "include": [ 4 | "**/dist/lib/**/*.js" 5 | ], 6 | "exclude": [ 7 | "**/*.d.ts", 8 | "**/sample/**", 9 | "**/test/**", 10 | "**/test-d/**", 11 | "**/test_async/**", 12 | "**/docs/**" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws/aws-xray-sdk-node/issues), or [recently closed](https://github.com/aws/aws-xray-sdk-node/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional documents on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws/aws-xray-sdk-node/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws/aws-xray-sdk-node/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /images/example_servicemap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-xray-sdk-node/f17e750e785bd1144bfb282055b12707ca0f3727/images/example_servicemap.png -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "3.15.0", 3 | "packages": [ 4 | ".", 5 | "packages/*", 6 | "sdk_contrib/*" 7 | ], 8 | "version": "independent", 9 | "command": { 10 | "publish": { 11 | "allowBranch": ["master", "2.x"] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-xray-sdk-node", 3 | "version": "3.10.3", 4 | "private": true, 5 | "license": "Apache-2.0", 6 | "overrides": { 7 | "cls-hooked": { 8 | "semver": "^7.5.3" 9 | } 10 | }, 11 | "devDependencies": { 12 | "@hapi/hapi": "^20.0.0", 13 | "@smithy/config-resolver": "^3.0.5", 14 | "@smithy/middleware-stack": "^3.0.3", 15 | "@smithy/node-config-provider": "^3.1.4", 16 | "@smithy/smithy-client": "^3.1.7", 17 | "@types/chai": "^4.2.12", 18 | "@types/koa": "^2.11.3", 19 | "@types/mocha": "^8.0.0", 20 | "@types/node": "^10.17.28", 21 | "@types/sinon": "^9.0.4", 22 | "@types/sinon-chai": "^3.2.4", 23 | "@typescript-eslint/eslint-plugin": "^4.25.0", 24 | "@typescript-eslint/parser": "^4.25.0", 25 | "aws-sdk": "^2.304.0", 26 | "aws-xray-sdk-core": "3.10.3", 27 | "aws-xray-sdk-express": "3.10.3", 28 | "chai": "^4.2.0", 29 | "cls-hooked": "^4.2.2", 30 | "codecov": "^3.8.3", 31 | "eslint": "^7.5.0", 32 | "eslint-config-prettier": "^6.11.0", 33 | "eslint-config-semistandard": "^15.0.1", 34 | "eslint-config-standard": "^14.1.1", 35 | "eslint-plugin-import": "^2.23.3", 36 | "eslint-plugin-mocha": "^7.0.1", 37 | "eslint-plugin-node": "^11.1.0", 38 | "eslint-plugin-prettier": "^3.1.4", 39 | "eslint-plugin-promise": "^4.2.1", 40 | "eslint-plugin-standard": "^4.0.1", 41 | "express": "^4.16.4", 42 | "fastify": "^4.0.1", 43 | "fastify-plugin": "^4.2.0", 44 | "grunt": "^1.0.4", 45 | "grunt-contrib-clean": "^1.0.0", 46 | "grunt-jsdoc": "^2.4.0", 47 | "koa": "^2.13.0", 48 | "lerna": "^6.6.2", 49 | "mocha": "^10.2.0", 50 | "nock": "^13.2.9", 51 | "nyc": "^15.1.0", 52 | "prettier": "^2.0.5", 53 | "rewire": "^4.0.1", 54 | "sinon": "^9.0.2", 55 | "sinon-chai": "^3.5.0", 56 | "tsd": "^0.25.0", 57 | "typescript": "^4.4.4", 58 | "upath": "^1.2.0" 59 | }, 60 | "engines": { 61 | "node": ">= 14.x", 62 | "npm": ">= 2.x" 63 | }, 64 | "dependencies": { 65 | "aws-xray-sdk": "file:packages/full_sdk", 66 | "aws-xray-sdk-core": "file:packages/core", 67 | "aws-xray-sdk-express": "file:packages/express", 68 | "aws-xray-sdk-fastify": "file:sdk_contrib/fastify", 69 | "aws-xray-sdk-fetch": "file:sdk_contrib/fetch", 70 | "aws-xray-sdk-hapi": "file:sdk_contrib/hapi", 71 | "aws-xray-sdk-koa2": "file:sdk_contrib/koa", 72 | "aws-xray-sdk-mysql": "file:packages/mysql", 73 | "aws-xray-sdk-node": "file:", 74 | "aws-xray-sdk-postgres": "file:packages/postgres", 75 | "aws-xray-sdk-restify": "file:packages/restify", 76 | "test-aws-xray-sdk-express": "file:packages/test_express" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/core/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /packages/core/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/core/.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | node_modules 3 | npm-debug.log 4 | docs 5 | AWSXRay.log 6 | Config 7 | fat_sdk 8 | core 9 | express 10 | mysql 11 | postgres 12 | -------------------------------------------------------------------------------- /packages/core/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | // Project configuration. 3 | grunt.initConfig({ 4 | jsdoc: { 5 | dist: { 6 | src: ['lib/**/*.js', 'README.md'], 7 | dest: 'docs', 8 | options: { 9 | configure: 'jsdoc_conf.json' 10 | } 11 | } 12 | }, 13 | clean: { 14 | folder: ['docs'] 15 | } 16 | }); 17 | 18 | // Register jsdoc as a grunt task 19 | grunt.loadNpmTasks('grunt-jsdoc'); 20 | grunt.loadNpmTasks('grunt-contrib-clean'); 21 | 22 | grunt.registerTask('docs', ['clean', 'jsdoc']); 23 | }; 24 | -------------------------------------------------------------------------------- /packages/core/NOTICE.txt: -------------------------------------------------------------------------------- 1 | AWS X-Ray SDK Core for JavaScript 2 | Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | This product includes software developed at 5 | Amazon Web Services, Inc. (http://aws.amazon.com/). 6 | -------------------------------------------------------------------------------- /packages/core/doc-src/templates/layout.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: <?js= title ?> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |

19 | 20 | 21 |
22 | 23 | 26 | 27 |
28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /packages/core/jsdoc_conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": { 3 | "default": { 4 | "layoutFile": "doc-src/templates/layout.tmpl" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/lib/aws-xray.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import * as ec2Plugin from './segments/plugins/ec2_plugin'; 3 | import * as ecsPlugin from './segments/plugins/ecs_plugin'; 4 | import * as elasticBeanstalkPlugin from './segments/plugins/elastic_beanstalk_plugin'; 5 | import * as segmentUtils from './segments/segment_utils'; 6 | import * as utils from './utils'; 7 | import * as middleware from './middleware/mw_utils'; 8 | import Segment = require('./segments/segment'); 9 | import Subsegment = require('./segments/attributes/subsegment'); 10 | import sqlData = require('./database/sql_data'); 11 | import TraceID = require('./segments/attributes/trace_id'); 12 | 13 | export namespace plugins { 14 | const EC2Plugin: typeof ec2Plugin; 15 | const ECSPlugin: typeof ecsPlugin; 16 | const ElasticBeanstalkPlugin: typeof elasticBeanstalkPlugin; 17 | 18 | type EC2Plugin = typeof ec2Plugin; 19 | type ECSPlugin = typeof ecsPlugin; 20 | type ElasticBeanstalkPlugin = typeof elasticBeanstalkPlugin; 21 | 22 | type EC2Metadata = ec2Plugin.EC2Metadata; 23 | type ECSMetadata = ecsPlugin.ECSMetadata; 24 | type ElasticBeanstalkMetadata = elasticBeanstalkPlugin.ElasticBeanstalkMetadata; 25 | 26 | type Plugin = EC2Plugin | ECSPlugin | ElasticBeanstalkPlugin; 27 | } 28 | 29 | export function config(plugins: plugins.Plugin[]): void; 30 | 31 | export { appendAWSWhitelist, setAWSWhitelist } from './segments/attributes/aws'; 32 | 33 | export { setStreamingThreshold } from './segments/segment_utils'; 34 | 35 | export { setLogger, getLogger, Logger } from './logger'; 36 | 37 | export { setDaemonAddress } from './daemon_config'; 38 | 39 | export { captureAsyncFunc, captureCallbackFunc, captureFunc } from './capture'; 40 | 41 | export { captureAWS, captureAWSClient } from './patchers/aws_p'; 42 | 43 | export { captureAWSClient as captureAWSv3Client } from './patchers/aws3_p'; 44 | 45 | export { captureHTTPs, captureHTTPsGlobal } from './patchers/http_p'; 46 | 47 | export { capturePromise } from './patchers/promise_p'; 48 | 49 | export { utils }; 50 | 51 | export namespace database { 52 | const SqlData: typeof sqlData; 53 | type SqlData = sqlData; 54 | } 55 | 56 | export { middleware }; 57 | 58 | export { 59 | getNamespace, 60 | resolveSegment, 61 | resolveManualSegmentParams, 62 | getSegment, 63 | setSegment, 64 | isAutomaticMode, 65 | enableAutomaticMode, 66 | enableManualMode, 67 | setContextMissingStrategy 68 | } from './context_utils'; 69 | 70 | export { 71 | Segment, 72 | Subsegment, 73 | TraceID, 74 | segmentUtils as SegmentUtils 75 | }; 76 | 77 | export type SegmentLike = Segment | Subsegment; 78 | -------------------------------------------------------------------------------- /packages/core/lib/capture.d.ts: -------------------------------------------------------------------------------- 1 | import Segment = require('./segments/segment'); 2 | import Subsegment = require('./segments/attributes/subsegment'); 3 | 4 | export function captureFunc(name: string, fcn: (subsegment?: Subsegment) => T, parent?: Segment | Subsegment): T; 5 | 6 | export function captureAsyncFunc( 7 | name: string, 8 | fcn: (subsegment?: Subsegment) => T, 9 | parent?: Segment | Subsegment 10 | ): T; 11 | 12 | export function captureCallbackFunc( 13 | name: string, 14 | fcn: (...args: S) => T, 15 | parent?: Segment | Subsegment 16 | ): (...args: S) => T; 17 | -------------------------------------------------------------------------------- /packages/core/lib/context_utils.d.ts: -------------------------------------------------------------------------------- 1 | import { Namespace } from 'cls-hooked'; 2 | import Segment = require('./segments/segment'); 3 | import Subsegment = require('./segments/attributes/subsegment'); 4 | 5 | export function getNamespace(): Namespace; 6 | 7 | export function resolveSegment(segment?: Segment | Subsegment | null): Segment | Subsegment | undefined; 8 | 9 | export function resolveManualSegmentParams(segment?: Segment | Subsegment | null): Segment | Subsegment | undefined; 10 | 11 | export function getSegment(): Segment | Subsegment | undefined; 12 | 13 | export function setSegment(segment: Segment | Subsegment): void; 14 | 15 | export function isAutomaticMode(): boolean; 16 | 17 | export function enableAutomaticMode(): void; 18 | 19 | export function enableManualMode(): void; 20 | 21 | export type ContextMissingStrategy = 'LOG_ERROR' | 'RUNTIME_ERROR' | 'IGNORE_ERROR' | ((msg: string) => void); 22 | 23 | export function setContextMissingStrategy(strategy: ContextMissingStrategy): void; 24 | -------------------------------------------------------------------------------- /packages/core/lib/daemon_config.d.ts: -------------------------------------------------------------------------------- 1 | export function setDaemonAddress(address: string): void; 2 | -------------------------------------------------------------------------------- /packages/core/lib/daemon_config.js: -------------------------------------------------------------------------------- 1 | var logger = require('./logger'); 2 | 3 | /** 4 | * A module representing the X-Ray daemon configuration including the udp and tcp addresses. 5 | * @module DaemonConfig 6 | */ 7 | var DaemonConfig = { 8 | udp_ip: '127.0.0.1', 9 | udp_port: 2000, 10 | tcp_ip: '127.0.0.1', 11 | tcp_port: 2000, 12 | 13 | setDaemonAddress: function setDaemonAddress(address) { 14 | if (!process.env.AWS_XRAY_DAEMON_ADDRESS) { 15 | processAddress(address); 16 | logger.getLogger().info('Configured daemon address to ' + address + '.'); 17 | } else { 18 | logger.getLogger().warn('Ignoring call to setDaemonAddress as AWS_XRAY_DAEMON_ADDRESS is set. '+ 19 | 'The current daemon address will not be changed.'); 20 | } 21 | } 22 | }; 23 | 24 | var processAddress = function processAddress(address) { 25 | if (address.indexOf(':') === -1) { 26 | throw new Error('Invalid Daemon Address. You must specify an ip and port.'); 27 | } else { 28 | var splitAddress = address.split(' '); 29 | if (splitAddress.length === 1) { 30 | // in format of 127.0.0.1:2000 31 | if (address.indexOf('udp') > -1 || address.indexOf('tcp') > -1) { 32 | throw new Error('Invalid Daemon Address. You must specify both tcp and udp addresses.'); 33 | } 34 | var addr = address.split(':'); 35 | if (!addr[0]) { 36 | throw new Error('Invalid Daemon Address. You must specify an ip.'); 37 | } 38 | DaemonConfig.udp_ip = addr[0]; 39 | DaemonConfig.tcp_ip = addr[0]; 40 | DaemonConfig.udp_port = addr[1]; 41 | DaemonConfig.tcp_port = addr[1]; 42 | } else if (splitAddress.length === 2) { 43 | // in format of udp:127.0.0.1:2000 tcp:127.0.0.1:2001 44 | var part_1 = splitAddress[0].split(':'); 45 | var part_2 = splitAddress[1].split(':'); 46 | var addr_map = {}; 47 | addr_map[part_1[0]] = part_1; 48 | addr_map[part_2[0]] = part_2; 49 | 50 | DaemonConfig.udp_ip = addr_map['udp'][1]; 51 | DaemonConfig.udp_port = parseInt(addr_map['udp'][2]); 52 | DaemonConfig.tcp_ip = addr_map['tcp'][1]; 53 | DaemonConfig.tcp_port = parseInt(addr_map['tcp'][2]); 54 | 55 | if (!DaemonConfig.udp_port || !DaemonConfig.tcp_port) { 56 | throw new Error('Invalid Daemon Address. You must specify port number.'); 57 | } 58 | } 59 | } 60 | }; 61 | 62 | if (process.env.AWS_XRAY_DAEMON_ADDRESS) { 63 | processAddress(process.env.AWS_XRAY_DAEMON_ADDRESS); 64 | } 65 | module.exports = DaemonConfig; 66 | -------------------------------------------------------------------------------- /packages/core/lib/database/sql_data.d.ts: -------------------------------------------------------------------------------- 1 | declare class SqlData { 2 | database_version?: string; 3 | driver_version?: string; 4 | preparation?: string; 5 | url?: string; 6 | user?: string; 7 | 8 | constructor(databaseVer?: string, driverVer?: string, user?: string, url?: string, queryType?: string); 9 | } 10 | 11 | export = SqlData; 12 | -------------------------------------------------------------------------------- /packages/core/lib/database/sql_data.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a SQL database call. 3 | * @constructor 4 | * @param {string} databaseVer - The version on the database (user supplied). 5 | * @param {string} driverVer - The version on the database driver (user supplied). 6 | * @param {string} user - The user associated to the database call. 7 | * @param {string} queryType - The SQL query type. 8 | */ 9 | 10 | function SqlData(databaseVer, driverVer, user, url, queryType) { 11 | this.init(databaseVer, driverVer, user, url, queryType); 12 | } 13 | 14 | SqlData.prototype.init = function init(databaseVer, driverVer, user, url, queryType) { 15 | if (databaseVer) { 16 | this.database_version = databaseVer; 17 | } 18 | 19 | if (driverVer) { 20 | this.driver_version = driverVer; 21 | } 22 | 23 | if (queryType) { 24 | this.preparation = queryType; 25 | } 26 | 27 | this.url = url; 28 | this.user = user; 29 | }; 30 | 31 | module.exports = SqlData; 32 | -------------------------------------------------------------------------------- /packages/core/lib/env/sqs_message_helper.js: -------------------------------------------------------------------------------- 1 | class SqsMessageHelper { 2 | 3 | static isSampled(message) { 4 | const {attributes} = message; // extract attributes from message 5 | if (!('AWSTraceHeader' in attributes)) { 6 | return false; 7 | } 8 | return attributes['AWSTraceHeader'].includes('Sampled=1'); 9 | } 10 | } 11 | 12 | export default SqsMessageHelper; 13 | -------------------------------------------------------------------------------- /packages/core/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './aws-xray'; 2 | -------------------------------------------------------------------------------- /packages/core/lib/index.js: -------------------------------------------------------------------------------- 1 | // Convenience file to require the SDK from the root of the repository 2 | module.exports = require('./aws-xray'); 3 | -------------------------------------------------------------------------------- /packages/core/lib/logger.d.ts: -------------------------------------------------------------------------------- 1 | export interface Logger { 2 | debug(...args: any[]): any; 3 | info(...args: any[]): any; 4 | warn(...args: any[]): any; 5 | error(...args: any[]): any; 6 | } 7 | 8 | export function setLogger(logObj: Logger): void; 9 | 10 | export function getLogger(): Logger; 11 | -------------------------------------------------------------------------------- /packages/core/lib/logger.js: -------------------------------------------------------------------------------- 1 | var validLogLevels = [ 'debug', 'info', 'warn', 'error', 'silent' ]; 2 | var defaultLogLevel = validLogLevels.indexOf('error'); 3 | var logLevel = calculateLogLevel(process.env.AWS_XRAY_DEBUG_MODE ? 'debug' : process.env.AWS_XRAY_LOG_LEVEL); 4 | 5 | var logger = { 6 | error: createLoggerForLevel('error'), 7 | info: createLoggerForLevel('info'), 8 | warn: createLoggerForLevel('warn'), 9 | debug: createLoggerForLevel('debug'), 10 | }; 11 | 12 | function createLoggerForLevel(level) { 13 | var loggerLevel = validLogLevels.indexOf(level); 14 | var consoleMethod = console[level] || console.log || (() => {}); 15 | 16 | if (loggerLevel >= logLevel) { 17 | return (message, meta) => { 18 | if (message || meta) { 19 | consoleMethod(formatLogMessage(level, message, meta)); 20 | } 21 | }; 22 | } else { 23 | return () => {}; 24 | } 25 | } 26 | 27 | function calculateLogLevel(level) { 28 | if (level) { 29 | var normalisedLevel = level.toLowerCase(); 30 | var index = validLogLevels.indexOf(normalisedLevel); 31 | return index >= 0 ? index : defaultLogLevel; 32 | } 33 | 34 | // Silently ignore invalid log levels, default to default level 35 | return defaultLogLevel; 36 | } 37 | 38 | function createTimestamp(date) { 39 | var tzo = -date.getTimezoneOffset(), // Negate to make this tzo = local - UTC 40 | dif = tzo >= 0 ? '+' : '-', 41 | pad = function(num) { 42 | var norm = Math.floor(Math.abs(num)); 43 | return (norm < 10 ? '0' : '') + norm; 44 | }; 45 | 46 | return new Date(date.getTime() + (tzo * 60 * 1000)).toISOString() 47 | .replace(/T/, ' ') 48 | .replace(/Z/, ' ') + 49 | dif + pad(tzo / 60) + 50 | ':' + pad(tzo % 60); 51 | } 52 | 53 | function isLambdaFunction() { 54 | return process.env.LAMBDA_TASK_ROOT !== undefined; 55 | } 56 | 57 | function formatLogMessage(level, message, meta) { 58 | var messageParts = []; 59 | 60 | if (!isLambdaFunction()) { 61 | messageParts.push(createTimestamp(new Date())); 62 | messageParts.push(`[${level.toUpperCase()}]`); 63 | } 64 | 65 | if (message) { 66 | messageParts.push(message); 67 | } 68 | 69 | var logString = messageParts.join(' '); 70 | var metaDataString = formatMetaData(meta); 71 | return [logString, metaDataString].filter(str => str.length > 0).join('\n '); 72 | } 73 | 74 | function formatMetaData(meta) { 75 | if (!meta) { 76 | return ''; 77 | } 78 | 79 | return ((typeof(meta) === 'string') ? meta : JSON.stringify(meta)); 80 | } 81 | 82 | var logging = { 83 | setLogger: function setLogger(logObj) { 84 | logger = logObj; 85 | }, 86 | 87 | getLogger: function getLogger() { 88 | return logger; 89 | } 90 | }; 91 | 92 | module.exports = logging; 93 | -------------------------------------------------------------------------------- /packages/core/lib/middleware/incoming_request_data.d.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http'; 2 | 3 | declare class IncomingRequestData { 4 | request: { [key: string]: any }; 5 | 6 | constructor(req: http.IncomingMessage); 7 | 8 | close(res: http.ServerResponse): void; 9 | } 10 | 11 | export = IncomingRequestData; 12 | -------------------------------------------------------------------------------- /packages/core/lib/middleware/incoming_request_data.js: -------------------------------------------------------------------------------- 1 | var { getHttpResponseData } = require('../segments/segment_utils'); 2 | 3 | /** 4 | * Represents an incoming HTTP/HTTPS call. 5 | * @constructor 6 | * @param {http.IncomingMessage|https.IncomingMessage} req - The request object from the HTTP/HTTPS call. 7 | */ 8 | 9 | function IncomingRequestData(req) { 10 | this.init(req); 11 | } 12 | 13 | IncomingRequestData.prototype.init = function init(req) { 14 | var forwarded = !!req.headers['x-forwarded-for']; 15 | var url; 16 | 17 | if (req.connection) { 18 | url = ((req.connection.secure || req.connection.encrypted) ? 'https://' : 'http://') + 19 | ((req.headers['host'] || '') + (req.url || '')); 20 | } 21 | 22 | this.request = { 23 | method: req.method || '', 24 | user_agent: req.headers['user-agent'] || '', 25 | client_ip: getClientIp(req) || '', 26 | url: url || '', 27 | }; 28 | 29 | if (forwarded) { 30 | this.request.x_forwarded_for = forwarded; 31 | } 32 | }; 33 | 34 | var getClientIp = function getClientIp(req) { 35 | var clientIp; 36 | 37 | if (req.headers['x-forwarded-for']) { 38 | clientIp = (req.headers['x-forwarded-for'] || '').split(',')[0]; 39 | } else if (req.connection && req.connection.remoteAddress) { 40 | clientIp = req.connection.remoteAddress; 41 | } else if (req.socket && req.socket.remoteAddress) { 42 | clientIp = req.socket.remoteAddress; 43 | } else if (req.connection && req.connection.socket && req.connection.socket.remoteAddress) { 44 | clientIp = req.connection.socket.remoteAddress; 45 | } 46 | 47 | return clientIp; 48 | }; 49 | 50 | /** 51 | * Closes the local and automatically captures the response data. 52 | * @param {http.ServerResponse|https.ServerResponse} res - The response object from the HTTP/HTTPS call. 53 | */ 54 | 55 | IncomingRequestData.prototype.close = function close(res) { 56 | this.response = getHttpResponseData(res); 57 | }; 58 | 59 | module.exports = IncomingRequestData; 60 | -------------------------------------------------------------------------------- /packages/core/lib/middleware/mw_utils.d.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http'; 2 | import Segment = require('../segments/segment'); 3 | import IncomingRequestData = require('./incoming_request_data'); 4 | 5 | export const defaultName: string | undefined; 6 | 7 | export const dynamicNaming: boolean; 8 | 9 | export const hostPattern: string | null; 10 | 11 | export function enableDynamicNaming(hostPattern?: string): void; 12 | 13 | export function processHeaders(req?: Partial>): { [key: string]: string }; 14 | 15 | export function resolveName(hostHeader?: string): string; 16 | 17 | export function resolveSampling( 18 | amznTraceHeader: { [key: string]: string }, 19 | segment: Segment, 20 | res: http.ServerResponse 21 | ): void; 22 | 23 | export function setDefaultName(name: string): void; 24 | 25 | export function disableCentralizedSampling(): void; 26 | 27 | export function middlewareLog(message: string, url: string, segment: Segment): void; 28 | 29 | export function traceRequestResponseCycle(req: http.IncomingMessage, res: http.ServerResponse): Segment; 30 | 31 | export interface BaseRuleConfig { 32 | http_method: string; 33 | url_path: string; 34 | fixed_target: number; 35 | rate: number; 36 | description?: string; 37 | } 38 | 39 | export interface RuleConfigV1 extends BaseRuleConfig { 40 | service_name: string; 41 | } 42 | 43 | export interface RuleConfigV2 extends BaseRuleConfig { 44 | host: string; 45 | } 46 | 47 | export type RuleConfig = RuleConfigV1 | RuleConfigV2; 48 | 49 | export interface DefaultRuleConfig { 50 | fixed_target: number; 51 | rate: number; 52 | } 53 | 54 | export interface RulesConfig { 55 | version: number; 56 | default: DefaultRuleConfig; 57 | rules?: RuleConfig[]; 58 | } 59 | 60 | export function setSamplingRules(source: string | RulesConfig): void; 61 | 62 | export { 63 | IncomingRequestData 64 | }; 65 | -------------------------------------------------------------------------------- /packages/core/lib/middleware/sampling/default_sampler.js: -------------------------------------------------------------------------------- 1 | var logger = require('../../logger'); 2 | const util = require('util'); 3 | 4 | var SegmentUtils = require('../../segments/segment_utils'); 5 | /** 6 | * The default sampler used to make sampling decisions when the decisions are absent in the incoming requests. 7 | * The sampler use pollers to poll sampling rules from X-Ray service. 8 | * @module DefaultSampler 9 | */ 10 | var DefaultSampler = { 11 | localSampler: require('./local_sampler'), 12 | rulePoller: require('./rule_poller'), 13 | targetPoller: require('./target_poller'), 14 | ruleCache: require('./rule_cache'), 15 | started: false, 16 | 17 | /** 18 | * Makes a sample decision based on the sample request. 19 | * @param {object} sampleRequest - Contains information for rules matching. 20 | * @module DefaultSampler 21 | * @function shouldSample 22 | */ 23 | shouldSample: function shouldSample(sampleRequest) { 24 | try { 25 | if (!this.started) { 26 | this.start(); 27 | } 28 | if (!sampleRequest.serviceType) { 29 | sampleRequest.serviceType = SegmentUtils.origin; 30 | } 31 | var now = Math.floor(new Date().getTime() / 1000); 32 | var matchedRule = this.ruleCache.getMatchedRule(sampleRequest, now); 33 | if (matchedRule) { 34 | logger.getLogger().debug(util.format('Rule %s is matched.', matchedRule.getName())); 35 | return processMatchedRule(matchedRule, now); 36 | } else { 37 | logger.getLogger().info('No effective centralized sampling rule match. Fallback to local rules.'); 38 | return this.localSampler.shouldSample(sampleRequest); 39 | } 40 | } catch (err) { 41 | logger.getLogger().error('Unhandled exception by the SDK during making sampling decisions: ' + err); 42 | } 43 | }, 44 | 45 | /** 46 | * Set local rules in case there is a need to fallback. 47 | * @module DefaultSampler 48 | * @function setLocalRules 49 | */ 50 | setLocalRules: function setLocalRules(source) { 51 | this.localSampler.setLocalRules(source); 52 | }, 53 | 54 | /** 55 | * Start the pollers to poll sampling rules and targets from X-Ray service. 56 | * @module DefaultSampler 57 | * @function start 58 | */ 59 | start: function start() { 60 | if (!this.started) { 61 | this.rulePoller.start(); 62 | this.targetPoller.start(); 63 | this.started = true; 64 | } 65 | } 66 | }; 67 | 68 | var processMatchedRule = function processMatchedRule(rule, now) { 69 | // As long as a rule is matched we increment request counter. 70 | rule.incrementRequestCount(); 71 | var reservoir = rule.getReservoir(); 72 | var sample = true; 73 | // We check if we can borrow or take from reservoir first. 74 | var decision = reservoir.borrowOrTake(now, rule.canBorrow()); 75 | if (decision === 'borrow') { 76 | rule.incrementBorrowCount(); 77 | } else if (decision === 'take') { 78 | rule.incrementSampledCount(); 79 | } else if (Math.random() <= rule.getRate()) { 80 | // Otherwise we compute based on FixedRate of this sampling rule. 81 | rule.incrementSampledCount(); 82 | } else { 83 | sample = false; 84 | } 85 | 86 | if (sample) { 87 | return rule.getName(); 88 | } else { 89 | return false; 90 | } 91 | }; 92 | 93 | module.exports = DefaultSampler; 94 | -------------------------------------------------------------------------------- /packages/core/lib/middleware/sampling/local_reservoir.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Represents a LocalReservoir object that keeps track of the number of traces per second sampled and 4 | * the fixed rate for a given sampling rule defined locally. 5 | * It also decides if a given trace should be sampled or not based on the state of current second. 6 | * @constructor 7 | * @param {number} fixedTarget - An integer value to specify the maximum number of traces per second to sample. 8 | * @param {number} fallbackRate - A value between 0 and 1 indicating the sampling rate after the maximum traces per second has been hit. 9 | */ 10 | 11 | function LocalReservoir (fixedTarget, fallbackRate) { 12 | this.init(fixedTarget, fallbackRate); 13 | } 14 | 15 | LocalReservoir.prototype.init = function init(fixedTarget, fallbackRate) { 16 | this.usedThisSecond = 0; 17 | 18 | if (typeof fixedTarget === 'number' && fixedTarget % 1 === 0 && fixedTarget >= 0) { 19 | this.fixedTarget = fixedTarget; 20 | } else { 21 | throw new Error('Error in sampling file. Rule attribute "fixed_target" must be a non-negative integer.'); 22 | } 23 | 24 | if (typeof fallbackRate === 'number' && fallbackRate >= 0 && fallbackRate <= 1) { 25 | this.fallbackRate = fallbackRate; 26 | } else { 27 | throw new Error('Error in sampling file. Rule attribute "rate" must be a number between 0 and 1 inclusive.'); 28 | } 29 | }; 30 | 31 | LocalReservoir.prototype.isSampled = function isSampled() { 32 | var now = Math.round(new Date().getTime() / 1000); 33 | 34 | if (now !== this.thisSecond) { 35 | this.usedThisSecond = 0; 36 | this.thisSecond = now; 37 | } 38 | 39 | if (this.usedThisSecond >= this.fixedTarget) { 40 | return Math.random() < this.fallbackRate; 41 | } 42 | 43 | this.usedThisSecond++; 44 | return true; 45 | }; 46 | 47 | module.exports = LocalReservoir; 48 | -------------------------------------------------------------------------------- /packages/core/lib/middleware/sampling/reservoir.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a Reservoir object that keeps track of the number of traces per second sampled and 3 | * the fixed rate for a given sampling rule. This information is fetched from X-Ray serivce. 4 | * It decides if a given trace should be borrowed or sampled or not sampled based on the state of current second. 5 | * @constructor 6 | */ 7 | function Reservoir () { 8 | this.init(); 9 | } 10 | 11 | Reservoir.prototype.init = function init() { 12 | this.quota = null; 13 | this.TTL = null; 14 | this.takenThisSec = 0; 15 | this.borrowedThisSec = 0; 16 | this.reportInterval = 1; 17 | this.reportElapsed = 0; 18 | }; 19 | 20 | Reservoir.prototype.borrowOrTake = function borrowOrTake(now, canBorrow) { 21 | this.adjustThisSec(now); 22 | // Don't borrow if the quota is available and fresh. 23 | if (this.quota >= 0 && this.TTL >= now) { 24 | if (this.takenThisSec >= this.quota) { 25 | return false; 26 | } 27 | 28 | this.takenThisSec++; 29 | return 'take'; 30 | } 31 | 32 | // Otherwise try to borrow if the quota is not present or expired. 33 | if (canBorrow) { 34 | if (this.borrowedThisSec >= 1) { 35 | return false; 36 | } 37 | 38 | this.borrowedThisSec++; 39 | return 'borrow'; 40 | } 41 | }; 42 | 43 | Reservoir.prototype.adjustThisSec = function adjustThisSec(now) { 44 | if (now !== this.thisSec) { 45 | this.takenThisSec = 0; 46 | this.borrowedThisSec = 0; 47 | this.thisSec = now; 48 | } 49 | }; 50 | 51 | Reservoir.prototype.loadNewQuota = function loadNewQuota(quota, TTL, interval) { 52 | if (quota) { 53 | this.quota = quota; 54 | } 55 | if (TTL) { 56 | this.TTL = TTL; 57 | } 58 | if (interval) { 59 | this.reportInterval = interval/10; 60 | } // Report interval is always time of 10. 61 | }; 62 | 63 | Reservoir.prototype.timeToReport = function timeToReport() { 64 | if (this.reportElapsed + 1 >= this.reportInterval) { 65 | this.reportElapsed = 0; 66 | return true; 67 | } else { 68 | this.reportElapsed += 1; 69 | return false; 70 | } 71 | }; 72 | 73 | module.exports = Reservoir; 74 | -------------------------------------------------------------------------------- /packages/core/lib/middleware/sampling/rule_cache.js: -------------------------------------------------------------------------------- 1 | var TTL = 60 * 60; // The cache expires 1 hour after the last refresh time. 2 | 3 | /** 4 | * The rule cache that stores sampling rules fetched from X-Ray service. 5 | * @module RuleCache 6 | */ 7 | var RuleCache = { 8 | rules: [], 9 | lastUpdated: null, 10 | 11 | /** 12 | * Tries to find a valid rule that matches the sample request. 13 | * @param {object} sampleRequest - Contains information for rules matching. 14 | * @param {number} now - Current epoch in seconds. 15 | * @module RuleCache 16 | * @function getMatchedRule 17 | */ 18 | getMatchedRule: function getMatchedRule(sampleRequest, now) { 19 | if (isExpired(now)) { 20 | return null; 21 | } 22 | var matchedRule; 23 | this.rules.forEach(function(rule) { 24 | if (!matchedRule && rule.match(sampleRequest)) { 25 | matchedRule = rule; 26 | } 27 | if (rule.isDefault() && !matchedRule) { 28 | matchedRule = rule; 29 | } 30 | }); 31 | return matchedRule; 32 | }, 33 | 34 | /** 35 | * Load rules fetched from X-Ray service in order sorted by priorities. 36 | * @param {object} rules - Newly fetched rules to load. 37 | * @module RuleCache 38 | * @function loadRules 39 | */ 40 | loadRules: function loadRules(rules) { 41 | // Record the old rules for later merging. 42 | var oldRules = {}; 43 | this.rules.forEach(function(rule) { 44 | oldRules[rule.getName()] = rule; 45 | }); 46 | 47 | // Update the rules in the cache. 48 | this.rules = rules; 49 | 50 | // Transfer state information to refreshed rules. 51 | this.rules.forEach(function(rule) { 52 | var oldRule = oldRules[rule.getName()]; 53 | if (oldRule) { 54 | rule.merge(oldRule); 55 | } 56 | }); 57 | 58 | // The cache should maintain the order of the rules based on 59 | // priority. If priority is the same we sort name by alphabet 60 | // as rule name is unique. 61 | this.rules.sort(function(a, b) { 62 | var v = a.getPriority() - b.getPriority(); 63 | if (v !== 0) { 64 | return v; 65 | } 66 | if (a.getName() > b.getName()) { 67 | return 1; 68 | } else { 69 | return -1; 70 | } 71 | }); 72 | }, 73 | 74 | /** 75 | * Load targets fetched from X-Ray service. 76 | * @param {object} targetsMapping - Newly fetched targets map with rule name as key. 77 | * @module RuleCache 78 | * @function loadTargets 79 | */ 80 | loadTargets: function loadTargets(targetsMapping) { 81 | this.rules.forEach(function(rule) { 82 | var target = targetsMapping[rule.getName()]; 83 | if (target) { 84 | rule.getReservoir().loadNewQuota(target.quota, target.TTL, target.interval); 85 | rule.setRate(target.rate); 86 | } 87 | }); 88 | }, 89 | 90 | getRules: function getRules() { 91 | return this.rules; 92 | }, 93 | 94 | timestamp: function timestamp(now) { 95 | this.lastUpdated = now; 96 | }, 97 | 98 | getLastUpdated: function getLastUpdated() { 99 | return this.lastUpdated; 100 | } 101 | }; 102 | 103 | var isExpired = function isExpired(now) { 104 | // The cache is considered expired if it is never loaded. 105 | if (!RuleCache.getLastUpdated()) { 106 | return true; 107 | } 108 | return now > RuleCache.getLastUpdated() + TTL; 109 | }; 110 | 111 | module.exports = RuleCache; 112 | -------------------------------------------------------------------------------- /packages/core/lib/middleware/sampling/rule_poller.js: -------------------------------------------------------------------------------- 1 | var logger = require('../../logger'); 2 | var ServiceConnector = require('./service_connector'); 3 | var ruleCache = require('./rule_cache'); 4 | var DEFAULT_INTERVAL = 5 * 60 * 1000; // 5 minutes on sampling rules fetch 5 | 6 | /** 7 | * The RulePoller that periodically fetch sampling rules from X-Ray service 8 | * and load them into RuleCache. 9 | * @module RulePoller 10 | */ 11 | var RulePoller = { 12 | 13 | start: function start() { 14 | if (this.poller) { 15 | clearInterval(this.poller); 16 | } 17 | 18 | // Refresh sampling rules cache with no jitter upon start. 19 | refresh(false); 20 | this.poller = setInterval(refresh, DEFAULT_INTERVAL); 21 | this.poller.unref(); 22 | }, 23 | }; 24 | 25 | var refresh = function refresh(jitter) { 26 | // Add jitter by default unless explicitly told not to. 27 | jitter = typeof jitter === 'undefined' ? true : jitter; 28 | 29 | if (jitter) { 30 | var delay = getJitter(); 31 | setTimeout(refreshWithFirewall, delay); 32 | } else { 33 | refreshWithFirewall(); 34 | } 35 | }; 36 | 37 | var refreshWithFirewall = function refreshWithFirewall() { 38 | try { 39 | refreshCache(); 40 | } catch (e) { 41 | logger.getLogger().warn('Encountered unexpected exception when fetching sampling rules: ' + e); 42 | } 43 | }; 44 | 45 | var refreshCache = function refreshCache() { 46 | // Timestamp should be generated *before* the actual outbound call to ensure 47 | // we don't mark the cache as being fresher than it actually is. 48 | var now = Math.floor(new Date().getTime() / 1000); 49 | 50 | // Pass a callback that only runs when the new rules are 51 | // successfully fetched. 52 | ServiceConnector.fetchSamplingRules(function(err, newRules) { 53 | if (err) { 54 | logger.getLogger().warn('Failed to retrieve sampling rules from X-Ray service:', err); 55 | } else if (newRules.length !== 0) { 56 | ruleCache.loadRules(newRules); 57 | ruleCache.timestamp(now); 58 | logger.getLogger().info('Successfully refreshed centralized sampling rule cache.'); 59 | } 60 | }); 61 | }; 62 | 63 | // A random jitter of up to 5 seconds is injected after every run to ensure 64 | // the calls eventually get evenly distributed over the 5 minute window. 65 | var getJitter = function getJitter() { 66 | return Math.random() * 5; 67 | }; 68 | 69 | module.exports = RulePoller; 70 | -------------------------------------------------------------------------------- /packages/core/lib/middleware/sampling/target_poller.js: -------------------------------------------------------------------------------- 1 | var rulePoller = require('./rule_poller'); 2 | var serviceConnector = require('./service_connector'); 3 | var ruleCache = require('./rule_cache'); 4 | var logger = require('../../logger'); 5 | var DEFAULT_INTERVAL = 10 * 1000; // 10 seconds on sampling targets fetch 6 | 7 | 8 | /** 9 | * The TargetPoller that periodically fetch sampling targets from X-Ray service 10 | * and load them into RuleCache. 11 | * @module TargetPoller 12 | */ 13 | var TargetPoller = { 14 | 15 | interval: DEFAULT_INTERVAL, 16 | 17 | start: function start() { 18 | this.poller = setInterval(refreshWithFirewall, DEFAULT_INTERVAL + getJitter()); 19 | this.poller.unref(); 20 | }, 21 | }; 22 | 23 | var refreshWithFirewall = function refreshWithFirewall() { 24 | try { 25 | refresh(); 26 | } catch (e) { 27 | logger.getLogger().warn('Encountered unexpected exception when fetching sampling targets: ' + e); 28 | } 29 | }; 30 | 31 | var refresh = function refresh() { 32 | var candidates = getCandidates(); 33 | if (candidates && candidates.length > 0) { 34 | serviceConnector.fetchTargets(candidates, function(err, targetsMapping, ruleFreshness) { 35 | if (err) { 36 | logger.getLogger().warn('Failed to retrieve sampling targets from X-Ray service:', err); 37 | return; 38 | } 39 | 40 | ruleCache.loadTargets(targetsMapping); 41 | if (ruleFreshness > ruleCache.getLastUpdated()) { 42 | logger.getLogger().info('Performing out-of-band sampling rule polling to fetch updated rules.'); 43 | rulePoller.start(); 44 | } 45 | 46 | logger.getLogger().info('Successfully reported rule statistics to get new sampling quota.'); 47 | }); 48 | } 49 | }; 50 | 51 | // Don't report a rule statistics if any of the conditions is met: 52 | // 1. The report time hasn't come (some rules might have larger report intervals). 53 | // 2. The rule is never matched. 54 | var getCandidates = function getCandidates() { 55 | var rules = ruleCache.getRules(); 56 | 57 | var candidates = []; 58 | rules.forEach(function(rule) { 59 | if (rule.everMatched() && rule.timeToReport()) { 60 | candidates.push(rule); 61 | } 62 | }); 63 | 64 | return candidates; 65 | }; 66 | 67 | // A random jitter of up to 0.1 seconds is injected after every run to ensure 68 | // the calls eventually get evenly distributed over the 10 second window. 69 | var getJitter = function getJitter() { 70 | return Math.random() / TargetPoller.interval; 71 | }; 72 | 73 | module.exports = TargetPoller; 74 | -------------------------------------------------------------------------------- /packages/core/lib/patchers/aws3_p.d.ts: -------------------------------------------------------------------------------- 1 | import { SegmentLike } from '../aws-xray'; 2 | /** 3 | * Instruments AWS SDK V3 clients with X-Ray via middleware. 4 | * 5 | * @param client - AWS SDK V3 client to instrument 6 | * @param manualSegment - Parent segment or subsegment that is passed in for manual mode users 7 | * @returns - the client with the X-Ray instrumentation middleware added to its middleware stack 8 | */ 9 | export declare function captureAWSClient(client: T, manualSegment?: SegmentLike): T 10 | -------------------------------------------------------------------------------- /packages/core/lib/patchers/aws_p.d.ts: -------------------------------------------------------------------------------- 1 | /* The type accepted and returned from patching AWS clients is generic because using types defined 2 | * by the aws-sdk would require us to depend on it, which would make our bundle size too large. 3 | * 4 | * See: https://github.com/aws/aws-xray-sdk-node/pull/255 5 | */ 6 | 7 | export function captureAWS(awssdk: T): T; 8 | 9 | export function captureAWSClient(service: T): T; 10 | -------------------------------------------------------------------------------- /packages/core/lib/patchers/http_p.d.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http'; 2 | import * as https from 'https'; 3 | import { Subsegment } from '../aws-xray'; 4 | 5 | type httpSubsegmentCallback = (subsegment: Subsegment, req: http.ClientRequest, res: http.IncomingMessage | null, error: Error) => void 6 | 7 | export function captureHTTPs(mod: T, downstreamXRayEnabled?: boolean, subsegmentCallback?: httpSubsegmentCallback): T; 8 | 9 | export function captureHTTPsGlobal(mod: typeof https | typeof http, downstreamXRayEnabled?: boolean, subsegmentCallback?: httpSubsegmentCallback): void; 10 | -------------------------------------------------------------------------------- /packages/core/lib/patchers/promise_p.d.ts: -------------------------------------------------------------------------------- 1 | export const capturePromise: { 2 | (): void; 3 | patchThirdPartyPromise(Promise: any): void; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/core/lib/patchers/promise_p.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module promise_p 3 | */ 4 | 5 | /** 6 | * This module patches native Promise libraries provided by V8 engine 7 | * so all subsegments generated within Promise are attached to the correct parent. 8 | */ 9 | 10 | const contextUtils = require('../context_utils'); 11 | 12 | const originalThen = Symbol('original then'); 13 | const originalCatch = Symbol('original catch'); 14 | 15 | function patchPromise(Promise) { 16 | const then = Promise.prototype.then; 17 | if (!then[originalThen]) { 18 | Promise.prototype.then = function(onFulfilled, onRejected) { 19 | if (contextUtils.isAutomaticMode() 20 | && tryGetCurrentSegment() 21 | ) { 22 | const ns = contextUtils.getNamespace(); 23 | 24 | onFulfilled = onFulfilled && ns.bind(onFulfilled); 25 | onRejected = onRejected && ns.bind(onRejected); 26 | } 27 | 28 | return then.call(this, onFulfilled, onRejected); 29 | }; 30 | Promise.prototype.then[originalThen] = then; 31 | } 32 | 33 | const origCatch = Promise.prototype.catch; 34 | if (origCatch && !origCatch[originalCatch]) { 35 | Promise.prototype.catch = function (onRejected) { 36 | if (contextUtils.isAutomaticMode() 37 | && tryGetCurrentSegment() 38 | ) { 39 | const ns = contextUtils.getNamespace(); 40 | 41 | onRejected = onRejected && ns.bind(onRejected); 42 | } 43 | 44 | return origCatch.call(this, onRejected); 45 | }; 46 | Promise.prototype.catch[originalCatch] = origCatch; 47 | } 48 | } 49 | 50 | function unpatchPromise(Promise) { 51 | const then = Promise.prototype.then; 52 | if (then[originalThen]) { 53 | Promise.prototype.then = then[originalThen]; 54 | } 55 | const origCatch = Promise.prototype.catch; 56 | if (origCatch && origCatch[originalCatch]) { 57 | Promise.prototype.catch = origCatch[originalCatch]; 58 | } 59 | } 60 | 61 | function tryGetCurrentSegment() { 62 | try { 63 | return contextUtils.getSegment(); 64 | } catch (e) { 65 | return undefined; 66 | } 67 | } 68 | 69 | function capturePromise() { 70 | patchPromise(Promise); 71 | } 72 | 73 | function uncapturePromise() { 74 | unpatchPromise(Promise); 75 | } 76 | 77 | capturePromise.patchThirdPartyPromise = patchPromise; 78 | 79 | module.exports.capturePromise = capturePromise; 80 | module.exports.uncapturePromise = uncapturePromise; 81 | -------------------------------------------------------------------------------- /packages/core/lib/resources/default_sampling_rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": { 3 | "fixed_target": 1, 4 | "rate": 0.05 5 | }, 6 | "version": 2 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/lib/segment_emitter.d.ts: -------------------------------------------------------------------------------- 1 | import Segment = require('./segments/segment'); 2 | 3 | export function format(segment: Segment): string; 4 | 5 | export function send(segment: Segment): void; 6 | 7 | export function setDaemonAddress(address: string): void; 8 | 9 | export function getIp(): string; 10 | 11 | export function getPort(): number; 12 | 13 | export function disableReusableSocket(): void; 14 | -------------------------------------------------------------------------------- /packages/core/lib/segments/attributes/aws.d.ts: -------------------------------------------------------------------------------- 1 | declare class Aws { 2 | constructor(res: any, serviceName: string); 3 | 4 | addData(data: any): void; 5 | } 6 | 7 | declare namespace Aws { 8 | function setAWSWhitelist(source: string | object): void; 9 | 10 | function appendAWSWhitelist(source: string | object): void; 11 | } 12 | 13 | export = Aws; 14 | -------------------------------------------------------------------------------- /packages/core/lib/segments/attributes/aws.js: -------------------------------------------------------------------------------- 1 | var CallCapturer = require('../../patchers/call_capturer.js'); 2 | 3 | var capturer = new CallCapturer(); 4 | 5 | /** 6 | * Represents a AWS client call. Automatically captures data from the supplied response object, 7 | * Data captured depends on the whitelisting file supplied. 8 | * The base whitelisting file can be found at /lib/resources/aws_whitelist.json. 9 | * @constructor 10 | * @param {any} res - The response object from the AWS call. Typed as any to avoid AWS SDK dependency. Otherwise would be AWS.Response. 11 | * @param {string} serviceName - The service name of the AWS client. 12 | * @see https://github.com/aws/aws-sdk-js/blob/master/lib/response.js 13 | */ 14 | 15 | function Aws(res, serviceName) { 16 | this.init(res, serviceName); 17 | } 18 | 19 | Aws.prototype.init = function init(res, serviceName) { 20 | //TODO: account ID 21 | this.operation = formatOperation(res.request.operation) || ''; 22 | if (res && res.request && res.request.httpRequest && res.request.httpRequest.region) { 23 | this.region = res.request.httpRequest.region; 24 | } 25 | if (res && res.requestId) { 26 | this.request_id = res.requestId; 27 | } 28 | this.retries = res.retryCount || 0; 29 | 30 | if (res.extendedRequestId && serviceName && serviceName.toLowerCase() === 's3') { 31 | this.id_2 = res.extendedRequestId; 32 | } 33 | 34 | if (serviceName) { 35 | this.addData(capturer.capture(serviceName.toLowerCase(), res)); 36 | } 37 | }; 38 | 39 | Aws.prototype.addData = function addData(data) { 40 | for (var attribute in data) { 41 | this[attribute] = data[attribute]; 42 | } 43 | }; 44 | 45 | /** 46 | * Overrides the default whitelisting file to specify what params to capture on each AWS Service call. 47 | * @param {string|Object} source - The path to the custom whitelist file, or a whitelist source JSON object. 48 | * @exports setAWSWhitelist 49 | */ 50 | 51 | var setAWSWhitelist = function setAWSWhitelist(source) { 52 | if (!source || source instanceof String || !(typeof source === 'string' || (source instanceof Object))) { 53 | throw new Error('Please specify a path to the local whitelist file, or supply a whitelist source object.'); 54 | } 55 | 56 | capturer = new CallCapturer(source); 57 | }; 58 | 59 | /** 60 | * Appends to the default whitelisting file to specify what params to capture on each AWS Service call. 61 | * @param {string|Object} source - The path to the custom whitelist file, or a whitelist source JSON object. 62 | * @exports appendAWSWhitelist 63 | */ 64 | 65 | var appendAWSWhitelist = function appendAWSWhitelist(source) { 66 | if (!source || source instanceof String || !(typeof source === 'string' || (source instanceof Object))) { 67 | throw new Error('Please specify a path to the local whitelist file, or supply a whitelist source object.'); 68 | } 69 | 70 | capturer.append(source); 71 | }; 72 | 73 | function formatOperation(operation) { 74 | if (!operation) { 75 | return; 76 | } 77 | 78 | return operation.charAt(0).toUpperCase() + operation.slice(1); 79 | } 80 | 81 | module.exports = Aws; 82 | module.exports.appendAWSWhitelist = appendAWSWhitelist; 83 | module.exports.setAWSWhitelist = setAWSWhitelist; 84 | -------------------------------------------------------------------------------- /packages/core/lib/segments/attributes/captured_exception.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | 3 | /** 4 | * Represents a captured exception. 5 | * @constructor 6 | * @param {Exception} err - The exception to capture. 7 | * @param {boolean} [remote] - Flag for whether the error was remote. 8 | */ 9 | 10 | function CapturedException(err, remote) { 11 | this.init(err, remote); 12 | } 13 | 14 | CapturedException.prototype.init = function init(err, remote) { 15 | var e = (typeof err === 'string' || err instanceof String) ? { message: err, name: '' } : err; 16 | 17 | this.message = e.message; 18 | this.type = e.name; 19 | this.stack = []; 20 | this.remote = !!remote; 21 | this.id = crypto.randomBytes(8).toString('hex'); 22 | 23 | if (e.stack) { 24 | var stack = e.stack.split('\n'); 25 | stack.shift(); 26 | 27 | stack.forEach((stackline) => { 28 | var line = stackline.trim().replace(/\(|\)/g, ''); 29 | line = line.substring(line.indexOf(' ') + 1); 30 | 31 | var label = line.lastIndexOf(' ') >= 0 ? line.slice(0, line.lastIndexOf(' ')) : null; 32 | var path = Array.isArray(label) && !label.length ? line : line.slice(line.lastIndexOf(' ') + 1); 33 | path = path.split(':'); 34 | 35 | var entry = { 36 | path: path[0], 37 | line: parseInt(path[1]), 38 | label: label || 'anonymous' 39 | }; 40 | 41 | this.stack.push(entry); 42 | }, this); 43 | } 44 | }; 45 | 46 | module.exports = CapturedException; 47 | -------------------------------------------------------------------------------- /packages/core/lib/segments/attributes/remote_request_data.js: -------------------------------------------------------------------------------- 1 | const { getHttpResponseData } = require('../segment_utils'); 2 | var { stripQueryStringFromPath } = require('../../utils'); 3 | 4 | /** 5 | * Represents an outgoing HTTP/HTTPS call. 6 | * @constructor 7 | * @param {http.ClientRequest|https.ClientRequest} req - The request object from the HTTP/HTTPS call. 8 | * @param {http.IncomingMessage|https.IncomingMessage} res - The response object from the HTTP/HTTPS call. 9 | * @param {boolean} downstreamXRayEnabled - when true, adds a "traced": true hint to generated subsegments such that the AWS X-Ray service expects a corresponding segment from the downstream service. 10 | */ 11 | 12 | function RemoteRequestData(req, res, downstreamXRayEnabled) { 13 | this.init(req, res, downstreamXRayEnabled); 14 | } 15 | 16 | RemoteRequestData.prototype.init = function init(req, res, downstreamXRayEnabled) { 17 | this.request = { 18 | url: (req.agent && req.agent.protocol) ? (req.agent.protocol + '//' + (req.host || req.getHeader('host')) + stripQueryStringFromPath(req.path)) : '', 19 | method: req.method || '', 20 | }; 21 | 22 | if (downstreamXRayEnabled) { 23 | this.request.traced = true; 24 | } 25 | 26 | if (res) { 27 | this.response = getHttpResponseData(res); 28 | } 29 | }; 30 | 31 | module.exports = RemoteRequestData; 32 | -------------------------------------------------------------------------------- /packages/core/lib/segments/attributes/subsegment.d.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http'; 2 | import { Segment, SegmentLike } from '../../aws-xray'; 3 | 4 | declare class Subsegment { 5 | id: string; 6 | name: string; 7 | start_time: number; 8 | in_progress?: boolean; 9 | subsegments?: Array; 10 | parent: SegmentLike; 11 | segment: Segment; 12 | namespace?: string; 13 | notTraced: boolean; 14 | 15 | constructor(name: string); 16 | 17 | addNewSubsegment(name: string): Subsegment; 18 | 19 | addSubsegment(subsegment: Subsegment): void; 20 | 21 | addNewSubsegmentWithoutSampling(name: String): Subsegment; 22 | 23 | addSubsegmentWithoutSampling(subsegment: Subsegment): void; 24 | 25 | removeSubsegment(subsegment: Subsegment): void; 26 | 27 | addAttribute(name: string, data: any): void; 28 | 29 | addPrecursorId(id: string): void; 30 | 31 | addAnnotation(key: string, value: boolean | string | number): void; 32 | 33 | addMetadata(key: string, value: any, namespace?: string): void; 34 | 35 | addSqlData(sqlData: any): void; 36 | 37 | addError(err: Error | string, remote?: boolean): void; 38 | 39 | addRemoteRequestData(req: http.ClientRequest, res: http.IncomingMessage, downstreamXRayEnabled?: boolean): void; 40 | 41 | addFaultFlag(): void; 42 | 43 | addErrorFlag(): void; 44 | 45 | addThrottleFlag(): void; 46 | 47 | close(err?: Error | string | null, remote?: boolean): void; 48 | 49 | incrementCounter(additional?: number): void; 50 | 51 | decrementCounter(): void; 52 | 53 | isClosed(): boolean; 54 | 55 | flush(): void; 56 | 57 | streamSubsegments(): true | undefined; 58 | 59 | format(): string; 60 | 61 | toString(): string; 62 | 63 | toJSON(): { [key: string]: any }; 64 | 65 | serialize(subsegment?: Subsegment): string; 66 | } 67 | 68 | export = Subsegment; 69 | -------------------------------------------------------------------------------- /packages/core/lib/segments/attributes/trace_id.d.ts: -------------------------------------------------------------------------------- 1 | declare class TraceID { 2 | version: number; 3 | timestamp: string; 4 | id: string; 5 | 6 | constructor(tsHex?: string, numberhex?: string); 7 | 8 | static Invalid(): TraceID; 9 | 10 | static FromString(rawId: string): TraceID; 11 | 12 | toString(): string; 13 | } 14 | 15 | export = TraceID; 16 | -------------------------------------------------------------------------------- /packages/core/lib/segments/attributes/trace_id.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | var logger = require('../../logger'); 3 | 4 | /** 5 | * Class describing an AWS X-Ray trace ID. 6 | * @see https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-traces 7 | */ 8 | class TraceID { 9 | /** 10 | * Constructs a new trace ID using the current time. 11 | * @param {string} [tsHex] - time stamp to use for trace ID in hexadecimal format 12 | * @param {string} [numberhex] - string of hexadecimal characters for random portion of Trace ID 13 | * @constructor 14 | */ 15 | constructor(tsHex, numberhex) { 16 | this.version = 1; 17 | this.timestamp = tsHex || Math.round(new Date().getTime() / 1000).toString(16); 18 | this.id = numberhex || crypto.randomBytes(12).toString('hex'); 19 | } 20 | 21 | /** 22 | * @returns {TraceID} - a hardcoded trace ID using zeroed timestamp and random ID 23 | */ 24 | static Invalid() { 25 | return new TraceID('00000000', '000000000000000000000000'); 26 | } 27 | 28 | /** 29 | * Constructs a new trace ID from provided string. If no string is provided or the provided string is invalid, 30 | * log an error but a new trace ID still returned. This can be used as a trace ID string validator. 31 | * @param {string} [rawID] - string to create a Trace ID object from. 32 | */ 33 | static FromString(rawID) { 34 | const DELIMITER = '-'; 35 | var traceID = new TraceID(); 36 | var version, timestamp; 37 | 38 | if (!rawID || typeof rawID !== 'string') { 39 | logger.getLogger().error('Empty or non-string trace ID provided'); 40 | return traceID; 41 | } 42 | 43 | const parts = rawID.trim().split(DELIMITER); 44 | if (parts.length !== 3) { 45 | logger.getLogger().error('Unrecognized trace ID format'); 46 | return traceID; 47 | } 48 | 49 | version = parseInt(parts[0]); 50 | if (isNaN(version) || version < 1) { 51 | logger.getLogger().error('Trace ID version must be positive integer'); 52 | return traceID; 53 | } 54 | 55 | timestamp = parseInt(parts[1], 16).toString(16); 56 | if (timestamp === 'NaN') { 57 | logger.getLogger().error('Trace ID timestamp must be a hex-encoded value'); 58 | return traceID; 59 | } else { 60 | timestamp = timestamp.padStart(8, '0'); 61 | } 62 | 63 | traceID.version = version; 64 | traceID.timestamp = timestamp; 65 | traceID.id = parts[2]; 66 | 67 | return traceID; 68 | } 69 | 70 | /** 71 | * Returns a string representation of the trace ID. 72 | * @returns {string} - stringified trace ID, e.g. 1-57fbe041-2c7ad569f5d6ff149137be86 73 | */ 74 | toString() { 75 | return `${this.version.toString()}-${this.timestamp}-${this.id}`; 76 | } 77 | } 78 | 79 | module.exports = TraceID; 80 | -------------------------------------------------------------------------------- /packages/core/lib/segments/plugins/ec2_plugin.d.ts: -------------------------------------------------------------------------------- 1 | export interface EC2Metadata { 2 | ec2: { 3 | instance_id: string; 4 | availability_zone: string; 5 | }; 6 | } 7 | 8 | export function getData(callback: (metadata?: EC2Metadata) => void): void; 9 | 10 | export const originName: string; 11 | -------------------------------------------------------------------------------- /packages/core/lib/segments/plugins/ec2_plugin.js: -------------------------------------------------------------------------------- 1 | var Plugin = require('./plugin'); 2 | var logger = require('../../logger'); 3 | var http = require('http'); 4 | 5 | var EC2Plugin = { 6 | /** 7 | * A function to get the instance data from the EC2 metadata service. 8 | * @param {function} callback - The callback for the plugin loader. 9 | */ 10 | getData: function(callback) { 11 | const METADATA_PATH = '/latest/dynamic/instance-identity/document'; 12 | 13 | function populateMetadata(token) { 14 | const options = getOptions( 15 | METADATA_PATH, 16 | 'GET', 17 | token ? { 'X-aws-ec2-metadata-token': token } : {} 18 | ); 19 | 20 | Plugin.getPluginMetadata(options, function(err, data) { 21 | if (err || !data) { 22 | logger.getLogger().error('Error loading EC2 plugin metadata: ', err ? err.toString() : 'Could not retrieve data from IMDS.'); 23 | callback(); 24 | return; 25 | } 26 | 27 | const metadata = { 28 | ec2: { 29 | instance_id: data.instanceId, 30 | availability_zone: data.availabilityZone, 31 | instance_size: data.instanceType, 32 | ami_id: data.imageId 33 | } 34 | }; 35 | callback(metadata); 36 | }); 37 | } 38 | 39 | /** 40 | * This kicks off a requet to get a token used for requests to IMDSv2. If the request for the token 41 | * fails, we fall back to IMDSv1. Otherwise, the token will be used for an IMDSv2 request. 42 | */ 43 | getToken(function(token) { 44 | if (token === null) { 45 | logger.getLogger().debug('EC2Plugin failed to get token from IMDSv2. Falling back to IMDSv1.'); 46 | } 47 | 48 | populateMetadata(token); 49 | }); 50 | }, 51 | originName: 'AWS::EC2::Instance' 52 | }; 53 | 54 | /** 55 | * Asynchronously retrieves a token used in requests to EC2 instance metadata service. 56 | * @param {function} callback - callback to plugin 57 | */ 58 | function getToken(callback) { 59 | const httpReq = http.__request ? http.__request : http.request; 60 | const TTL = 60; //seconds 61 | const TOKEN_PATH = '/latest/api/token'; 62 | const options = getOptions(TOKEN_PATH, 'PUT', { 63 | 'X-aws-ec2-metadata-token-ttl-seconds': TTL 64 | }); 65 | 66 | let req = httpReq(options, function(res) { 67 | let body = ''; 68 | 69 | res.on('data', function(chunk) { 70 | body += chunk; 71 | }); 72 | 73 | res.on('end', function() { 74 | if (this.statusCode === 200 || this.statusCode === 300) { 75 | callback(body); 76 | } else { 77 | callback(null); 78 | } 79 | }); 80 | }); 81 | 82 | req.on('error', function() { 83 | callback(null); 84 | }); 85 | 86 | req.on('timeout', function() { 87 | req.abort(); 88 | callback(null); 89 | }); 90 | 91 | req.setTimeout(Plugin.METADATA_TIMEOUT); 92 | req.end(); 93 | } 94 | 95 | function getOptions(path, method, headers) { 96 | if (!method) { 97 | method = 'GET'; 98 | } 99 | 100 | if (!headers) { 101 | headers = {}; 102 | } 103 | 104 | return { 105 | host: '169.254.169.254', 106 | path: path, 107 | method: method, 108 | headers: headers 109 | }; 110 | } 111 | 112 | module.exports = EC2Plugin; 113 | -------------------------------------------------------------------------------- /packages/core/lib/segments/plugins/ecs_plugin.d.ts: -------------------------------------------------------------------------------- 1 | export interface ECSMetadata { 2 | ecs: { 3 | container: string; 4 | }; 5 | } 6 | 7 | export function getData(callback: (metadata?: ECSMetadata) => void): void; 8 | 9 | export const originName: string; 10 | -------------------------------------------------------------------------------- /packages/core/lib/segments/plugins/ecs_plugin.js: -------------------------------------------------------------------------------- 1 | var os = require('os'); 2 | 3 | var ECSPlugin = { 4 | /** 5 | * A function to get the instance data from the ECS instance. 6 | * @param {function} callback - The callback for the plugin loader. 7 | */ 8 | getData: function(callback) { 9 | callback({ ecs: { container: os.hostname() }}); 10 | }, 11 | originName: 'AWS::ECS::Container' 12 | }; 13 | 14 | module.exports = ECSPlugin; 15 | -------------------------------------------------------------------------------- /packages/core/lib/segments/plugins/elastic_beanstalk_plugin.d.ts: -------------------------------------------------------------------------------- 1 | export interface ElasticBeanstalkMetadata { 2 | elastic_beanstalk: { 3 | environment: string; 4 | version_label: string; 5 | deployment_id: number; 6 | }; 7 | } 8 | 9 | export function getData(callback: (metadata?: ElasticBeanstalkMetadata) => void): void; 10 | 11 | export const originName: string; 12 | -------------------------------------------------------------------------------- /packages/core/lib/segments/plugins/elastic_beanstalk_plugin.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var logger = require('../../logger'); 4 | 5 | var ENV_CONFIG_LOCATION = '/var/elasticbeanstalk/xray/environment.conf'; 6 | 7 | var ElasticBeanstalkPlugin = { 8 | /** 9 | * A function to get data from the Elastic Beanstalk environment configuration file. 10 | * @param {function} callback - The callback for the plugin loader. 11 | */ 12 | getData: function(callback) { 13 | fs.readFile(ENV_CONFIG_LOCATION, 'utf8', function(err, rawData) { 14 | if (err) { 15 | logger.getLogger().error('Error loading Elastic Beanstalk plugin:', err.stack); 16 | callback(); 17 | } else { 18 | var data = JSON.parse(rawData); 19 | 20 | var metadata = { 21 | elastic_beanstalk: { 22 | environment: data.environment_name, 23 | version_label: data.version_label, 24 | deployment_id: data.deployment_id 25 | } 26 | }; 27 | 28 | callback(metadata); 29 | } 30 | }); 31 | }, 32 | originName: 'AWS::ElasticBeanstalk::Environment' 33 | }; 34 | 35 | module.exports = ElasticBeanstalkPlugin; 36 | -------------------------------------------------------------------------------- /packages/core/lib/segments/plugins/plugin.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | 3 | var Plugin = { 4 | METADATA_TIMEOUT: 1000, // Millis 5 | 6 | /** 7 | * Asynchronously retrieves metadata from on-instance endpoint with an HTTP request using retries for 8 | * requests that time out. 9 | * @param {object} options - The HTTP options to make the request with 10 | * @param {function} callback - callback to plugin 11 | */ 12 | getPluginMetadata: function(options, callback) { 13 | const METADATA_RETRY_TIMEOUT = 250; // Millis 14 | const METADATA_RETRIES = 5; 15 | 16 | var retries = METADATA_RETRIES; 17 | 18 | var getMetadata = function() { 19 | var httpReq = http.__request ? http.__request : http.request; 20 | 21 | var req = httpReq(options, function(res) { 22 | var body = ''; 23 | 24 | res.on('data', function(chunk) { 25 | body += chunk; 26 | }); 27 | 28 | res.on('end', function() { 29 | if (this.statusCode === 200 || this.statusCode === 300) { 30 | try { 31 | body = JSON.parse(body); 32 | } catch (e) { 33 | callback(e); 34 | return; 35 | } 36 | 37 | callback(null, body); 38 | } else if (retries > 0 && Math.floor(this.statusCode / 100) === 5) { 39 | retries--; 40 | setTimeout(getMetadata, METADATA_RETRY_TIMEOUT); 41 | } else { 42 | callback(new Error(`Failed to retrieve metadata with options: ${options}`)); 43 | } 44 | }); 45 | }); 46 | 47 | req.on('error', function(err) { 48 | callback(err); 49 | }); 50 | 51 | req.on('timeout', function() { 52 | req.abort(); 53 | }); 54 | 55 | req.setTimeout(Plugin.METADATA_TIMEOUT); 56 | req.end(); 57 | }; 58 | 59 | getMetadata(); 60 | } 61 | }; 62 | 63 | module.exports = Plugin; 64 | -------------------------------------------------------------------------------- /packages/core/lib/segments/segment.d.ts: -------------------------------------------------------------------------------- 1 | import Subsegment = require('./attributes/subsegment'); 2 | import IncomingRequestData = require('../middleware/incoming_request_data'); 3 | 4 | declare class Segment { 5 | id: string; 6 | name: string; 7 | start_time: number; 8 | end_time?: number; 9 | in_progress?: boolean; 10 | trace_id: string; 11 | parent_id?: string; 12 | origin?: string; 13 | subsegments?: Array; 14 | notTraced?: boolean; 15 | 16 | additionalTraceData?: object 17 | 18 | constructor(name: string, rootId?: string | null, parentId?: string | null); 19 | 20 | addIncomingRequestData(data: IncomingRequestData): void; 21 | 22 | addAnnotation(key: string, value: boolean | string | number): void; 23 | 24 | setUser(user: string): void; 25 | 26 | addMetadata(key: string, value: any, namespace?: string): void; 27 | 28 | setSDKData(data: object): void; 29 | 30 | setMatchedSamplingRule(ruleName: string): void; 31 | 32 | setServiceData(data: any): void; 33 | 34 | addPluginData(data: object): void; 35 | 36 | addNewSubsegment(name: string): Subsegment; 37 | 38 | addSubsegment(subsegment: Subsegment): void; 39 | 40 | addSubsegmentWithoutSampling(subsegment: Subsegment): void; 41 | 42 | addNewSubsegmentWithoutSampling(name: string): Subsegment 43 | 44 | removeSubsegment(subsegment: Subsegment): void; 45 | 46 | addError(err: Error | string, remote?: boolean): void; 47 | 48 | addFaultFlag(): void; 49 | 50 | addErrorFlag(): void; 51 | 52 | addThrottleFlag(): void; 53 | 54 | isClosed(): boolean; 55 | 56 | incrementCounter(additional?: number): void; 57 | 58 | decrementCounter(): void; 59 | 60 | close(err?: Error | string | null, remote?: boolean): void; 61 | 62 | flush(): void; 63 | 64 | format(): string; 65 | 66 | toString(): string; 67 | 68 | serialize(segment?: Segment): string; 69 | } 70 | 71 | export = Segment; 72 | -------------------------------------------------------------------------------- /packages/core/lib/segments/segment_utils.d.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http'; 2 | 3 | export const streamingThreshold: number; 4 | 5 | export function getCurrentTime(): number; 6 | 7 | export function setOrigin(origin: string): void; 8 | 9 | export function setPluginData(pluginData: object): void; 10 | 11 | export function setSDKData(sdkData: object): void; 12 | 13 | export function setServiceData(serviceData: any): void; 14 | 15 | export function setStreamingThreshold(threshold: number): void; 16 | 17 | export function getStreamingThreshold(): number; 18 | 19 | export function getHttpResponseData(res: http.ServerResponse): object; 20 | 21 | export function getJsonStringifyReplacer(): (key: string, value: any) => any; 22 | -------------------------------------------------------------------------------- /packages/core/lib/segments/segment_utils.js: -------------------------------------------------------------------------------- 1 | const { safeParseInt } = require('../utils'); 2 | var logger = require('../logger'); 3 | 4 | var DEFAULT_STREAMING_THRESHOLD = 100; 5 | 6 | var utils = { 7 | streamingThreshold: DEFAULT_STREAMING_THRESHOLD, 8 | 9 | getCurrentTime: function getCurrentTime() { 10 | return Date.now() / 1000; 11 | }, 12 | 13 | setOrigin: function setOrigin(origin) { 14 | this.origin = origin; 15 | }, 16 | 17 | setPluginData: function setPluginData(pluginData) { 18 | this.pluginData = pluginData; 19 | }, 20 | 21 | setSDKData: function setSDKData(sdkData) { 22 | this.sdkData = sdkData; 23 | }, 24 | 25 | setServiceData: function setServiceData(serviceData) { 26 | this.serviceData = serviceData; 27 | }, 28 | 29 | /** 30 | * Overrides the default streaming threshold (100). 31 | * The threshold represents the maximum number of subsegments on a single segment before 32 | * the SDK beings to send the completed subsegments out of band of the main segment. 33 | * Reduce this threshold if you see the 'Segment too large to send' error. 34 | * @param {number} threshold - The new threshold to use. 35 | * @memberof AWSXRay 36 | */ 37 | 38 | setStreamingThreshold: function setStreamingThreshold(threshold) { 39 | if (isFinite(threshold) && threshold >= 0) { 40 | utils.streamingThreshold = threshold; 41 | logger.getLogger().debug('Subsegment streaming threshold set to: ' + threshold); 42 | } else { 43 | logger.getLogger().error('Invalid threshold: ' + threshold + '. Must be a whole number >= 0.'); 44 | } 45 | }, 46 | 47 | getStreamingThreshold: function getStreamingThreshold() { 48 | return utils.streamingThreshold; 49 | }, 50 | 51 | /** 52 | * Parses an HTTP response object to return an X-Ray compliant HTTP response object. 53 | * @param {http.ServerResponse} res 54 | * @returns {Object} - X-Ray response object to be added to (sub)segment 55 | */ 56 | getHttpResponseData: (res) => { 57 | const ret = {}; 58 | if (!res) { 59 | return ret; 60 | } 61 | 62 | const status = safeParseInt(res.statusCode); 63 | if (status !== 0) { 64 | ret.status = status; 65 | } 66 | if (res.headers && res.headers['content-length']) { 67 | ret.content_length = safeParseInt(res.headers['content-length']); 68 | } 69 | return ret; 70 | }, 71 | 72 | getJsonStringifyReplacer: () => (_, value) => { 73 | if (typeof value === 'bigint') { 74 | return value.toString(); 75 | } 76 | 77 | return value; 78 | } 79 | }; 80 | 81 | module.exports = utils; 82 | -------------------------------------------------------------------------------- /packages/core/lib/utils.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import Segment = require('./segments/segment'); 3 | 4 | export function getCauseTypeFromHttpStatus(status: number | string): 'error' | 'fault' | undefined; 5 | 6 | export function stripQueryStringFromPath(path: string): string; 7 | 8 | export function wildcardMatch(pattern: string, text: string): boolean; 9 | 10 | export namespace LambdaUtils { 11 | function validTraceData(xAmznTraceId?: string): boolean; 12 | 13 | function populateTraceData(segment: Segment, xAmznTraceId: string): boolean; 14 | } 15 | 16 | export function processTraceData(traceData?: string): { [key: string]: string }; 17 | 18 | export function objectWithoutProperties( 19 | obj: T, 20 | keys: K[], 21 | preservePrototype?: boolean 22 | ): Omit; 23 | 24 | export function safeParseInt(val: number | string): number; 25 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-xray-sdk-core", 3 | "version": "3.10.3", 4 | "description": "AWS X-Ray SDK for Javascript", 5 | "author": "Amazon Web Services", 6 | "contributors": [ 7 | "Sandra McMullen ", 8 | "William Armiros ", 9 | "Moritz Onken " 10 | ], 11 | "files": [ 12 | "dist/lib/**/*", 13 | "LICENSE", 14 | "README.md" 15 | ], 16 | "main": "dist/lib/index.js", 17 | "types": "dist/lib/index.d.ts", 18 | "engines": { 19 | "node": ">= 14.x" 20 | }, 21 | "directories": { 22 | "test": "test" 23 | }, 24 | "//": "@types/cls-hooked is exposed in API so must be in dependencies, not devDependencies", 25 | "dependencies": { 26 | "@aws-sdk/types": "^3.4.1", 27 | "@smithy/service-error-classification": "^2.0.4", 28 | "@types/cls-hooked": "^4.3.3", 29 | "atomic-batcher": "^1.0.2", 30 | "cls-hooked": "^4.2.2", 31 | "semver": "^7.5.3" 32 | }, 33 | "scripts": { 34 | "prepare": "npm run compile", 35 | "compile": "tsc && npm run copy-lib && npm run copy-test", 36 | "copy-lib": "find lib -type f \\( -name '*.d.ts' -o -name '*.json' \\) | xargs -I % ../../scripts/cp-with-structure.sh % dist", 37 | "copy-test": "find test -name '*.json' | xargs -I % ../../scripts/cp-with-structure.sh % dist", 38 | "lint": "eslint .", 39 | "lint:fix": "eslint . --fix", 40 | "test": "npm run compile && mocha --recursive ./dist/test/ -R spec && tsd && mocha --recursive ./dist/test_async/ -R spec", 41 | "test-d": "tsd", 42 | "test-async": "npm run compile && mocha --recursive ./dist/test_async/ -R spec", 43 | "clean": "rm -rf dist && rm -rf node_modules", 44 | "testcov": "nyc npm run test", 45 | "reportcov": "nyc report --reporter=text-lcov > coverage.lcov" 46 | }, 47 | "keywords": [ 48 | "amazon", 49 | "api", 50 | "aws", 51 | "core", 52 | "xray", 53 | "x-ray", 54 | "x ray" 55 | ], 56 | "license": "Apache-2.0", 57 | "repository": "https://github.com/aws/aws-xray-sdk-node/tree/master/packages/core" 58 | } -------------------------------------------------------------------------------- /packages/core/test/integration/segment_maintained_across_shared_promise.test.js: -------------------------------------------------------------------------------- 1 | if (!global.Promise) { 2 | process.exit(0); 3 | } 4 | 5 | var assert = require('chai').assert; 6 | var http = require('http'); 7 | 8 | var AWSXRay = require('../../lib'); 9 | var Segment = AWSXRay.Segment; 10 | 11 | AWSXRay.capturePromise(); 12 | AWSXRay.enableAutomaticMode(); 13 | 14 | var sharedPromise = null; 15 | 16 | var server = http 17 | .createServer(function(req, res) { 18 | var ns = AWSXRay.getNamespace(); 19 | ns.bindEmitter(req); 20 | ns.bindEmitter(res); 21 | 22 | ns.run(function () { 23 | var segment = new Segment('foo'); 24 | 25 | AWSXRay.setSegment(segment); 26 | 27 | if (!sharedPromise) { 28 | sharedPromise = Promise.resolve(); 29 | } 30 | 31 | sharedPromise.then(function() { 32 | var retrievedSegment = AWSXRay.getSegment(); 33 | res.end(); 34 | 35 | // setTimeout so the assertion isn't caught by the promise 36 | setTimeout(function() { 37 | assert.equal(segment.id, retrievedSegment.id); 38 | // Cancel the patch because it doesn't affect other tests 39 | require('../../lib/patchers/promise_p').uncapturePromise(); 40 | }); 41 | }); 42 | }); 43 | }).listen(8080, '0.0.0.0', function() { 44 | var address = server.address(); 45 | 46 | var count = 0; 47 | function cb(err) { 48 | if (err) { 49 | throw err; 50 | } 51 | 52 | if (++count === 2) { 53 | server.close(); 54 | } 55 | } 56 | sendRequest(address, cb); 57 | sendRequest(address, cb); 58 | }); 59 | 60 | function sendRequest(address, cb) { 61 | http 62 | .request({ 63 | hostname: address.address, 64 | port: address.port, 65 | path: '/' 66 | }) 67 | .on('response', function(res) { 68 | res.on('end', cb).resume(); 69 | }) 70 | .on('error', cb) 71 | .end(); 72 | } 73 | -------------------------------------------------------------------------------- /packages/core/test/resources/custom_sampling.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "description": "Root", 5 | "http_method": "GET", 6 | "service_name": "localhost:*", 7 | "url_path": "/", 8 | "fixed_target": 0, 9 | "rate": 0 10 | }, 11 | { 12 | "http_method": "GET", 13 | "service_name": "*", 14 | "url_path": "/getSQS", 15 | "fixed_target": 10, 16 | "rate": 0.05 17 | }, 18 | { 19 | "http_method": "GET", 20 | "service_name": "*.foo.com", 21 | "url_path": "/signin/*", 22 | "fixed_target": 10, 23 | "rate": 0.05 24 | } 25 | ], 26 | "default": { 27 | "fixed_target": 10, 28 | "rate": 0.05 29 | }, 30 | "version": 1 31 | } -------------------------------------------------------------------------------- /packages/core/test/resources/custom_whitelist.json: -------------------------------------------------------------------------------- 1 | { 2 | "services": { 3 | "s3": { 4 | "operations": { 5 | "getObject": { 6 | "request_parameters": [ 7 | "Bucket", 8 | "Key" 9 | ] 10 | } 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /packages/core/test/unit/aws-xray.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var chai = require('chai'); 3 | var sinon = require('sinon'); 4 | var sinonChai = require('sinon-chai'); 5 | 6 | var segmentUtils = require('../../lib/segments/segment_utils'); 7 | 8 | chai.should(); 9 | chai.use(sinonChai); 10 | 11 | describe('AWSXRay', function() { 12 | var AWSXRay; 13 | 14 | describe('on load', function() { 15 | var sandbox, setSDKDataStub, setServiceDataStub; 16 | 17 | beforeEach(function() { 18 | sandbox = sinon.createSandbox(); 19 | 20 | setSDKDataStub = sandbox.stub(segmentUtils, 'setSDKData'); 21 | setServiceDataStub = sandbox.stub(segmentUtils, 'setServiceData'); 22 | }); 23 | 24 | afterEach(function() { 25 | sandbox.restore(); 26 | }); 27 | 28 | it('should set the segmentUtils version and SDK version', function() { 29 | // This test requires both index.js and aws-xray.js are first time required. 30 | // We should always clear the require cache for these two files so this test 31 | // could run independently. 32 | const indexPath = '../../lib/index'; 33 | const xrayPath = '../../lib/aws-xray'; 34 | delete require.cache[require.resolve(indexPath)]; 35 | delete require.cache[require.resolve(xrayPath)]; 36 | 37 | AWSXRay = require(indexPath); 38 | 39 | setSDKDataStub.should.have.been.calledWithExactly(sinon.match.object); 40 | setServiceDataStub.should.have.been.calledWithExactly(sinon.match.object); 41 | 42 | assert.property(setSDKDataStub.firstCall.args[0], 'sdk'); 43 | assert.property(setSDKDataStub.firstCall.args[0], 'sdk_version'); 44 | assert.notStrictEqual( 45 | setSDKDataStub.firstCall.args[0].sdk_version, 46 | 'unknown', 47 | 'Expected sdk_version to not be unknown' 48 | ); 49 | assert.property(setSDKDataStub.firstCall.args[0], 'package'); 50 | assert.notStrictEqual( 51 | setSDKDataStub.firstCall.args[0].package, 52 | 'unknown', 53 | 'Expected package to not be unknown' 54 | ); 55 | 56 | assert.property(setServiceDataStub.firstCall.args[0], 'runtime'); 57 | assert.property(setServiceDataStub.firstCall.args[0], 'runtime_version'); 58 | assert.property(setServiceDataStub.firstCall.args[0], 'name'); 59 | assert.property(setServiceDataStub.firstCall.args[0], 'version'); 60 | }); 61 | }); 62 | 63 | describe('#config', function() { 64 | var sandbox, setOriginStub, setPluginDataStub; 65 | 66 | beforeEach(function() { 67 | sandbox = sinon.createSandbox(); 68 | 69 | setPluginDataStub = sandbox.stub(segmentUtils, 'setPluginData'); 70 | setOriginStub = sandbox.stub(segmentUtils, 'setOrigin'); 71 | }); 72 | 73 | afterEach(function() { 74 | sandbox.restore(); 75 | }); 76 | 77 | it('should load the given plugins and set the data on segmentUtils', function(done) { 78 | var data = { client: 'data' }; 79 | var pluginStub = { 80 | getData: function(callback) { 81 | callback(data); 82 | } 83 | }; 84 | AWSXRay.config([pluginStub]); 85 | 86 | setTimeout(function() { 87 | setPluginDataStub.should.have.been.calledWithExactly(data); 88 | done(); 89 | }, 50); 90 | }); 91 | 92 | it('should set segmentUtils origin to beanstalk if beanstalk plugin was loaded', function(done) { 93 | var pluginStub = { 94 | getData: function(callback) { 95 | callback('data'); 96 | }, 97 | originName: 'AWS::ElasticBeanstalk::Environment' 98 | }; 99 | AWSXRay.config([pluginStub]); 100 | 101 | setTimeout(function() { 102 | setOriginStub.should.have.been.calledWithExactly('AWS::ElasticBeanstalk::Environment'); 103 | done(); 104 | }, 50); 105 | }); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /packages/core/test/unit/env/sqs_message_helper.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var chai = require('chai'); 3 | var sinonChai = require('sinon-chai'); 4 | 5 | import SqsMessageHelper from '../../../lib/env/sqs_message_helper'; 6 | 7 | chai.should(); 8 | chai.use(sinonChai); 9 | 10 | describe('#SqsMessageHelper', function () { 11 | 12 | // sample records from https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html 13 | const sampleSqsMessageEvent = { 14 | 'Records': [ 15 | { 16 | 'messageId': '059f36b4-87a3-44ab-83d2-661975830a7d', 17 | 'receiptHandle': 'AQEBwJnKyrHigUMZj6rYigCgxlaS3SLy0a...', 18 | 'body': 'Test message.', 19 | 'attributes': { 20 | 'ApproximateReceiveCount': '1', 21 | 'SentTimestamp': '1545082649183', 22 | 'SenderId': 'AIDAIENQZJOLO23YVJ4VO', 23 | 'ApproximateFirstReceiveTimestamp': '1545082649185', 24 | 'AWSTraceHeader':'Root=1-632BB806-bd862e3fe1be46a994272793;Sampled=1' 25 | }, 26 | 'messageAttributes': {}, 27 | 'md5OfBody': 'e4e68fb7bd0e697a0ae8f1bb342846b3', 28 | 'eventSource': 'aws:sqs', 29 | 'eventSourceARN': 'arn:aws:sqs:us-east-2:123456789012:my-queue', 30 | 'awsRegion': 'us-east-2' 31 | }, 32 | { 33 | 'messageId': '2e1424d4-f796-459a-8184-9c92662be6da', 34 | 'receiptHandle': 'AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...', 35 | 'body': 'Test message.', 36 | 'attributes': { 37 | 'ApproximateReceiveCount': '1', 38 | 'SentTimestamp': '1545082650636', 39 | 'SenderId': 'AIDAIENQZJOLO23YVJ4VO', 40 | 'ApproximateFirstReceiveTimestamp': '1545082650649', 41 | 'AWSTraceHeader':'Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=0' 42 | }, 43 | 'messageAttributes': {}, 44 | 'md5OfBody': 'e4e68fb7bd0e697a0ae8f1bb342846b3', 45 | 'eventSource': 'aws:sqs', 46 | 'eventSourceARN': 'arn:aws:sqs:us-east-2:123456789012:my-queue', 47 | 'awsRegion': 'us-east-2' 48 | }, 49 | { 50 | 'messageId': '2e1424d4-f796-459a-8184-9c92662be6da', 51 | 'receiptHandle': 'AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...', 52 | 'body': 'Test message.', 53 | 'attributes': { 54 | 'ApproximateReceiveCount': '1', 55 | 'SentTimestamp': '1545082650636', 56 | 'SenderId': 'AIDAIENQZJOLO23YVJ4VO', 57 | 'ApproximateFirstReceiveTimestamp': '1545082650649', 58 | 'AWSTraceHeader':'Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8' 59 | }, 60 | 'messageAttributes': {}, 61 | 'md5OfBody': 'e4e68fb7bd0e697a0ae8f1bb342846b3', 62 | 'eventSource': 'aws:sqs', 63 | 'eventSourceARN': 'arn:aws:sqs:us-east-2:123456789012:my-queue', 64 | 'awsRegion': 'us-east-2' 65 | } 66 | ] 67 | }; 68 | 69 | describe('SqsMessageHelper isSampled', function() { 70 | 71 | it('should return true when AWSTraceHeader has Sampled=1', function() { 72 | assert.equal(SqsMessageHelper.isSampled(sampleSqsMessageEvent.Records[0]), true); 73 | }); 74 | 75 | it('should return false when AWSTraceHeader has Sampled=0', function() { 76 | assert.equal(SqsMessageHelper.isSampled(sampleSqsMessageEvent.Records[1]), false); 77 | }); 78 | 79 | it('should return false when AWSTraceHeader has no Sampled flag', function() { 80 | assert.equal(SqsMessageHelper.isSampled(sampleSqsMessageEvent.Records[2]), false); 81 | }); 82 | 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /packages/core/test/unit/sampling/local_reservoir.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var expect = require('chai').expect; 3 | var sinon = require('sinon'); 4 | 5 | var LocalReservoir = require('../../../lib/middleware/sampling/local_reservoir'); 6 | 7 | describe('LocalReservoir', function() { 8 | describe('#constructor', function() { 9 | it('should return a new Sampler with fixed target and rate set', function() { 10 | var localReservoir = new LocalReservoir(5, 0.5); 11 | 12 | assert(!isNaN(localReservoir.fixedTarget), 'Expected fixed target to be a number.'); 13 | assert(!isNaN(localReservoir.fallbackRate), 'Expected rate to be a number.'); 14 | }); 15 | 16 | it('should throw an exception if fixed target is a float or a negative number', function() { 17 | expect(function() { 18 | new LocalReservoir(123.45, 0.5); 19 | }).to.throw(Error, '"fixed_target" must be a non-negative integer.'); 20 | expect(function() { 21 | new LocalReservoir(-123, 0.5); 22 | }).to.throw(Error, '"fixed_target" must be a non-negative integer.'); 23 | }); 24 | 25 | it('should throw an exception if rate is not a number between 0 and 1', function() { 26 | expect(function() { 27 | new LocalReservoir(5, 123); 28 | }).to.throw(Error, '"rate" must be a number between 0 and 1 inclusive.'); 29 | expect(function() { 30 | new LocalReservoir(5, -0.5); 31 | }).to.throw(Error, '"rate" must be a number between 0 and 1 inclusive.'); 32 | }); 33 | }); 34 | 35 | describe('#isSampled', function() { 36 | var sandbox, localReservoir; 37 | var fixedTarget = 5; 38 | 39 | before(function() { 40 | sandbox = sinon.createSandbox(); 41 | sandbox.stub(Math, 'round').returns(1); 42 | }); 43 | 44 | beforeEach(function() { 45 | localReservoir = new LocalReservoir(fixedTarget, 0); 46 | }); 47 | 48 | after(function() { 49 | sandbox.restore(); 50 | }); 51 | 52 | it('should return true up to the fixed target set.', function() { 53 | for (var i = 0; i < fixedTarget; i++) { 54 | assert.isTrue(localReservoir.isSampled()); 55 | } 56 | 57 | assert.isFalse(localReservoir.isSampled()); 58 | }); 59 | 60 | it('should call Math.random and use the rate set if the fixed target has already been reached.', function() { 61 | localReservoir.thisSecond = 1; 62 | localReservoir.usedThisSecond = 5; 63 | var randomStub = sandbox.stub(Math, 'random').returns(1); 64 | 65 | localReservoir.isSampled(); 66 | randomStub.should.have.been.calledOnce; 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /packages/core/test/unit/segments/attributes/captured_exception.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var CapturedException = require('../../../../lib/segments/attributes/captured_exception'); 3 | 4 | describe('CapturedException', function() { 5 | describe('#constructor', function() { 6 | it('should create a CapturedException for a String', function() { 7 | var err = 'Error here!'; 8 | var captured = new CapturedException(err); 9 | 10 | assert.equal(captured.message, err); 11 | assert.equal(captured.type, ''); 12 | assert.equal(captured.id.length, 16); 13 | assert.deepEqual(captured.stack, []); 14 | }); 15 | 16 | it('should create a CapturedException for an Error', function() { 17 | var err = new Error('Error here!'); 18 | var captured = new CapturedException(err); 19 | 20 | assert.equal(captured.message, err.message); 21 | assert.equal(captured.type, err.name); 22 | assert.equal(captured.id.length, 16); 23 | assert.isArray(captured.stack); 24 | }); 25 | 26 | it('should create a CapturedException for an Error with no stack trace', function() { 27 | var err = { message: 'Error here!', name: 'Error'}; 28 | var captured = new CapturedException(err); 29 | 30 | assert.deepEqual(captured.stack, []); 31 | }); 32 | 33 | it('should create a CapturedException for an Error with a parsed stack trace', function() { 34 | var err = new Error('Test error'); 35 | err.stack = ('Test error\n at /path/to/file.js:200:15\n ' + 36 | 'at myTestFunction /path/to/another/file.js:20:30\n ' + 37 | 'at myTest [as _myTests] (test.js:10:5)'); 38 | 39 | var stack = [ 40 | { 41 | path: '/path/to/file.js', 42 | line: 200, 43 | label: 'anonymous' 44 | }, 45 | { 46 | path: '/path/to/another/file.js', 47 | line: 20, 48 | label: 'myTestFunction' 49 | }, 50 | { 51 | path: 'test.js', 52 | line: 10, 53 | label: 'myTest [as _myTests]' 54 | } 55 | ]; 56 | 57 | var captured = new CapturedException(err); 58 | assert.deepEqual(captured.stack, stack); 59 | }); 60 | 61 | it('should create a CapturedException with remote false by default', function() { 62 | var err = { message: 'Error here!', name: 'Error'}; 63 | var captured = new CapturedException(err); 64 | 65 | assert.equal(captured.remote, false); 66 | }); 67 | 68 | it('should create a CapturedException with remote true when set', function() { 69 | var err = { message: 'Error here!', name: 'Error'}; 70 | var captured = new CapturedException(err, true); 71 | 72 | assert.equal(captured.remote, true); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /packages/core/test/unit/segments/attributes/remote_request_data.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var chai = require('chai'); 3 | 4 | var RemoteRequestData = require('../../../../lib/segments/attributes/remote_request_data'); 5 | 6 | chai.should(); 7 | 8 | describe('RemoteRequestData', function() { 9 | const defaultRequest = { 10 | agent: { 11 | protocol: 'https:' 12 | }, 13 | getHeader: (key) => { 14 | if (key === 'host') { 15 | return 'host.com'; 16 | } 17 | return undefined; 18 | }, 19 | path: '/path/to/resource' 20 | }; 21 | 22 | const defaultResponse = { 23 | statusCode: 200, 24 | headers: { 25 | 'content-length': 10, 26 | } 27 | }; 28 | 29 | var request = defaultRequest; 30 | var response = defaultResponse; 31 | 32 | this.beforeEach(function () { 33 | request = defaultRequest; 34 | response = defaultResponse; 35 | }); 36 | 37 | describe('#constructor', function() { 38 | it('should mask out query string in path', function() { 39 | const requestWithPathQueryString = Object.assign(request, { path: '/path/to/resource?qs=qs' }); 40 | 41 | assert.propertyVal( 42 | new RemoteRequestData(requestWithPathQueryString, response, true).request, 43 | 'url', 44 | 'https://host.com/path/to/resource' 45 | ); 46 | }); 47 | it('should return empty url if request agent is missing', function() { 48 | const requestWithoutAgent = {}; 49 | 50 | assert.propertyVal( 51 | new RemoteRequestData(requestWithoutAgent, response, true).request, 52 | 'url', 53 | '' 54 | ); 55 | }); 56 | it('should use the host from the request object over headers', () => { 57 | const requestWithHost = Object.assign(request, { host: 'different-site.com' }); 58 | 59 | assert.propertyVal( 60 | new RemoteRequestData(requestWithHost, response, true).request, 61 | 'url', 62 | 'https://different-site.com/path/to/resource' 63 | ); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /packages/core/test/unit/segments/attributes/trace_id.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var TraceID = require('../../../../lib/segments/attributes/trace_id'); 3 | 4 | function validateTraceID(traceID) { 5 | const hexRegex = /^[0-9a-fA-F]+$/; 6 | assert.isNumber(traceID.version); 7 | assert.isTrue(hexRegex.test(traceID.timestamp)); 8 | assert.isTrue(hexRegex.test(traceID.id)); 9 | } 10 | 11 | describe('TraceID', function() { 12 | it('should construct a valid trace ID', function() { 13 | var traceId = new TraceID(); 14 | validateTraceID(traceId); 15 | }); 16 | 17 | it('should have created a valid trace ID from given string', function() { 18 | const traceStr = '1-57fbe041-2c7ad569f5d6ff149137be86'; 19 | var traceId = TraceID.FromString(traceStr); 20 | assert.equal(traceId.version, 1); 21 | assert.equal(traceId.timestamp, '57fbe041'); 22 | assert.equal(traceId.id, '2c7ad569f5d6ff149137be86'); 23 | }); 24 | 25 | it('should return a valid trace ID given undefined', function() { 26 | var traceId = TraceID.FromString(undefined); 27 | validateTraceID(traceId); 28 | }); 29 | 30 | it('should return a valid trace ID when given malformed string', function() { 31 | const traceStr = 'FAKE-TRACE'; 32 | var traceId = TraceID.FromString(traceStr); 33 | validateTraceID(traceId); 34 | }); 35 | 36 | it('should return a valid trace ID when given partially malformed string', function() { 37 | const traceStr = '1-XYZ-2c7ad569f5d6ff149137be86'; 38 | var traceId = TraceID.FromString(traceStr); 39 | validateTraceID(traceId); 40 | }); 41 | 42 | it('should keep given trace ID the same between fromString and toString', function() { 43 | const traceStr = '1-57fbe041-2c7ad569f5d6ff149137be86'; 44 | var traceId = TraceID.FromString(traceStr); 45 | assert.equal(traceId.toString(), traceStr); 46 | }); 47 | 48 | it('should keep leading 0\'s for trace ID from given string', function() { 49 | const traceStr = '1-00fbe041-2c7ad569f5d6ff149137be86'; 50 | var traceId = TraceID.FromString(traceStr); 51 | assert.equal(traceId.version, 1); 52 | assert.equal(traceId.timestamp, '00fbe041'); 53 | assert.equal(traceId.id, '2c7ad569f5d6ff149137be86'); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /packages/core/test/unit/segments/plugins/ecs_plugin.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var chai = require('chai'); 3 | var sinon = require('sinon'); 4 | var sinonChai = require('sinon-chai'); 5 | 6 | chai.use(sinonChai); 7 | 8 | var ECSPlugin = require('../../../../lib/segments/plugins/ecs_plugin'); 9 | 10 | describe('ECSPlugin', function() { 11 | var sandbox; 12 | 13 | beforeEach(function() { 14 | sandbox = sinon.createSandbox(); 15 | }); 16 | 17 | afterEach(function() { 18 | sandbox.restore(); 19 | }); 20 | 21 | it('should return an object holding ECS metadata', function(done) { 22 | ECSPlugin.getData(function(data) { 23 | expect(data.ecs.container).not.to.be.empty; 24 | done(); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/core/test/unit/segments/plugins/elastic_beanstalk_plugin.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var fs = require('fs'); 3 | var chai = require('chai'); 4 | var sinon = require('sinon'); 5 | var sinonChai = require('sinon-chai'); 6 | 7 | chai.use(sinonChai); 8 | 9 | var ElasticBeanstalkPlugin = require('../../../../lib/segments/plugins/elastic_beanstalk_plugin'); 10 | 11 | describe('ElasticBeanstalkPlugin', function() { 12 | var err = new Error('Cannot load file.'); 13 | var data = { 14 | deployment_id: 'deployment_id', 15 | version_label: 'version_label', 16 | environment_name: 'my_env' 17 | }; 18 | 19 | var readStub, sandbox; 20 | 21 | beforeEach(function() { 22 | sandbox = sinon.createSandbox(); 23 | }); 24 | 25 | afterEach(function() { 26 | sandbox.restore(); 27 | }); 28 | 29 | it('should return an object holding Beanstalk metadata if it read data', function(done) { 30 | readStub = sandbox.stub(fs, 'readFile').yields(null, data); 31 | sandbox.stub(JSON, 'parse').returns(data); 32 | 33 | ElasticBeanstalkPlugin.getData(function(data) { 34 | readStub.should.have.been.calledOnce; 35 | expect(data.elastic_beanstalk).not.to.be.empty; 36 | done(); 37 | }); 38 | }); 39 | 40 | it('should return undefined if the read fails', function(done) { 41 | readStub = sandbox.stub(fs, 'readFile').yields(err, null); 42 | 43 | ElasticBeanstalkPlugin.getData(function(data) { 44 | readStub.should.have.been.calledOnce; 45 | expect(data).to.be.undefined; 46 | done(); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /packages/core/test/unit/segments/plugins/plugin.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var assert = require('chai').assert; 3 | var nock = require('nock'); 4 | 5 | var Plugin = require('../../../../lib/segments/plugins/plugin'); 6 | 7 | describe('Plugin', function() { 8 | const METADATA_HOST = 'http://localhost'; 9 | 10 | describe('#getPluginMetadata', function() { 11 | var data = { data: 1234 }; 12 | var getPluginMetadata = Plugin.getPluginMetadata; 13 | const METADATA_PATH = '/metadata'; 14 | const OPTIONS = { 15 | host: 'localhost', 16 | path: '/metadata' 17 | }; 18 | 19 | var getMetadata; 20 | 21 | it('should return metadata if 200 OK', function(done) { 22 | getMetadata = nock(METADATA_HOST) 23 | .get(METADATA_PATH) 24 | .reply(200, data); 25 | 26 | getPluginMetadata(OPTIONS, function(err, data) { 27 | expect(data.data).to.equal(1234); 28 | getMetadata.done(); 29 | done(); 30 | }); 31 | }); 32 | 33 | it('should retry on 5xx', function(done) { 34 | getMetadata = nock(METADATA_HOST) 35 | .get(METADATA_PATH) 36 | .times(3) 37 | .reply(500) 38 | .get(METADATA_PATH) 39 | .reply(200, data); 40 | 41 | getPluginMetadata(OPTIONS, function(err, data) { 42 | expect(data.data).to.equal(1234); 43 | getMetadata.done(); 44 | done(); 45 | }); 46 | }); 47 | 48 | it('should retry on 5xx 5 times then error out', function(done) { 49 | getMetadata = nock(METADATA_HOST) 50 | .get(METADATA_PATH) 51 | .times(3) 52 | .reply(500) 53 | .get(METADATA_PATH) 54 | .times(3) 55 | .reply(504); // Ensure retry on different 5xx codes 56 | 57 | getPluginMetadata(OPTIONS, function(err, data) { 58 | assert.isUndefined(data); 59 | getMetadata.done(); 60 | done(); 61 | }); 62 | }); 63 | 64 | it('should fast fail on 4xx status code', function(done) { 65 | getMetadata = nock(METADATA_HOST) 66 | .get(METADATA_PATH) 67 | .reply(400); 68 | 69 | getPluginMetadata(OPTIONS, function(err, data) { 70 | assert.isUndefined(data); 71 | getMetadata.done(); 72 | done(); 73 | }); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /packages/core/test/unit/segments/segment_utils.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | 3 | var SegmentUtils = require('../../../lib/segments/segment_utils'); 4 | 5 | describe('SegmentUtils', function() { 6 | afterEach(function() { 7 | SegmentUtils.setStreamingThreshold(100); 8 | }); 9 | 10 | describe('#setStreamingThreshold', function() { 11 | it('should override the default streaming threshold', function() { 12 | SegmentUtils.setStreamingThreshold(10); 13 | 14 | assert.equal(SegmentUtils.streamingThreshold, 10); 15 | }); 16 | }); 17 | 18 | describe('#getHttpResponseData', () => { 19 | it('should populate attributes as integers', () => { 20 | const responseWithStrings = {statusCode: '200', headers: {'content-length': '42'}}; 21 | const res = SegmentUtils.getHttpResponseData(responseWithStrings); 22 | assert.deepEqual(res, { 23 | 'content_length': 42, 24 | 'status': 200 25 | }); 26 | }); 27 | 28 | it('should omit missing properties', () => { 29 | const responseWithStatus = {statusCode: 200}; 30 | const responseWithLength = {headers: {'content-length': 42}}; 31 | const emptyResponse = {}; 32 | 33 | const statusRes = SegmentUtils.getHttpResponseData(responseWithStatus); 34 | const lengthRes = SegmentUtils.getHttpResponseData(responseWithLength); 35 | const emptyRes = SegmentUtils.getHttpResponseData(emptyResponse); 36 | 37 | assert.deepEqual(statusRes, { 38 | 'status': 200 39 | }); 40 | assert.deepEqual(lengthRes, { 41 | 'content_length': 42 42 | }); 43 | assert.deepEqual(emptyRes, {}); 44 | }); 45 | }); 46 | 47 | describe('#getJsonStringifyReplacer', () => { 48 | it('should stringify BigInts', () => { 49 | const obj = {foo: 1n, bar: BigInt(2)}; 50 | const replacer = SegmentUtils.getJsonStringifyReplacer(); 51 | const result = JSON.stringify(obj, replacer); 52 | 53 | assert.equal(result, '{"foo":"1","bar":"2"}'); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/core/test/unit/test_utils.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events'); 2 | var util = require('util'); 3 | 4 | var TestUtils = {}; 5 | 6 | TestUtils.TestEmitter = function TestEmitter() { 7 | EventEmitter.call(this); 8 | }; 9 | 10 | util.inherits(TestUtils.TestEmitter, EventEmitter); 11 | 12 | TestUtils.onEvent = function onEvent(event, fcn) { 13 | this.emitter.on(event, fcn.bind(this)); 14 | return this; 15 | }; 16 | 17 | TestUtils.randomString = function randomString(length) { 18 | var text = ''; 19 | var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.#-_$%^&@!'; 20 | for (var i = 0; i < length; i++) { 21 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 22 | } 23 | 24 | return text; 25 | }; 26 | 27 | module.exports = TestUtils; 28 | -------------------------------------------------------------------------------- /packages/core/test_async/integration/segment_maintained_across_awaited.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var http = require('http'); 3 | var AWSXRay = require('../../lib'); 4 | var Segment = AWSXRay.Segment; 5 | 6 | AWSXRay.enableAutomaticMode(); 7 | 8 | describe('Integration', function() { 9 | describe('#async', function() { 10 | it('should maintain segment in async functions', function(done) { 11 | var sharedPromise = null; 12 | 13 | var requestCount = 0; 14 | var server = http 15 | .createServer(function(req, res) { 16 | var ns = AWSXRay.getNamespace(); 17 | ns.bindEmitter(req); 18 | ns.bindEmitter(res); 19 | 20 | ns.run(function () { 21 | var segment = new Segment('root'); 22 | 23 | AWSXRay.setSegment(segment); 24 | 25 | if (!sharedPromise) { 26 | sharedPromise = Promise.resolve(); 27 | } 28 | 29 | // execute an async task 30 | sharedPromise.then(async () => { 31 | 32 | await sleep(0); 33 | 34 | var retrievedSegment = AWSXRay.getSegment(); 35 | res.end(); 36 | 37 | // setTimeout so the assertion isn't caught by the promise 38 | setTimeout(function() { 39 | assert.equal(segment.id, retrievedSegment.id); 40 | if (++requestCount === 2) { 41 | done(); 42 | } 43 | }); 44 | }); 45 | }); 46 | }).listen(8080, '0.0.0.0', function() { 47 | 48 | var address = server.address(); 49 | 50 | var count = 0; 51 | function cb(err) { 52 | if (err) { 53 | throw err; 54 | } 55 | 56 | if (++count === 2) { 57 | server.close(); 58 | } 59 | } 60 | sendRequest(address, cb); 61 | sendRequest(address, cb); 62 | }); 63 | }); 64 | }); 65 | }); 66 | 67 | function sendRequest(address, cb) { 68 | http 69 | .request({ 70 | hostname: address.address, 71 | port: address.port, 72 | path: '/' 73 | }) 74 | .on('response', function(res) { 75 | res.on('end', cb).resume(); 76 | }) 77 | .on('error', cb) 78 | .end(); 79 | } 80 | 81 | function sleep(ms) { 82 | return new Promise((resolve) => { 83 | setTimeout(resolve, ms); 84 | }); 85 | } 86 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | "strict": true, 6 | "strictNullChecks": true, 7 | "declaration": false, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "allowJs": true, 11 | "checkJs": false, 12 | "outDir": "dist", 13 | }, 14 | "include": ["lib", "test", "test_async"], 15 | "exclude": ["dist", "node_modules"] 16 | } 17 | -------------------------------------------------------------------------------- /packages/express/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/express/.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | node_modules 3 | npm-debug.log 4 | docs 5 | AWSXRay.log 6 | Config 7 | -------------------------------------------------------------------------------- /packages/express/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for AWS X-Ray SDK Express for JavaScript 2 | 3 | **Beginning after v3.2.0, ChangeLog entries for this package are recorded in the [top-level CHANGELOG file](../../CHANGELOG.md).** 4 | 5 | 6 | 7 | ## 2.5.0 8 | * improvement: Added TypeScript definitions [PR #207](https://github.com/aws/aws-xray-sdk-node/pull/207) 9 | 10 | ## 2.3.4 11 | * improvement: Updated eslint dev dependency: [PR #145](https://github.com/aws/aws-xray-sdk-node/pull/145) 12 | * improvement: Updated .eslintrc.json to enable es6 and fixed eslint errors: [PR #146](https://github.com/aws/aws-xray-sdk-node/pull/146) 13 | * improvement: Updated nock,mocha,sinon dependencies to fix lodash version: [PR #153](https://github.com/aws/aws-xray-sdk-node/pull/153) 14 | 15 | ## 2.3.3 16 | * bugfix(express): express middleware closes segments when client request cancelled. [PR#128](https://github.com/aws/aws-xray-sdk-node/pull/128) 17 | 18 | ## 1.1.5 19 | * The X-Ray SDK for Node.js is now an open source project. You can follow the project and submit issues and pull requests on [GitHub](https://github.com/aws/aws-xray-sdk-node). 20 | 21 | ## 1.1.2 22 | * bugfix: Changed behavior on a http status code 429. Segment should have been marked as 'throttle' and 'error'. 23 | 24 | ## 1.1.1 25 | * feature: Added debug logs for opening and closing segments. 26 | -------------------------------------------------------------------------------- /packages/express/NOTICE.txt: -------------------------------------------------------------------------------- 1 | AWS X-Ray SDK Express for JavaScript 2 | Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | This product includes software developed at 5 | Amazon Web Services, Inc. (http://aws.amazon.com/). 6 | -------------------------------------------------------------------------------- /packages/express/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Requirements 3 | 4 | * AWS X-Ray SDK Core (aws-xray-sdk-core) 5 | * Express 4.14.0 or greater 6 | 7 | ## AWS X-Ray and Express 8 | 9 | The AWS X-Ray Express package automatically records information for incoming and outgoing 10 | requests and responses, via the middleware functions in this package. To configure sampling, 11 | dynamic naming, and more see the [set up section](https://github.com/aws/aws-xray-sdk-node/tree/master/packages/core#setup). 12 | 13 | The AWS X-Ray SDK Core has two modes - `manual` and `automatic`. 14 | Automatic mode uses the `cls-hooked` package and automatically 15 | tracks the current segment and subsegment. This is the default mode. 16 | Manual mode requires that you pass around the segment reference. 17 | 18 | In automatic mode, you can get the current segment/subsegment at any time: 19 | 20 | var segment = AWSXRay.getSegment(); 21 | 22 | In manual mode, you can get the base segment off of the request object: 23 | 24 | var segment = req.segment; 25 | 26 | ## Middleware Usage 27 | 28 | The X-Ray SDK provides two middlewares: `AWSXRay.express.openSegment()` 29 | and `AWSXRay.express.closeSegment()`. These two middlewares must be used together 30 | and wrap all of your defined routes that you'd like to trace. 31 | In automatic mode, the `openSegment` middleware *must* be the last middleware added 32 | before defining routes, and the `closeSegment` middleware *must* be the 33 | first middleware added after defining routes. Otherwise issues with the `cls-hooked` 34 | context may occur. 35 | 36 | ## Sample App 37 | 38 | To get started with a functional express application instrumented with the X-Ray SDK, check out our [sample app](https://github.com/aws-samples/aws-xray-sdk-node-sample). 39 | 40 | ## Automatic mode example 41 | For more automatic mode examples, see the 42 | [Automatic Mode Examples](https://github.com/aws/aws-xray-sdk-node/tree/master/packages/core#Automatic-Mode-Examples). 43 | 44 | ### Capture all incoming requests to `/` and `/directory` 45 | 46 | ```js 47 | var AWSXRay = require('aws-xray-sdk-core'); 48 | var xrayExpress = require('aws-xray-sdk-express'); 49 | var app = express(); 50 | 51 | //... 52 | 53 | app.use(xrayExpress.openSegment('defaultName')); 54 | 55 | app.get('/', function (req, res) { 56 | var segment = AWSXRay.getSegment(); 57 | segment.addAnnotation('page', 'home'); 58 | 59 | //... 60 | 61 | res.render('index'); 62 | }); 63 | 64 | app.get('/directory', function (req, res) { 65 | var segment = AWSXRay.getSegment(); 66 | segment.addAnnotation('page', 'directory'); 67 | 68 | //... 69 | 70 | res.render('directory'); 71 | }); 72 | 73 | app.use(xrayExpress.closeSegment()); 74 | ``` 75 | 76 | ## Manual mode examples 77 | For more manual mode examples, e.g. what to do with the segment inside your route logic, 78 | see the [Manual Mode Examples](https://github.com/aws/aws-xray-sdk-node/tree/master/packages/core#Manual-Mode-Examples). Note that you don't have to manually start or close the segments since that is handled by the X-Ray middleware. 79 | 80 | ### Capture All incoming requests to `/` 81 | 82 | ```js 83 | var AWSXRay = require('aws-xray-sdk-core'); 84 | var xrayExpress = require('aws-xray-sdk-express'); 85 | var app = express(); 86 | 87 | //... 88 | 89 | var AWSXRay = require('aws-xray-sdk'); 90 | 91 | //Required at the start of your routes 92 | app.use(xrayExpress.openSegment('defaultName')); 93 | 94 | app.get('/', function (req, res) { 95 | var segment = req.segment; 96 | 97 | //... 98 | 99 | res.render('index'); 100 | }); 101 | 102 | app.use(xrayExpress.closeSegment()); //Required at the end of your routes / first in error handling routes 103 | ``` 104 | -------------------------------------------------------------------------------- /packages/express/lib/express_mw.d.ts: -------------------------------------------------------------------------------- 1 | import { ErrorRequestHandler, RequestHandler } from 'express'; 2 | 3 | export function openSegment(defaultName: string): RequestHandler; 4 | 5 | export function closeSegment(): ErrorRequestHandler; 6 | -------------------------------------------------------------------------------- /packages/express/lib/express_mw.js: -------------------------------------------------------------------------------- 1 | const AWSXRay = require('aws-xray-sdk-core'); 2 | 3 | const mwUtils = AWSXRay.middleware; 4 | 5 | /** 6 | * Express middleware module. 7 | * 8 | * Exposes Express middleware functions to enable automated data capturing on a web service. To enable on a Node.js/Express application, 9 | * use 'app.use(AWSXRayExpress.openSegment())' before defining your routes. After your routes, before any extra error 10 | * handling middleware, use 'app.use(AWSXRayExpress.closeSegment())'. 11 | * Use AWSXRay.getSegment() to access the current sub/segment. 12 | * Otherwise, for manual mode, this appends the Segment object to the request object as req.segment. 13 | * @module express_mw 14 | */ 15 | const expressMW = { 16 | 17 | /** 18 | * Use 'app.use(AWSXRayExpress.openSegment('defaultName'))' before defining your routes. 19 | * Use AWSXRay.getSegment() to access the current sub/segment. 20 | * Otherwise, for manual mode, this appends the Segment object to the request object as req.segment. 21 | * @param {string} defaultName - The default name for the segment. 22 | * @alias module:express_mw.openSegment 23 | * @returns {function} 24 | */ 25 | openSegment: (defaultName) => { 26 | if (!defaultName || typeof defaultName !== 'string') { 27 | throw new Error('Default segment name was not supplied. Please provide a string.'); 28 | } 29 | 30 | mwUtils.setDefaultName(defaultName); 31 | 32 | return (req, res, next) => { 33 | const segment = mwUtils.traceRequestResponseCycle(req, res); 34 | 35 | if (AWSXRay.isAutomaticMode()) { 36 | const ns = AWSXRay.getNamespace(); 37 | ns.bindEmitter(req); 38 | ns.bindEmitter(res); 39 | 40 | ns.run(() => { 41 | AWSXRay.setSegment(segment); 42 | if (next) { 43 | next(); 44 | } 45 | }); 46 | } else { 47 | req.segment = segment; 48 | if (next) { 49 | next(); 50 | } 51 | } 52 | }; 53 | }, 54 | 55 | /** 56 | * After your routes, before any extra error handling middleware, use 'app.use(AWSXRayExpress.closeSegment())'. 57 | * This is error-handling middleware, so it is called only when there is a server-side fault. 58 | * @alias module:express_mw.closeSegment 59 | * @returns {function} 60 | */ 61 | closeSegment: () => { 62 | return (err, req, res, next) => { 63 | const segment = AWSXRay.resolveSegment(req.segment); 64 | 65 | if (segment && err) { 66 | segment.addError(err); 67 | AWSXRay.getLogger().debug('Added Express server fault to segment'); 68 | } 69 | 70 | if (next) { 71 | next(err); 72 | } 73 | }; 74 | } 75 | }; 76 | 77 | module.exports = expressMW; 78 | -------------------------------------------------------------------------------- /packages/express/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as AWSXRay from 'aws-xray-sdk-core'; 2 | 3 | declare global { 4 | namespace Express { 5 | interface Request { 6 | segment?: AWSXRay.Segment; 7 | } 8 | } 9 | } 10 | 11 | export * from './express_mw'; 12 | -------------------------------------------------------------------------------- /packages/express/lib/index.js: -------------------------------------------------------------------------------- 1 | // Convenience file to require the SDK from the root of the repository 2 | module.exports = require('./express_mw'); 3 | -------------------------------------------------------------------------------- /packages/express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-xray-sdk-express", 3 | "version": "3.10.3", 4 | "description": "AWS X-Ray Middleware for Express (Javascript)", 5 | "author": "Amazon Web Services", 6 | "contributors": [ 7 | "Sandra McMullen " 8 | ], 9 | "main": "lib/index.js", 10 | "types": "lib/index.d.ts", 11 | "engines": { 12 | "node": ">= 14.x" 13 | }, 14 | "directories": { 15 | "test": "test" 16 | }, 17 | "dependencies": { 18 | "@types/express": "*" 19 | }, 20 | "peerDependencies": { 21 | "aws-xray-sdk-core": "^3.10.3" 22 | }, 23 | "scripts": { 24 | "test": "mocha --recursive ./test/ -R spec && tsd", 25 | "test-d": "tsd", 26 | "lint": "eslint .", 27 | "lint:fix": "eslint . --fix" 28 | }, 29 | "keywords": [ 30 | "amazon", 31 | "api", 32 | "aws", 33 | "express", 34 | "xray", 35 | "x-ray", 36 | "x ray" 37 | ], 38 | "license": "Apache-2.0", 39 | "repository": "https://github.com/aws/aws-xray-sdk-node/tree/master/packages/express" 40 | } 41 | -------------------------------------------------------------------------------- /packages/express/test-d/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import * as AWSXRay from 'aws-xray-sdk-core'; 2 | import express from 'express'; 3 | import { expectType } from 'tsd'; 4 | import * as xrayExpress from '../lib'; 5 | 6 | const app = express(); 7 | 8 | app.use(xrayExpress.openSegment('defaultName')); 9 | 10 | app.get('/', function(req, res) { 11 | expectType(req.segment); 12 | res.render('index'); 13 | }); 14 | 15 | app.use(xrayExpress.closeSegment()); 16 | -------------------------------------------------------------------------------- /packages/express/test/test_utils.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events'); 2 | var util = require('util'); 3 | 4 | var TestUtils = {}; 5 | 6 | TestUtils.TestEmitter = function TestEmitter() { 7 | EventEmitter.call(this); 8 | }; 9 | 10 | util.inherits(TestUtils.TestEmitter, EventEmitter); 11 | 12 | TestUtils.onEvent = function onEvent(event, fcn) { 13 | this.emitter.on(event, fcn.bind(this)); 14 | return this; 15 | }; 16 | 17 | module.exports = TestUtils; 18 | -------------------------------------------------------------------------------- /packages/express/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/full_sdk/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/full_sdk/.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | node_modules 3 | npm-debug.log 4 | docs 5 | AWSXRay.log 6 | Config 7 | -------------------------------------------------------------------------------- /packages/full_sdk/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for AWS X-Ray SDK for JavaScript 2 | 3 | The primary changelog for this SDK has moved to the [root of the repo](https://github.com/aws/aws-xray-sdk-node/blob/master/CHANGELOG.md). 4 | -------------------------------------------------------------------------------- /packages/full_sdk/NOTICE.txt: -------------------------------------------------------------------------------- 1 | AWS X-Ray SDK for JavaScript 2 | Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | This product includes software developed at 5 | Amazon Web Services, Inc. (http://aws.amazon.com/). 6 | -------------------------------------------------------------------------------- /packages/full_sdk/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Requirements 3 | 4 | * AWS SDK v2.7.15 or greater (if using `captureAWS` or `captureAWSClient`) 5 | * Express 4.14.0 or greater (if using Express and the associated X-Ray middleware) 6 | * MySQL 2.12.0 or greater (if using `captureMySQL`) 7 | * Postgres 6.1.0 or greater (if using `capturePostgres`) 8 | 9 | ## AWS X-Ray 10 | 11 | The AWS X-Ray SDK automatically records information for incoming and outgoing requests and responses (via middleware), as well as local data 12 | such as function calls, time, variables (via metadata and annotations), even EC2 instance data (via plugins). 13 | 14 | Although the AWS X-Ray SDK was originally intended to capture request/response data on a web app, the SDK provides functionality for use cases 15 | outside this as well. The SDK exposes the 'Segment' and 'Subsegment' objects to create your own capturing mechanisms, middleware, etc. 16 | 17 | This package includes the following AWS X-Ray packages. 18 | 19 | aws-xray-sdk-core 20 | aws-xray-sdk-express 21 | aws-xray-sdk-postgres 22 | aws-xray-sdk-mysql 23 | 24 | ## Setup 25 | 26 | The core package contains the base SDK functionality. Please see the aws-xray-sdk-core [README.md](https://github.com/aws/aws-xray-sdk-node/tree/master/packages/core/README.md) for more details. 27 | 28 | ### Support for web frameworks 29 | 30 | * [Express](https://github.com/aws/aws-xray-sdk-node/tree/master/packages/express) 31 | * [Restify](https://github.com/aws/aws-xray-sdk-node/tree/master/packages/restify) 32 | -------------------------------------------------------------------------------- /packages/full_sdk/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'aws-xray-sdk-express'; 2 | import { captureMySQL } from 'aws-xray-sdk-mysql'; 3 | import { capturePostgres } from 'aws-xray-sdk-postgres'; 4 | 5 | export * from 'aws-xray-sdk-core'; 6 | 7 | export { 8 | express, 9 | captureMySQL, 10 | capturePostgres 11 | }; 12 | -------------------------------------------------------------------------------- /packages/full_sdk/lib/index.js: -------------------------------------------------------------------------------- 1 | // Convenience file to require the SDK from the root of the repository 2 | var AWSXRay = require('aws-xray-sdk-core'); 3 | AWSXRay.express = require('aws-xray-sdk-express'); 4 | AWSXRay.captureMySQL = require('aws-xray-sdk-mysql'); 5 | AWSXRay.capturePostgres = require('aws-xray-sdk-postgres'); 6 | 7 | // Import Data from package.json, 8 | // If the importing of package.json fails leave 9 | // pkginfo as an empty object 10 | var pkginfo = {}; 11 | try { 12 | pkginfo = require('../package.json'); 13 | } catch (err) { 14 | AWSXRay.getLogger().debug('Failed to load SDK data:', err); 15 | } 16 | 17 | var UNKNOWN = 'unknown'; 18 | 19 | (function () { 20 | var sdkData = AWSXRay.SegmentUtils.sdkData || { sdk: 'X-Ray for Node.js' }; 21 | sdkData.sdk_version = pkginfo.version ? pkginfo.version : UNKNOWN; 22 | sdkData.package = pkginfo.name ? pkginfo.name : UNKNOWN; 23 | AWSXRay.SegmentUtils.setSDKData(sdkData); 24 | })(); 25 | 26 | module.exports = AWSXRay; 27 | -------------------------------------------------------------------------------- /packages/full_sdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-xray-sdk", 3 | "version": "3.10.3", 4 | "description": "AWS X-Ray SDK for Javascript", 5 | "author": "Amazon Web Services", 6 | "contributors": [ 7 | "Sandra McMullen ", 8 | "William Armiros " 9 | ], 10 | "main": "lib/index.js", 11 | "types": "lib/index.d.ts", 12 | "engines": { 13 | "node": ">= 14.x" 14 | }, 15 | "dependencies": { 16 | "aws-xray-sdk-core": "file:../core", 17 | "aws-xray-sdk-express": "file:../express", 18 | "aws-xray-sdk-mysql": "file:../mysql", 19 | "aws-xray-sdk-postgres": "file:../postgres" 20 | }, 21 | "scripts": { 22 | "test": "tsd", 23 | "test-d": "tsd", 24 | "lint": "eslint .", 25 | "lint:fix": "eslint . --fix" 26 | }, 27 | "keywords": [ 28 | "amazon", 29 | "api", 30 | "aws", 31 | "xray", 32 | "x-ray", 33 | "x ray" 34 | ], 35 | "license": "Apache-2.0", 36 | "repository": "https://github.com/aws/aws-xray-sdk-node/tree/master/packages/full_sdk" 37 | } 38 | -------------------------------------------------------------------------------- /packages/full_sdk/test-d/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'aws-xray-sdk-express'; 2 | import { captureMySQL } from 'aws-xray-sdk-mysql'; 3 | import { capturePostgres } from 'aws-xray-sdk-postgres'; 4 | import { expectType } from 'tsd'; 5 | import * as AWSXRay from '../lib'; 6 | 7 | expectType(AWSXRay.express); 8 | expectType(AWSXRay.captureMySQL); 9 | expectType(AWSXRay.capturePostgres); 10 | -------------------------------------------------------------------------------- /packages/full_sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/mysql/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/mysql/.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | node_modules 3 | npm-debug.log 4 | docs 5 | AWSXRay.log 6 | Config 7 | -------------------------------------------------------------------------------- /packages/mysql/NOTICE.txt: -------------------------------------------------------------------------------- 1 | AWS X-Ray SDK MySQL for JavaScript 2 | Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | This product includes software developed at 5 | Amazon Web Services, Inc. (http://aws.amazon.com/). 6 | -------------------------------------------------------------------------------- /packages/mysql/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Requirements 3 | 4 | * AWS X-Ray SDK Core 5 | * MySQL 2.12.0 or greater 6 | 7 | ## AWS X-Ray and MySQL 8 | 9 | The AWS X-Ray MySQL package automatically records query information and request and 10 | response data. Simply patch the MySQL package via `captureMySQL` as shown below. 11 | 12 | The AWS X-Ray SDK Core has two modes - `manual` and `automatic`. 13 | Automatic mode uses the `cls-hooked` package and automatically 14 | tracks the current segment and subsegment. This is the default mode. 15 | Manual mode requires that you pass around the segment reference. See the examples below. 16 | 17 | ### Environment variables 18 | 19 | MYSQL_DATABASE_VERSION Sets additional data for the sql subsegment. 20 | MYSQL_DRIVER_VERSION Sets additional data for the sql subsegment. 21 | 22 | ### Lambda Example 23 | 24 | ```js 25 | var AWSXRay = require('aws-xray-sdk'); 26 | var pg = AWSXRay.captureMySQL(require('mysql')); 27 | 28 | ... 29 | 30 | exports.handler = function (event, context, callback) { 31 | // Make MySQL queries normally 32 | } 33 | ``` 34 | 35 | ## Automatic mode example 36 | 37 | ```js 38 | var AWSXRay = require('aws-xray-sdk-core'); 39 | var captureMySQL = require('aws-xray-sdk-mysql'); 40 | 41 | var mysql = captureMySQL(require('mysql')); 42 | 43 | var config = { ... }; 44 | 45 | ... 46 | 47 | var connection = mysql.createConnection(config); 48 | 49 | connection.query('SELECT * FROM cats', function(err, rows) { 50 | //Automatically captures query information and errors (if any) 51 | }); 52 | 53 | ... 54 | 55 | var pool = mysql.createPool(config); 56 | 57 | pool.query('SELECT * FROM cats', function(err, rows, fields) { 58 | //Automatically captures query information and errors (if any) 59 | } 60 | ``` 61 | 62 | ## Manual mode example 63 | 64 | ```js 65 | var AWSXRay = require('aws-xray-sdk-core'); 66 | var captureMySQL = require('aws-xray-sdk-mysql'); 67 | 68 | var mysql = captureMySQL(require('mysql')); 69 | 70 | var config = { ... }; 71 | 72 | ... 73 | 74 | var connection = mysql.createConnection(config); 75 | 76 | connection.query('SELECT * FROM cats', function(err, rows) { 77 | //Automatically captures query information and errors (if any) 78 | }, segment); 79 | 80 | ... 81 | 82 | var pool = mysql.createPool(config); 83 | 84 | pool.query('SELECT * FROM cats', function(err, rows, fields) { 85 | //Automatically captures query information and errors (if any) 86 | }, segment); 87 | ``` 88 | -------------------------------------------------------------------------------- /packages/mysql/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './mysql_p'; 2 | -------------------------------------------------------------------------------- /packages/mysql/lib/index.js: -------------------------------------------------------------------------------- 1 | // Convenience file to require the SDK from the root of the repository 2 | module.exports = require('./mysql_p'); 3 | -------------------------------------------------------------------------------- /packages/mysql/lib/mysql_p.d.ts: -------------------------------------------------------------------------------- 1 | import * as AWSXRay from 'aws-xray-sdk-core'; 2 | import * as MySQL from 'mysql'; 3 | 4 | export function captureMySQL(mysql: typeof MySQL): captureMySQL.PatchedMySQL; 5 | 6 | declare namespace captureMySQL { 7 | interface PatchedQueryFunction { 8 | (query: MySQL.Query, segment?: AWSXRay.SegmentLike): MySQL.Query; 9 | (options: string | MySQL.QueryOptions, callback?: MySQL.queryCallback, segment?: AWSXRay.SegmentLike): MySQL.Query; 10 | (options: string, values: any, callback?: MySQL.queryCallback, segment?: AWSXRay.SegmentLike): MySQL.Query; 11 | } 12 | 13 | type PatchedConnection = { 14 | [K in keyof T]: K extends 'query' 15 | ? PatchedQueryFunction 16 | : T[K]; 17 | }; 18 | 19 | type PatchedPoolConnection = PatchedConnection; 20 | 21 | type PatchedPoolConnectionCallback = (err: MySQL.MysqlError, connection: PatchedPoolConnection) => void; 22 | 23 | interface PatchedPoolGetConnectionFunction { 24 | (callback: PatchedPoolConnectionCallback): void; 25 | } 26 | 27 | type PatchedPool = { 28 | [K in keyof T]: K extends 'query' 29 | ? PatchedQueryFunction 30 | : K extends 'getConnection' 31 | ? PatchedPoolGetConnectionFunction 32 | : T[K]; 33 | }; 34 | 35 | interface PatchedPoolClusterOfFunction { 36 | (pattern: string, selector?: string): PatchedPool; 37 | (pattern: undefined | null | false, selector: string): PatchedPool; 38 | } 39 | 40 | interface PatchedPoolClusterGetConnectionFunction { 41 | (callback: PatchedPoolConnectionCallback): void; 42 | (pattern: string, callback: PatchedPoolConnectionCallback): void; 43 | (pattern: string, selector: string, callback: PatchedPoolConnectionCallback): void; 44 | } 45 | 46 | type PatchedPoolCluster = { 47 | [K in keyof T]: K extends 'of' 48 | ? PatchedPoolClusterOfFunction 49 | : K extends 'getConnection' 50 | ? PatchedPoolClusterGetConnectionFunction 51 | : T[K] 52 | }; 53 | 54 | interface PatchedCreateConnectionFunction { 55 | (connectionUri: string | MySQL.ConnectionConfig): PatchedConnection; 56 | } 57 | 58 | interface PatchedCreatePoolFunction { 59 | (config: MySQL.PoolConfig | string): PatchedPool; 60 | } 61 | 62 | interface PatchedCreatePoolClusterFunction { 63 | (config?: MySQL.PoolClusterConfig): PatchedPoolCluster; 64 | } 65 | 66 | type PatchedMySQL = { 67 | [K in keyof T]: K extends 'createConnection' 68 | ? PatchedCreateConnectionFunction 69 | : K extends 'createPool' 70 | ? PatchedCreatePoolFunction 71 | : K extends 'createPoolCluster' 72 | ? PatchedCreatePoolClusterFunction 73 | : T[K]; 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /packages/mysql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-xray-sdk-mysql", 3 | "version": "3.10.3", 4 | "description": "AWS X-Ray Patcher for MySQL (Javascript)", 5 | "author": "Amazon Web Services", 6 | "contributors": [ 7 | "Sandra McMullen " 8 | ], 9 | "main": "lib/index.js", 10 | "types": "lib/index.d.ts", 11 | "engines": { 12 | "node": ">= 14.x" 13 | }, 14 | "directories": { 15 | "test": "test" 16 | }, 17 | "dependencies": { 18 | "@types/mysql": "*" 19 | }, 20 | "peerDependencies": { 21 | "aws-xray-sdk-core": "^3.10.3" 22 | }, 23 | "scripts": { 24 | "test": "mocha --recursive ./test/ -R spec && tsd", 25 | "test-d": "tsd", 26 | "lint": "eslint .", 27 | "lint:fix": "eslint . --fix" 28 | }, 29 | "keywords": [ 30 | "amazon", 31 | "api", 32 | "aws", 33 | "mysql", 34 | "xray", 35 | "x-ray", 36 | "x ray" 37 | ], 38 | "license": "Apache-2.0", 39 | "repository": "https://github.com/aws/aws-xray-sdk-node/tree/master/packages/mysql" 40 | } 41 | -------------------------------------------------------------------------------- /packages/mysql/test-d/index.test-d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import * as AWSXRay from 'aws-xray-sdk-core'; 3 | import * as MySQL from 'mysql'; 4 | import { expectType } from 'tsd'; 5 | import { captureMySQL } from '../lib'; 6 | 7 | const segment = AWSXRay.getSegment(); 8 | 9 | const mysql = captureMySQL(MySQL); 10 | 11 | const config = {}; 12 | 13 | const connection: captureMySQL.PatchedConnection = mysql.createConnection(config); 14 | const pool: captureMySQL.PatchedPool = mysql.createPool(config); 15 | const poolCluster: captureMySQL.PatchedPoolCluster = mysql.createPoolCluster(config); 16 | 17 | const queryCallback: MySQL.queryCallback = function(err: MySQL.MysqlError | null, rows: any) { 18 | }; 19 | 20 | const getConnectionCallback = function(err: MySQL.MysqlError, conn: captureMySQL.PatchedConnection) { 21 | }; 22 | 23 | expectType(connection.query('SELECT * FROM cats', queryCallback)); 24 | expectType(connection.query('SELECT * FROM cats', queryCallback, segment)); 25 | 26 | expectType(pool.query('SELECT * FROM cats', queryCallback)); 27 | expectType(pool.query('SELECT * FROM cats', queryCallback, segment)); 28 | expectType(pool.getConnection(getConnectionCallback)); 29 | 30 | expectType(poolCluster.getConnection(getConnectionCallback)); 31 | expectType(poolCluster.getConnection('pattern', getConnectionCallback)); 32 | expectType(poolCluster.getConnection('pattern', 'selector', getConnectionCallback)); 33 | 34 | expectType(poolCluster.of('pattern')); 35 | expectType(poolCluster.of('pattern', 'selector')); 36 | expectType(poolCluster.of(null, 'selector')); 37 | -------------------------------------------------------------------------------- /packages/mysql/test/test_utils.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events'); 2 | var util = require('util'); 3 | 4 | var TestUtils = {}; 5 | 6 | TestUtils.TestEmitter = function TestEmitter() { 7 | EventEmitter.call(this); 8 | }; 9 | 10 | util.inherits(TestUtils.TestEmitter, EventEmitter); 11 | 12 | TestUtils.onEvent = function onEvent(event, fcn) { 13 | this.emitter.on(event, fcn.bind(this)); 14 | return this; 15 | }; 16 | 17 | module.exports = TestUtils; 18 | -------------------------------------------------------------------------------- /packages/mysql/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/postgres/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/postgres/.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | node_modules 3 | npm-debug.log 4 | docs 5 | AWSXRay.log 6 | Config 7 | -------------------------------------------------------------------------------- /packages/postgres/NOTICE.txt: -------------------------------------------------------------------------------- 1 | AWS X-Ray SDK Postgres for JavaScript 2 | Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | This product includes software developed at 5 | Amazon Web Services, Inc. (http://aws.amazon.com/). 6 | -------------------------------------------------------------------------------- /packages/postgres/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Requirements 3 | 4 | * AWS X-Ray SDK Core 5 | * Postgres 6.1.0 or greater 6 | 7 | ## AWS X-Ray and Postgres 8 | 9 | The AWS X-Ray Postgres package automatically records query information and request 10 | and response data. Simply patch the Postgres package via `capturePostgres` as shown below. 11 | 12 | The AWS X-Ray SDK Core has two modes - `manual` and `automatic`. 13 | Automatic mode uses the `cls-hooked` package and automatically 14 | tracks the current segment and subsegment. This is the default mode. 15 | Manual mode requires that you pass around the segment reference. See the examples below. 16 | 17 | ### Environment variables 18 | 19 | POSTGRES_DATABASE_VERSION Sets additional data for the sql subsegment. 20 | POSTGRES_DRIVER_VERSION Sets additional data for the sql subsegment. 21 | 22 | ### Lambda Example 23 | 24 | ```js 25 | var AWSXRay = require('aws-xray-sdk'); 26 | var pg = AWSXRay.capturePostgres(require('pg')); 27 | 28 | ... 29 | 30 | exports.handler = function (event, context, callback) { 31 | // Make postgres queries normally 32 | } 33 | ``` 34 | 35 | ### Automatic mode example 36 | 37 | ```js 38 | var AWSXRay = require('aws-xray-sdk-core'); 39 | var capturePostgres = require('aws-xray-sdk-postgres'); 40 | 41 | var pg = capturePostgres(require('pg')); 42 | 43 | ... 44 | 45 | var client = new pg.Client(); 46 | 47 | client.connect(function (err) { 48 | ... 49 | 50 | client.query({name: 'moop', text: 'SELECT $1::text as name'}, ['brianc'], function (err, result) { 51 | //Automatically captures query information and errors (if any) 52 | }); 53 | }); 54 | 55 | ... 56 | 57 | var pool = new pg.Pool(config); 58 | pool.connect(function(err, client, done) { 59 | if(err) { 60 | return console.error('error fetching client from pool', err); 61 | } 62 | var query = client.query('SELECT * FROM mytable', function(err, result) { 63 | //Automatically captures query information and errors (if any) 64 | }); 65 | }); 66 | ``` 67 | 68 | ### Manual mode example 69 | 70 | ```js 71 | var AWSXRay = require('aws-xray-sdk-core'); 72 | var capturePostgres = require('aws-xray-sdk-postgres'); 73 | 74 | var pg = capturePostgres(require('pg')); 75 | 76 | ... 77 | 78 | var client = new pg.Client(); 79 | 80 | client.connect(function (err) { 81 | ... 82 | 83 | client.query({name: 'moop', text: 'SELECT $1::text as name'}, ['mcmuls'], function (err, result) { 84 | //Automatically captures query information and errors (if any) 85 | }); 86 | }); 87 | 88 | ... 89 | 90 | var pool = new pg.Pool(config); 91 | pool.connect(function(err, client, done) { 92 | if(err) { 93 | return console.error('error fetching client from pool', err); 94 | } 95 | var query = client.query('SELECT * FROM mytable', function(err, result) { 96 | //Automatically captures query information and errors (if any) 97 | }, segment)); 98 | }; 99 | ``` -------------------------------------------------------------------------------- /packages/postgres/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './postgres_p'; 2 | -------------------------------------------------------------------------------- /packages/postgres/lib/index.js: -------------------------------------------------------------------------------- 1 | // Convenience file to require the SDK from the root of the repository 2 | module.exports = require('./postgres_p'); 3 | -------------------------------------------------------------------------------- /packages/postgres/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-xray-sdk-postgres", 3 | "version": "3.10.3", 4 | "description": "AWS X-Ray Patcher for Postgres (Javascript)", 5 | "author": "Amazon Web Services", 6 | "contributors": [ 7 | "Sandra McMullen " 8 | ], 9 | "main": "lib/index.js", 10 | "types": "lib/index.d.ts", 11 | "engines": { 12 | "node": ">= 14.x" 13 | }, 14 | "directories": { 15 | "test": "test" 16 | }, 17 | "dependencies": { 18 | "@types/pg": "*" 19 | }, 20 | "peerDependencies": { 21 | "aws-xray-sdk-core": "^3.10.3" 22 | }, 23 | "scripts": { 24 | "test": "mocha --recursive ./test/ -R spec && tsd", 25 | "test-d": "tsd", 26 | "lint": "eslint .", 27 | "lint:fix": "eslint . --fix" 28 | }, 29 | "keywords": [ 30 | "amazon", 31 | "api", 32 | "aws", 33 | "postgres", 34 | "xray", 35 | "x-ray", 36 | "x ray" 37 | ], 38 | "license": "Apache-2.0", 39 | "repository": "https://github.com/aws/aws-xray-sdk-node/tree/master/packages/postgres" 40 | } 41 | -------------------------------------------------------------------------------- /packages/postgres/test-d/index.test-d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import * as AWSXRay from 'aws-xray-sdk-core'; 3 | import * as PG from 'pg'; 4 | import { expectType } from 'tsd'; 5 | import { capturePostgres } from '../lib'; 6 | 7 | const segment = AWSXRay.getSegment(); 8 | 9 | const pg = capturePostgres(PG); 10 | 11 | const client: capturePostgres.PatchedClient = new pg.Client(); 12 | 13 | client.connect((err: Error) => { }); 14 | 15 | const pool = new pg.Pool(); 16 | 17 | pool.connect().then((client: capturePostgres.PatchedPoolClient) => { }); 18 | pool.connect((err: Error, client: capturePostgres.PatchedPoolClient, done: (release?: any) => void) => { }); 19 | 20 | pool.on('error', (err: Error, client: capturePostgres.PatchedPoolClient) => { }) 21 | .on('connect', (client: capturePostgres.PatchedPoolClient) => { }) 22 | .on('acquire', (client: capturePostgres.PatchedPoolClient) => { }) 23 | .on('remove', (client: capturePostgres.PatchedPoolClient) => { }); 24 | 25 | function testQuery(client: capturePostgres.PatchedClient | capturePostgres.PatchedPoolClient): void { 26 | const queryCallback = (err: Error, result: PG.QueryResult) => void { 27 | }; 28 | 29 | const queryArrayCallback = (err: Error, result: PG.QueryArrayResult) => void { 30 | }; 31 | 32 | const queryStream = new PG.Query(); 33 | expectType(client.query(queryStream)); 34 | expectType(client.query(queryStream, segment)); 35 | 36 | const queryArrayConfig: PG.QueryArrayConfig = { 37 | name: 'get-name', 38 | text: 'SELECT $1::text', 39 | values: ['brianc'], 40 | rowMode: 'array', 41 | }; 42 | expectType>(client.query(queryArrayConfig)); 43 | expectType>(client.query(queryArrayConfig, ['brianc'])); 44 | expectType>(client.query(queryArrayConfig, ['brianc'], segment)); 45 | 46 | expectType(client.query(queryArrayConfig, queryArrayCallback)); 47 | expectType(client.query(queryArrayConfig, queryArrayCallback)); 48 | expectType(client.query(queryArrayConfig, queryArrayCallback, segment)); 49 | 50 | const queryConfig: PG.QueryConfig = { 51 | name: 'moop', 52 | text: 'SELECT $1::text as name', 53 | values: ['brianc'] 54 | }; 55 | expectType>(client.query(queryConfig)); 56 | expectType>(client.query(queryConfig, ['brianc'])); 57 | expectType>(client.query(queryConfig, ['brianc'], segment)); 58 | 59 | expectType(client.query(queryConfig, queryCallback)); 60 | expectType(client.query(queryConfig, queryCallback)); 61 | expectType(client.query(queryConfig, queryCallback, segment)); 62 | 63 | const queryText = 'select $1::text as name'; 64 | expectType>(client.query(queryText)); 65 | expectType>(client.query(queryText, ['brianc'])); 66 | expectType>(client.query(queryText, ['brianc'], segment)); 67 | 68 | expectType(client.query(queryText, queryCallback)); 69 | expectType(client.query(queryText, ['brianc'], queryCallback)); 70 | expectType(client.query(queryText, ['brianc'], queryCallback, segment)); 71 | } 72 | -------------------------------------------------------------------------------- /packages/postgres/test/test_utils.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events'); 2 | var util = require('util'); 3 | 4 | var TestUtils = {}; 5 | 6 | TestUtils.TestEmitter = function TestEmitter() { 7 | EventEmitter.call(this); 8 | }; 9 | 10 | util.inherits(TestUtils.TestEmitter, EventEmitter); 11 | 12 | TestUtils.onEvent = function onEvent(event, fcn) { 13 | this.emitter.on(event, fcn.bind(this)); 14 | return this; 15 | }; 16 | 17 | module.exports = TestUtils; 18 | -------------------------------------------------------------------------------- /packages/postgres/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/restify/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/restify/.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | node_modules 3 | npm-debug.log 4 | docs 5 | AWSXRay.log 6 | Config 7 | -------------------------------------------------------------------------------- /packages/restify/NOTICE.txt: -------------------------------------------------------------------------------- 1 | AWS X-Ray SDK Restify for JavaScript 2 | Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | This product includes software developed at 5 | Amazon Web Services, Inc. (http://aws.amazon.com/). 6 | -------------------------------------------------------------------------------- /packages/restify/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Requirements 3 | 4 | * AWS X-Ray SDK Core (aws-xray-sdk-core) 5 | * Restify 4.3.0 or greater 6 | 7 | ## AWS X-Ray and Restify 8 | 9 | The AWS X-Ray Restify package automatically records information for incoming and outgoing 10 | requests and responses, via the 'enable' function in this package. To configure sampling, dynamic naming, and more see the [set up section](https://github.com/aws/aws-xray-sdk-node/tree/master/packages/core#setup). 11 | 12 | The AWS X-Ray SDK Core has two modes - `manual` and `automatic`. 13 | Automatic mode uses the `cls-hooked` package and automatically 14 | tracks the current segment and subsegment. This is the default mode. 15 | Manual mode requires that you pass around the segment reference. 16 | 17 | In automatic mode, you can get the current segment or subsegment at any time: 18 | 19 | var segment = AWSXRay.getSegment(); 20 | 21 | In manual mode, you can get the base segment off of the request object: 22 | 23 | var segment = req.segment; 24 | 25 | //If the restify context plugin is being used, it is placed under 'XRaySegment' 26 | var segment = req.get('XRaySegment'); 27 | 28 | ## Middleware usage 29 | 30 | The X-Ray SDK provides a single Restify middleware: 31 | `AWSXRayRestify.enable(, )`. This *must* be added as the last middleware before defining all routes you'd like to have traced, otherwise issues with the `cls-hooked` context may occur. Error capturing will be done automatically. 32 | 33 | ## Automatic mode examples 34 | 35 | For more automatic mode examples, see the [example code](https://github.com/aws/aws-xray-sdk-node/tree/master/packages/core#Automatic-Mode-Examples). 36 | 37 | ```js 38 | var AWSXRayRestify = require('aws-xray-sdk-restify'); 39 | var restify = require('restify'); 40 | var server = restify.createServer(); 41 | 42 | //... 43 | 44 | AWSXRayRestify.enable(server, 'defaultName'); 45 | 46 | server.get('/', function (req, res) { 47 | var segment = AWSXRay.getSegment(); 48 | 49 | //... 50 | 51 | res.send('hello'); 52 | }); 53 | 54 | //Error capturing is attached to the server's uncaughtException and after events (for both handled and unhandled errors) 55 | ``` 56 | 57 | ## Manual mode examples 58 | 59 | For more manual mode examples, see [manual mode examples](https://github.com/aws/aws-xray-sdk-node/tree/master/packages/core#Manual-Mode-Examples). The X-Ray SDK can be used identically inside Restify routes. Note that you don't have to manually start or close the segments since that is handled by the X-Ray middleware. 60 | 61 | ```js 62 | var AWSXRayRestify = require('aws-xray-sdk-restify'); 63 | var restify = require('restify'); 64 | var server = restify.createServer(); 65 | 66 | //... 67 | 68 | AWSXRayRestify.enable(server, 'defaultName'); //Required at the start of your routes 69 | 70 | server.get('/', function (req, res) { 71 | var segment = req.segment; 72 | 73 | //... 74 | 75 | res.send('hello'); 76 | }); 77 | 78 | //Error capturing is attached to the server's uncaughtException and after events (for both handled and unhandled errors) 79 | ``` 80 | -------------------------------------------------------------------------------- /packages/restify/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as AWSXRay from 'aws-xray-sdk-core'; 2 | 3 | declare module 'restify' { 4 | interface Request { 5 | segment?: AWSXRay.Segment; 6 | } 7 | } 8 | 9 | export * from './restify_mw'; 10 | -------------------------------------------------------------------------------- /packages/restify/lib/index.js: -------------------------------------------------------------------------------- 1 | // Convenience file to require the SDK from the root of the repository 2 | module.exports = require('./restify_mw'); 3 | -------------------------------------------------------------------------------- /packages/restify/lib/restify_mw.d.ts: -------------------------------------------------------------------------------- 1 | import * as restify from 'restify'; 2 | 3 | export function enable(server: restify.Server, defaultName: string): void; 4 | -------------------------------------------------------------------------------- /packages/restify/lib/restify_mw.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Enable X-Ray for Restify module. 3 | * 4 | * Exposes the 'enable' function to enable automated data capturing for a Restify web service. To enable on a Restify server, 5 | * use 'AWSXRayRestify.enable(, )' before defining your routes. 6 | * Use AWSXRay.getSegment() to access the current sub/segment. 7 | * If in manual mode, this appends the Segment object to the request object as req.segment, or in the case of using 8 | * the Restify context plugin, it will be set as 'XRaySegment'. 9 | * @module restify_mw 10 | */ 11 | 12 | var AWSXRay = require('aws-xray-sdk-core'); 13 | 14 | var mwUtils = AWSXRay.middleware; 15 | 16 | var restifyMW = { 17 | 18 | /** 19 | * Use 'AWSXRayRestify.enable(server, 'defaultName'))' before defining your routes. 20 | * Use AWSXRay.getSegment() to access the current sub/segment. 21 | * If in manual mode, this appends the Segment object to the request object as req.segment, or in the case of using 22 | * the Restify context plugin, it will be set as 'XRaySegment'. 23 | * @param {Server} server - The Restify server instance. 24 | * @param {string} defaultName - The default name for the segment. 25 | * @alias module:restify_mw.enable 26 | */ 27 | 28 | enable: function enable(server, defaultName) { 29 | if (!server) { 30 | throw new Error('Restify server instance to enable was not supplied. Please provide a server.'); 31 | } 32 | 33 | if (!defaultName || typeof defaultName !== 'string') { 34 | throw new Error('Default segment name was not supplied. Please provide a string.'); 35 | } 36 | 37 | mwUtils.setDefaultName(defaultName); 38 | AWSXRay.getLogger().debug('Enabling AWS X-Ray for Restify.'); 39 | 40 | var segment; 41 | 42 | server.use(function open(req, res, next) { 43 | segment = mwUtils.traceRequestResponseCycle(req, res); 44 | 45 | if (AWSXRay.isAutomaticMode()) { 46 | var ns = AWSXRay.getNamespace(); 47 | ns.bindEmitter(req); 48 | ns.bindEmitter(res); 49 | 50 | ns.run(function() { 51 | AWSXRay.setSegment(segment); 52 | 53 | if (next) { 54 | next(); 55 | } 56 | }); 57 | } else { 58 | if (req.set) { 59 | req.set('XRaySegment', segment); 60 | } else { 61 | req.segment = segment; 62 | } 63 | 64 | if (next) { 65 | next(); 66 | } 67 | } 68 | }); 69 | 70 | server.on('uncaughtException', function uncaughtError(req, res, route, err) { 71 | if (segment && err) { 72 | segment.close(err); 73 | } 74 | }); 75 | 76 | server.on('after', function handledError(req, res, route, err) { 77 | if (segment && err) { 78 | segment.addError(err); 79 | } 80 | }); 81 | } 82 | }; 83 | 84 | module.exports = restifyMW; 85 | -------------------------------------------------------------------------------- /packages/restify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-xray-sdk-restify", 3 | "version": "3.10.3", 4 | "description": "Enables AWS X-Ray for Restify (Javascript)", 5 | "author": "Amazon Web Services", 6 | "contributors": [ 7 | "Sandra McMullen " 8 | ], 9 | "main": "lib/index.js", 10 | "types": "lib/index.d.ts", 11 | "engines": { 12 | "node": ">= 14.x" 13 | }, 14 | "directories": { 15 | "test": "test" 16 | }, 17 | "dependencies": { 18 | "@types/restify": "*" 19 | }, 20 | "peerDependencies": { 21 | "aws-xray-sdk-core": "^3.10.3" 22 | }, 23 | "scripts": { 24 | "test": "mocha --recursive ./test/ -R spec && tsd", 25 | "test-d": "tsd", 26 | "lint": "eslint .", 27 | "lint:fix": "eslint . --fix" 28 | }, 29 | "keywords": [ 30 | "amazon", 31 | "api", 32 | "aws", 33 | "restify", 34 | "xray", 35 | "x-ray", 36 | "x ray" 37 | ], 38 | "license": "Apache-2.0", 39 | "repository": "https://github.com/aws/aws-xray-sdk-node/tree/master/packages/restify" 40 | } 41 | -------------------------------------------------------------------------------- /packages/restify/test-d/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import * as AWSXRay from 'aws-xray-sdk-core'; 2 | import * as restify from 'restify'; 3 | import { expectType } from 'tsd'; 4 | import * as AWSXRayRestify from '../lib'; 5 | 6 | const server = restify.createServer(); 7 | 8 | AWSXRayRestify.enable(server, 'defaultName'); 9 | 10 | server.get('/', function(req, res) { 11 | expectType(req.segment); 12 | res.send('hello'); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/restify/test/test_utils.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events'); 2 | var util = require('util'); 3 | 4 | var TestUtils = {}; 5 | 6 | TestUtils.TestEmitter = function TestEmitter() { 7 | EventEmitter.call(this); 8 | }; 9 | 10 | util.inherits(TestUtils.TestEmitter, EventEmitter); 11 | 12 | TestUtils.onEvent = function onEvent(event, fcn) { 13 | this.emitter.on(event, fcn.bind(this)); 14 | return this; 15 | }; 16 | 17 | module.exports = TestUtils; 18 | -------------------------------------------------------------------------------- /packages/restify/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/test_express/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "mocha": true, 5 | "es6": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "rules": { 9 | "indent": [ 10 | "error", 11 | 2 12 | ], 13 | "linebreak-style": [ 14 | "error", 15 | "unix" 16 | ], 17 | "quotes": [ 18 | "error", 19 | "single" 20 | ], 21 | "semi": [ 22 | "error", 23 | "always" 24 | ], 25 | "eol-last": [ 26 | "error", 27 | "always" 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/test_express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-aws-xray-sdk-express", 3 | "private": true, 4 | "version": "3.10.3", 5 | "description": "AWS X-Ray Middleware for Express (Javascript)", 6 | "author": "Amazon Web Services", 7 | "contributors": [ 8 | "Sandra McMullen " 9 | ], 10 | "main": "lib/index.js", 11 | "engines": { 12 | "node": ">= 14.x" 13 | }, 14 | "directories": { 15 | "test": "test" 16 | }, 17 | "scripts": { 18 | "test": "mocha --recursive --exit ./test/ -R spec" 19 | }, 20 | "keywords": [ 21 | "amazon", 22 | "api", 23 | "aws", 24 | "express", 25 | "xray", 26 | "x-ray", 27 | "x ray" 28 | ], 29 | "license": "Apache-2.0", 30 | "repository": "https://github.com/aws/aws-xray-sdk-node/tree/master/packages/express" 31 | } 32 | -------------------------------------------------------------------------------- /packages/test_express/test/shaky_stream.js: -------------------------------------------------------------------------------- 1 | var stream = require('stream'); 2 | var util = require('util'); 3 | 4 | var Readable = stream.Readable; 5 | 6 | var timeoutFn = typeof setTimeoutOrig === 'function' ? setTimeoutOrig : setTimeout; 7 | 8 | /** 9 | * ShakyStream will send data in 2 parts, pausing between parts. 10 | * @param {object} options 11 | * @param {number} options.pauseFor Length of time in ms to pause stream when reading. 12 | */ 13 | function ShakyStream(options) { 14 | if (!(this instanceof ShakyStream)) { 15 | return new ShakyStream(options); 16 | } 17 | if (!options.highWaterMark) { 18 | options.highWaterMark = 1024 * 16; 19 | } 20 | this._shakyTime = options.pauseFor; 21 | this._didStart = false; 22 | this._isPaused = false; 23 | 24 | Readable.call(this, options); 25 | } 26 | 27 | util.inherits(ShakyStream, Readable); 28 | 29 | ShakyStream.prototype._read = function _read(_) { 30 | if (!this._didStart) { 31 | this._didStart = true; 32 | this.push(Buffer.from('{"Count":1,"Items":[{"id":{"S":"2016-12-11"},"dateUTC":{"N":"1481494545591"},')); 33 | } 34 | if (this._didStart && this._isPaused) { 35 | return; 36 | } else if (this._didStart) { 37 | this._isPaused = true; 38 | var self = this; 39 | timeoutFn(function() { 40 | self.push(Buffer.from('"javascript":{"M":{"foo":{"S":"bar"},"baz":{"S":"buz"}}}}],"ScannedCount":1}')); 41 | self.push(null); 42 | }, this._shakyTime); 43 | } 44 | }; 45 | 46 | module.exports = {ShakyStream: ShakyStream}; 47 | -------------------------------------------------------------------------------- /scripts/cp-with-structure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## This script copies a file provided as the first parameter into the destination directory 4 | ## provided by the second parameter, while maintaining the directory structure of the source 5 | ## file. It is similar to 'cp --parents' on Linux, but usable cross-platform 6 | src=(${*: 1}) 7 | dest=${*: -1:1} 8 | for filename in $src; do 9 | [ -e "$filename" ] || continue 10 | dirPath=$(dirname "${filename}") 11 | mkdir -p $dest/$dirPath 12 | cp -a $filename $dest/$dirPath 13 | done 14 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/.eslintignore: -------------------------------------------------------------------------------- 1 | index.* 2 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json" 3 | } 4 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/NOTICE.txt: -------------------------------------------------------------------------------- 1 | AWS X-Ray SDK Fastify for JavaScript 2 | Copyright 2012-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | This product includes software developed at 5 | Amazon Web Services, Inc. (http://aws.amazon.com/). 6 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/README.md: -------------------------------------------------------------------------------- 1 | # fastify-xray 2 | 3 | A Fastify plugin to log requests and subsegments through AWSXray. 4 | 5 | ## Setup 6 | 7 | The plugin relies on the AWS credentials being set before being registered, or it will pull them from 8 | `~/.aws/credentials` as per the SDK default. 9 | 10 | For more details on using X-Ray, see the [docs](https://docs.aws.amazon.com/xray-sdk-for-nodejs/latest/reference) 11 | 12 | ## Usage 13 | 14 | Simply register as a normal Fastify plugin 15 | 16 | ```js 17 | const AWSXRay = require('aws-xray-sdk'); 18 | 19 | await server.register(require('aws-xray-sdk-fastify'), { 20 | segmentName: 'test segment', 21 | captureAWS: true, 22 | plugins: [AWSXRay.plugins.ECSPlugin], 23 | }); 24 | ``` 25 | 26 | In automatic mode, you can access the X-Ray segment at any time via the AWSXRay SDK: 27 | 28 | ```js 29 | const AWSXRay = require('aws-xray-sdk-core'); 30 | 31 | const segment = AWSXRay.getSegment(); 32 | segment.addAnnotation('hitController', 'true'); 33 | ``` 34 | 35 | In manual mode, you can access the current X-Ray segment from the request object: 36 | 37 | ```js 38 | server.route({ 39 | method: 'GET', 40 | path: '/items', 41 | handler: async (request, reply) => { 42 | const segment = request.segment; 43 | segment.addAnnotation('hitController', 'true'); 44 | 45 | return {}; 46 | }, 47 | }); 48 | ``` 49 | 50 | ### Options 51 | 52 | - `segmentName` Segment name to use in place of default segment name generator (**required**) 53 | - `automaticMode` Specifies that X-Ray automatic mode is in use (default true) 54 | - `plugins` An array of AWS plugins to use (i.e. `[AWSXRay.plugins.EC2Plugin]`) 55 | - `captureAWS` Enables AWS X-Ray to capture AWS calls. This requires having `aws-sdk` installed as a peer dependency 56 | - `captureHTTP` Enables AWS X-Ray to capture all http calls 57 | - `capturePromises` Enables AWS X-Ray to capture all promises 58 | - `logger` Bind AWS X-Ray to compatible logging interface `({ trace, debug, info })` 59 | 60 | ## Sample App 61 | 62 | A naive Fastify server with X-Ray enabled is available in the "sample" directory. 63 | The sample can be started from the sdk_contrib/fastify directory with: `npm run sample` 64 | 65 | Once running, a "hello world" GET endpoint will be available at `http://localhost:3010/` 66 | 67 | The sample will run but throw errors connecting to X-Ray if a local X-Ray daemon is not running. 68 | 69 | For more information on running the XRay daemon locally: 70 | https://docs.aws.amazon.com/xray/latest/devguide/xray-daemon-local.html 71 | 72 | ## Thanks 73 | 74 | Based on the hard work @[AWS X-Ray Express Middleware](https://github.com/aws/aws-xray-sdk-node/tree/master/packages/express) and heavily inspired by [X-Ray Hapi](https://github.com/aws/aws-xray-sdk-node/tree/master/sdk_contrib/hapi) 75 | 76 | ## Contributors 77 | 78 | - [Jorge Vargas](https://github.com/jorgevrgs) 79 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/lib/hooks/on-error.hook.js: -------------------------------------------------------------------------------- 1 | const AWSXRay = require('aws-xray-sdk-core'); 2 | const { middleware: mwUtils } = AWSXRay; 3 | 4 | /** @type {import('fastify').onErrorHookHandler} */ 5 | module.exports = function onErrorHook(request, reply, error, done) { 6 | const { segment } = request; 7 | 8 | if (segment) { 9 | segment.addError(error); 10 | mwUtils.middlewareLog( 11 | 'Fastify XRay segment encountered an error', 12 | request.url, 13 | segment 14 | ); 15 | } 16 | 17 | done(); 18 | }; 19 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/lib/hooks/on-request.hook.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const AWSXRay = require('aws-xray-sdk-core'); 3 | const { middleware: mwUtils } = AWSXRay; 4 | 5 | /** @type {import('fastify').onRequestHookHandler} */ 6 | module.exports = function onRequestHook(request, reply, done) { 7 | const req = request.raw; 8 | const res = reply.raw; 9 | 10 | const segment = mwUtils.traceRequestResponseCycle(req, res); 11 | 12 | if (AWSXRay.isAutomaticMode()) { 13 | const ns = AWSXRay.getNamespace(); 14 | ns.bindEmitter(req); 15 | ns.bindEmitter(res); 16 | 17 | ns.run(() => { 18 | AWSXRay.setSegment(segment); 19 | done(); 20 | }); 21 | } else { 22 | request.log.info('Manual mode, skipping segment'); 23 | 24 | if (!request.segment) { 25 | request.segment = segment; 26 | } else { 27 | request.log.warn('Request already has a segment, skipping'); 28 | } 29 | 30 | done(); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/lib/hooks/on-response.hook.js: -------------------------------------------------------------------------------- 1 | const AWSXRay = require('aws-xray-sdk-core'); 2 | const { middleware: mwUtils } = AWSXRay; 3 | 4 | /** @type {import('fastify').onResponseHookHandler} */ 5 | module.exports = function onResponseHook(request, reply, done) { 6 | try { 7 | const { segment } = request; 8 | 9 | if (!segment || segment.isClosed()) { 10 | return done(); 11 | } 12 | 13 | if (reply.statusCode >= 400) { 14 | if (reply.statusCode === 429) { 15 | segment.addThrottleFlag(); 16 | } 17 | 18 | const cause = AWSXRay.utils.getCauseTypeFromHttpStatus(reply.statusCode); 19 | 20 | if (cause) { 21 | segment[cause] = true; 22 | } 23 | } 24 | 25 | if (segment.http) { 26 | segment.http.close(reply.raw); 27 | } 28 | 29 | segment.close(); 30 | 31 | mwUtils.middlewareLog( 32 | 'Closed Fastify XRay segment successfully', 33 | request.url, 34 | segment 35 | ); 36 | 37 | done(); 38 | } catch (error) { 39 | done(error); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './plugin'; 2 | export { default } from './plugin'; 3 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/lib/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./plugin'); 2 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/lib/plugin.d.ts: -------------------------------------------------------------------------------- 1 | import * as AWSXRay from 'aws-xray-sdk-core'; 2 | import { FastifyLoggerInstance } from 'fastify'; 3 | 4 | declare module 'fastify' { 5 | interface FastifyRequest { 6 | segment?: AWSXRay.Segment; 7 | } 8 | } 9 | 10 | export interface XRayFastifyPluginOptions { 11 | segmentName?: string; 12 | captureAWS: boolean; 13 | captureHTTP: boolean; 14 | capturePromises: boolean; 15 | logger: FastifyLoggerInstance; 16 | automaticMode: boolean; 17 | plugins: AWSXRay.plugins.Plugin[]; 18 | } 19 | 20 | declare const xRayFastifyPlugin: FastifyPluginAsync; 21 | 22 | export default xRayFastifyPlugin; 23 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/lib/plugin.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const fp = require('fastify-plugin').default; 3 | const configureAWSXRaySync = require('./private/configure-aws-x-ray-sync'); 4 | const onRequestHook = require('./hooks/on-request.hook'); 5 | const onResponseHook = require('./hooks/on-response.hook'); 6 | const onErrorHook = require('./hooks/on-error.hook'); 7 | 8 | /** @type {import('fastify').FastifyPluginAsync} */ 9 | const xRayFastifyPlugin = fp(async (fastify, opts) => { 10 | configureAWSXRaySync(fastify, opts); 11 | 12 | fastify.decorateRequest('segment', null); 13 | fastify 14 | .addHook('onRequest', onRequestHook) 15 | .addHook('onResponse', onResponseHook) 16 | .addHook('onError', onErrorHook); 17 | }); 18 | 19 | module.exports = xRayFastifyPlugin; 20 | 21 | exports.xRayFastifyPlugin = xRayFastifyPlugin; 22 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/lib/private/configure-aws-x-ray-sync.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const AWSXRay = require('aws-xray-sdk-core'); 3 | const { middleware: mwUtils } = AWSXRay; 4 | 5 | /** 6 | * 7 | * @param {import('fastify').FastifyInstance} fastify 8 | * @param {*} opts 9 | */ 10 | module.exports = function configureAWSXRaySync(fastify, opts) { 11 | const defaultOptions = { 12 | automaticMode: true, 13 | logger: fastify.log, 14 | }; 15 | 16 | const localOptions = { ...defaultOptions, ...opts }; 17 | 18 | if (localOptions.logger) { 19 | AWSXRay.setLogger(localOptions.logger); 20 | } else { 21 | AWSXRay.setLogger(fastify.log); 22 | } 23 | 24 | const segmentName = localOptions.segmentName; 25 | 26 | if (!segmentName) { 27 | throw new Error('segmentName is required'); 28 | } 29 | 30 | mwUtils.setDefaultName(segmentName); 31 | 32 | if (localOptions.automaticMode) { 33 | AWSXRay.enableAutomaticMode(); 34 | } else { 35 | AWSXRay.enableManualMode(); 36 | } 37 | 38 | if (localOptions.plugins) { 39 | AWSXRay.config(localOptions.plugins); 40 | } 41 | 42 | if (localOptions.captureAWS) { 43 | AWSXRay.captureAWS(require('aws-sdk')); 44 | } 45 | 46 | if (localOptions.captureHTTP) { 47 | AWSXRay.captureHTTPsGlobal(require('http'), true); 48 | AWSXRay.captureHTTPsGlobal(require('https'), true); 49 | } 50 | 51 | if (localOptions.capturePromises) { 52 | AWSXRay.capturePromise(); 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-xray-sdk-fastify", 3 | "version": "3.10.3", 4 | "description": "AWS X-Ray plugin for Fastify", 5 | "author": "Amazon Web Services", 6 | "contributors": [ 7 | "Jorge Vargas " 8 | ], 9 | "main": "lib/index.js", 10 | "files": [ 11 | "lib" 12 | ], 13 | "types": "lib/index.d.ts", 14 | "scripts": { 15 | "lint": "eslint .", 16 | "lint:fix": "eslint --fix .", 17 | "test": "mocha --recursive --exit ./test/ -R spec && tsd", 18 | "test-d": "tsd", 19 | "sample": "node sample" 20 | }, 21 | "engines": { 22 | "node": ">= 14.x" 23 | }, 24 | "peerDependencies": { 25 | "aws-xray-sdk-core": "^3.10.3", 26 | "fastify": "^4.0.1", 27 | "fastify-plugin": "^4.2.0" 28 | }, 29 | "keywords": [ 30 | "amazon", 31 | "api", 32 | "aws", 33 | "fastify", 34 | "xray", 35 | "x-ray", 36 | "x ray" 37 | ], 38 | "directories": { 39 | "lib": "lib" 40 | }, 41 | "license": "Apache-2.0", 42 | "repository": "https://github.com/aws/aws-xray-sdk-node/tree/master/sdk_contrib/fastify" 43 | } 44 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/sample/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Fastify = require('fastify'); 4 | const xrayPlugin = require('./xray-plugin'); 5 | 6 | const server = Fastify(); 7 | 8 | server.register(xrayPlugin.plugin, xrayPlugin.options); 9 | server.route({ 10 | method: 'GET', 11 | path: '/', 12 | handler: async (_request, _reply) => { 13 | return 'Hello World!'; 14 | }, 15 | }); 16 | 17 | server 18 | .listen(3000) 19 | .then(() => { 20 | console.log(`server listening on port 3000`); 21 | }) 22 | .catch(console.error); 23 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/sample/xray-plugin.js: -------------------------------------------------------------------------------- 1 | const awsXray = require('aws-xray-sdk-core'); 2 | 3 | module.exports = { 4 | plugin: require('../lib'), 5 | options: { 6 | segmentName: 'fastify-xray-sample', 7 | captureAWS: true, 8 | captureHTTP: true, 9 | capturePromises: true, 10 | plugins: [awsXray.plugins.ECSPlugin], 11 | logger: console, 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/test-d/index.test-d.ts.js: -------------------------------------------------------------------------------- 1 | import * as AWSXRay from 'aws-xray-sdk-core'; 2 | import * as Fastify from 'fastify'; 3 | import { expectType } from 'tsd'; 4 | 5 | const server = Fastify(); 6 | server.register(require('../lib')); 7 | 8 | server.route({ 9 | method: 'GET', 10 | path: '/', 11 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 12 | handler: async (request, _reply) => { 13 | (expectType < AWSXRay.Segment) | (undefined > request.segment); 14 | return { data: 'ok' }; 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /sdk_contrib/fastify/test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "extends": [ 6 | "plugin:mocha/recommended" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /sdk_contrib/fetch/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /sdk_contrib/fetch/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json" 3 | } 4 | -------------------------------------------------------------------------------- /sdk_contrib/fetch/NOTICE.txt: -------------------------------------------------------------------------------- 1 | AWS X-Ray SDK Fetch for JavaScript 2 | Copyright 2012-2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | This product includes software developed at 5 | Amazon Web Services, Inc. (http://aws.amazon.com/). 6 | -------------------------------------------------------------------------------- /sdk_contrib/fetch/README.md: -------------------------------------------------------------------------------- 1 | # fetch-xray 2 | 3 | A patcher for AWSXRay to support fetch, implemented via either the [node-fetch module](https://www.npmjs.com/package/node-fetch) or the built-in 4 | global fetch support starting with [NodeJS 18](https://nodejs.org/en/blog/announcements/v18-release-announce). 5 | 6 | ## Usage 7 | 8 | ```js 9 | const { captureFetchGlobal } = require('aws-xray-sdk-fetch'); 10 | 11 | // To use globally defined fetch (available in NodeJS 18+) 12 | const fetch = captureFetchGlobal(); 13 | const result = await fetch('https://foo.com'); 14 | 15 | // To use node-fetch module 16 | const { captureFetchModule } = require('aws-xray-sdk-fetch'); 17 | const fetchModule = require('node-fetch'); 18 | const fetch = captureFetchModule(fetchModule); // Note, first parameter *must* be the node-fetch module 19 | const result = await fetch('https://foo.com'); 20 | ``` 21 | 22 | There are two optional parameters you can pass when calling `captureFetchGlobal` / `captureFetchModule`: 23 | 24 | * **downstreamXRayEnabled**: when True, adds a "traced:true" property to the subsegment so the AWS X-Ray service expects a corresponding segment from the downstream service (default = False) 25 | * **subsegmentCallback**: a callback that is called with the subsegment, the fetch request, the fetch response and any error issued, allowing custom annotations and metadata to be added 26 | 27 | TypeScript bindings for the capture functions are included. 28 | 29 | ## Testing 30 | 31 | Unit and integration tests can be run using `npm run test`. Typings file tess can be run using `npm run test-d`. 32 | 33 | ## Errata 34 | 35 | 1. This package CommonJS to conform with the rest of the AWSXRay codebase. As such, it is incompatible with node-fetch 3, which is ESM only. As such, it is written 36 | to be compatible with [node-fetch version 2](https://www.npmjs.com/package/node-fetch#CommonJS), which should still receive critical fixes. If you are using global 37 | fetch (available in NodeJS 18+) then this isn't an issue for you. 38 | 39 | 2. This package is designed working under the assumption that the NodeJS implementation of fetch is compatible with node-fetch, albeit with its own separate, 40 | built-in typings. If NodeJS takes fetch in a different direction (breaks compatibility) then that would most likely break this package. There is no indication that 41 | I could find that this will happen, but throwing it out there "just in case". 42 | 43 | ## Contributors 44 | 45 | - [Jason Terando](https://github.com/jasonterando) 46 | - [Bernd Fuhrmann](https://github.com/berndfuhrmann) 47 | -------------------------------------------------------------------------------- /sdk_contrib/fetch/lib/fetch_p.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import AWSXRay from 'aws-xray-sdk-core'; 3 | import * as fetchModule from 'node-fetch'; 4 | 5 | type FetchModuleType = typeof fetchModule; 6 | 7 | type fetchModuleFetch = (url: URL | fetchModule.RequestInfo, init?: fetchModule.RequestInit | undefined) => Promise; 8 | 9 | export function captureFetchGlobal( 10 | downstreamXRayEnabled?: boolean, 11 | subsegmentCallback?: (subsegment: AWSXRay.Subsegment, req: Request, res: Response | null, error?: Error | undefined) => void): 12 | typeof globalThis.fetch; 13 | 14 | export function captureFetchModule( 15 | fetch: FetchModuleType, 16 | downstreamXRayEnabled?: boolean, 17 | subsegmentCallback?: (subsegment: AWSXRay.Subsegment, req: fetchModule.Request, res: fetchModule.Response | null, error?: Error | undefined) => void): 18 | (url: URL | fetchModule.RequestInfo, init?: fetchModule.RequestInit | undefined) => Promise; 19 | 20 | export function enableCapture( 21 | fetch: Fetch, 22 | request: Request, 23 | downstreamXRayEnabled?: boolean, 24 | subsegmentCallback?: (subsegment: AWSXRay.Subsegment, req: Request, res: Response | null, error?: Error | undefined) => void 25 | ): Fetch; 26 | -------------------------------------------------------------------------------- /sdk_contrib/fetch/lib/subsegment_fetch.d.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'node-fetch'; 2 | 3 | declare module 'aws-xray-sdk-core' { 4 | interface Subsegment { 5 | /** 6 | * Extends Subsegment to append remote request data to subsegment, similar to what 7 | * Subsegment.prototype.addRemoteRequestData does in core/lib/segments/attributes/subsegment.js 8 | * @param {Fetch Request} request 9 | * @param {Fetch Request or null|undefined} response 10 | * @param {boolean} downstreamXRayEnabled 11 | */ 12 | addFetchRequestData( 13 | request: Request, 14 | response: Response, 15 | downstreamXRayEnabled: boolean): void; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sdk_contrib/fetch/lib/subsegment_fetch.js: -------------------------------------------------------------------------------- 1 | const {Subsegment} = require('aws-xray-sdk-core'); 2 | 3 | /** 4 | * Extends Subsegment to append remote request data to subsegment, similar to what 5 | * Subsegment.prototype.addRemoteRequestData does in core/lib/segments/attributes/subsegment.js 6 | * @param {Subsegment} subsegment 7 | * @param {Fetch Request} request 8 | * @param {Fetch Request or null|undefined} response 9 | * @param {boolean} downstreamXRayEnabled 10 | */ 11 | Subsegment.prototype.addFetchRequestData = function addFetchRequestData(request, response, downstreamXRayEnabled) { 12 | this.http = { 13 | request: { 14 | url: request.url?.toString() ?? '', 15 | method: request.method ?? '' 16 | } 17 | }; 18 | 19 | if (downstreamXRayEnabled) { 20 | this.traced = true; 21 | } 22 | 23 | if (response) { 24 | this.http.response = { 25 | status: response.status 26 | }; 27 | if (response.headers) { 28 | const clength = response.headers.get('content-length'); 29 | if (clength) { 30 | const v = parseInt(clength); 31 | if (! Number.isNaN(v)) { 32 | this.http.response.content_length = v; 33 | } 34 | } 35 | } 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /sdk_contrib/fetch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-xray-sdk-fetch", 3 | "version": "3.10.3", 4 | "description": "AWS X-Ray plugin for node-fetch", 5 | "author": "Amazon Web Services", 6 | "contributors": [ 7 | "Jason Terando " 8 | ], 9 | "main": "lib/fetch_p.js", 10 | "files": [ 11 | "lib" 12 | ], 13 | "types": "lib/fetch_p.d.ts", 14 | "scripts": { 15 | "lint": "eslint .", 16 | "lint:fix": "eslint --fix .", 17 | "test": "mocha --recursive --exit ./test/ -R spec && tsd", 18 | "test-d": "tsd" 19 | }, 20 | "engines": { 21 | "node": ">= 14.x" 22 | }, 23 | "peerDependencies": { 24 | "aws-xray-sdk-core": "^3.10.3" 25 | }, 26 | "keywords": [ 27 | "amazon", 28 | "api", 29 | "aws", 30 | "fetch", 31 | "node-fetch", 32 | "xray", 33 | "x-ray", 34 | "x ray" 35 | ], 36 | "directories": { 37 | "lib": "lib" 38 | }, 39 | "license": "Apache-2.0", 40 | "repository": "https://github.com/aws/aws-xray-sdk-node/tree/master/sdk_contrib/fetch", 41 | "devDependencies": { 42 | "@types/node-fetch": "^2.6.4", 43 | "chai-as-promised": "=7.1.1", 44 | "node-fetch": "^2.6.11", 45 | "ts-expect": "^1.3.0" 46 | }, 47 | "dependencies": { 48 | "tsd": "^0.28.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /sdk_contrib/fetch/test-d/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import AWSXRay from 'aws-xray-sdk-core'; 2 | import * as fetchModule from 'node-fetch'; 3 | import { expectType } from 'ts-expect'; 4 | import { captureFetchGlobal, captureFetchModule } from '../lib/fetch_p'; 5 | 6 | type ModuleFetch = (url: URL | fetchModule.RequestInfo, init?: fetchModule.RequestInit | undefined) => Promise; 7 | 8 | if (globalThis.fetch !== undefined) { 9 | function fetchGlobalCallback(subsegment: AWSXRay.Subsegment, req: Request, res: Response | null, error?: Error | undefined) { 10 | console.log({ subsegment, req, res, error }); 11 | } 12 | 13 | expectType(captureFetchGlobal()); 14 | expectType(captureFetchGlobal(true)); 15 | expectType(captureFetchGlobal(false)); 16 | expectType(captureFetchGlobal(true, fetchGlobalCallback)); 17 | expectType(captureFetchGlobal(false, fetchGlobalCallback)); 18 | } 19 | 20 | function fetchModuleCallback(subsegment: AWSXRay.Subsegment, req: fetchModule.Request, res: fetchModule.Response | null, error?: Error | undefined) { 21 | console.log({ subsegment, req, res, error }); 22 | } 23 | 24 | expectType(captureFetchModule(fetchModule)); 25 | expectType(captureFetchModule(fetchModule, true)); 26 | expectType(captureFetchModule(fetchModule, false)); 27 | expectType(captureFetchModule(fetchModule, true, fetchModuleCallback)); 28 | expectType(captureFetchModule(fetchModule, false, fetchModuleCallback)); 29 | -------------------------------------------------------------------------------- /sdk_contrib/fetch/test/global.js: -------------------------------------------------------------------------------- 1 | const AWSXray = require('aws-xray-sdk-core'); 2 | const sinon = require('sinon'); 3 | 4 | const sandbox = sinon.createSandbox(); 5 | const spyLogWarn = sandbox.spy(); 6 | const spyLogInfo = sandbox.spy(); 7 | 8 | sandbox.stub(AWSXray, 'getLogger').returns({ 9 | warn: spyLogWarn, 10 | info: spyLogInfo 11 | }); 12 | 13 | const stubResolveSegment = sandbox.stub(AWSXray, 'resolveSegment'); 14 | const stubResolveManualSegmentParams = sandbox.stub(AWSXray, 'resolveManualSegmentParams'); 15 | const stubIsAutomaticMode = sandbox.stub(AWSXray, 'isAutomaticMode'); 16 | 17 | afterEach(() => { 18 | sandbox.resetHistory(); 19 | // We have to create and re-create this stub here because promise_p.test doesn't use sandboxes 20 | // stubIsAutomaticMode.returns(true); 21 | }); 22 | 23 | module.exports.AWSXray = AWSXray; 24 | module.exports.sandbox = sandbox; 25 | module.exports.spyLogWarn = spyLogWarn; 26 | module.exports.spyLogInfo = spyLogInfo; 27 | module.exports.stubResolveSegment = stubResolveSegment; 28 | module.exports.stubResolveManualSegmentParams = stubResolveManualSegmentParams; 29 | module.exports.stubIsAutomaticMode = stubIsAutomaticMode; 30 | -------------------------------------------------------------------------------- /sdk_contrib/fetch/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | "strict": true, 6 | "strictNullChecks": true, 7 | "declaration": false, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "allowJs": true, 11 | "checkJs": false, 12 | "outDir": "dist", 13 | }, 14 | "include": ["lib", "test", "test_async"], 15 | "exclude": ["dist", "node_modules"] 16 | } 17 | -------------------------------------------------------------------------------- /sdk_contrib/hapi/.eslintignore: -------------------------------------------------------------------------------- 1 | index.* 2 | -------------------------------------------------------------------------------- /sdk_contrib/hapi/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json" 3 | } 4 | -------------------------------------------------------------------------------- /sdk_contrib/hapi/NOTICE.txt: -------------------------------------------------------------------------------- 1 | AWS X-Ray SDK Hapi for JavaScript 2 | Copyright 2012-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | This product includes software developed at 5 | Amazon Web Services, Inc. (http://aws.amazon.com/). 6 | -------------------------------------------------------------------------------- /sdk_contrib/hapi/README.md: -------------------------------------------------------------------------------- 1 | # hapi-xray 2 | A HapiJS plugin to log requests and subsegments through AWSXray. 3 | 4 | ## Setup 5 | 6 | The plugin relies on the AWS credentials being set before being registered, or it will pull them from 7 | `~/.aws/credentials` as per the SDK default. 8 | 9 | For more details on using X-Ray, see the [docs](https://docs.aws.amazon.com/xray-sdk-for-nodejs/latest/reference) 10 | 11 | ## Usage 12 | 13 | Simply register as a normal Hapi plugin 14 | 15 | ```js 16 | 17 | const AWSXRay = require('aws-xray-sdk'); 18 | 19 | await server.register({ 20 | plugin: require('aws-xray-sdk-hapi'), 21 | options: { 22 | captureAWS: true, 23 | plugins: [AWSXRay.plugins.ECSPlugin] 24 | } 25 | }); 26 | ``` 27 | 28 | In automatic mode, you can access the X-Ray segment at any time via the AWSXRay SDK: 29 | ```js 30 | const AWSXRay = require('aws-xray-sdk-core'); 31 | 32 | const segment = AWSXRay.getSegment(); 33 | segment.addAnnotation('hitController', 'true'); 34 | ``` 35 | 36 | In manual mode, you can access the current X-Ray segment from the request object: 37 | 38 | ```js 39 | server.route({ 40 | method: 'GET', 41 | path: '/items', 42 | handler: async (request, h) => { 43 | const segment = request.segment; 44 | segment.addAnnotation('hitController', 'true'); 45 | 46 | return {}; 47 | } 48 | }); 49 | ``` 50 | 51 | ### Options 52 | - `segmentName` Segment name to use in place of default segment name generator 53 | - `automaticMode` Specifies that X-Ray automatic mode is in use (default true) 54 | - `plugins` An array of AWS plugins to use (i.e. `[AWSXRay.plugins.EC2Plugin]`) 55 | - `captureAWS` Enables AWS X-Ray to capture AWS calls. This requires having `aws-sdk` installed as a peer dependency 56 | - `captureHTTP` Enables AWS X-Ray to capture all http calls 57 | - `capturePromises` Enables AWS X-Ray to capture all promises 58 | - `logger` Bind AWS X-Ray to compatible logging interface `({ trace, debug, info })` 59 | 60 | ## Sample App 61 | A naive hapi server with X-Ray enabled is available in the "sample" directory. 62 | The sample can be started from the sdk_contrib/hapi directory with: `npm run sample` 63 | 64 | Once running, a "hello world" GET endpoint will be available at `http://localhost:3010/` 65 | 66 | The sample will run but throw errors connecting to X-Ray if a local X-Ray daemon is not running. 67 | 68 | For more information on running the XRay daemon locally: 69 | https://docs.aws.amazon.com/xray/latest/devguide/xray-daemon-local.html 70 | 71 | ## Thanks 72 | 73 | Based on the hard work @[AWS X-Ray Express Middleware](https://github.com/aws/aws-xray-sdk-node/tree/master/packages/express) 74 | 75 | ## Contributors 76 | 77 | - [Alex Coulcher](https://github.com/moonthug) 78 | - [Eric Swann](https://github.com/eric-swann-q2) 79 | -------------------------------------------------------------------------------- /sdk_contrib/hapi/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as AWSXRay from 'aws-xray-sdk-core'; 2 | 3 | declare global { 4 | namespace Hapi { 5 | interface Request { 6 | segment?: AWSXRay.Segment; 7 | } 8 | } 9 | } 10 | 11 | export * from './index'; 12 | -------------------------------------------------------------------------------- /sdk_contrib/hapi/lib/index.js: -------------------------------------------------------------------------------- 1 | const { register } = require('./plugin'); 2 | const pjson = require('../package'); 3 | /** 4 | * 5 | * NPM Module 6 | */ 7 | module.exports = { 8 | /** 9 | * 10 | * HAPI Plugin 11 | */ 12 | plugin: { 13 | name: 'hapi-xray', 14 | register, 15 | once: true, 16 | version: pjson.version 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /sdk_contrib/hapi/lib/plugin.js: -------------------------------------------------------------------------------- 1 | const xray = require('./xray'); 2 | 3 | module.exports = { 4 | register: (server, options) => { 5 | xray.setup(options); 6 | 7 | server.ext({ 8 | type: 'onRequest', 9 | method: xray.handleRequest 10 | }); 11 | 12 | server.events.on( 13 | { name: 'request', channels: 'error' }, 14 | (request, event) => { 15 | xray.handleError(request, event.error); 16 | } 17 | ); 18 | 19 | server.events.on('response', xray.handleResponse); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /sdk_contrib/hapi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-xray-sdk-hapi", 3 | "version": "3.10.3", 4 | "description": "AWS X-Ray plugin for Hapi.JS", 5 | "author": "Amazon Web Services", 6 | "contributors": [ 7 | "Alex Coulcher", 8 | "Eric Swann " 9 | ], 10 | "main": "lib/index.js", 11 | "files": [ 12 | "lib" 13 | ], 14 | "types": "lib/index.d.ts", 15 | "scripts": { 16 | "lint": "eslint .", 17 | "lint:fix": "eslint --fix .", 18 | "test": "mocha --recursive --exit ./test/ -R spec && tsd", 19 | "test-d": "tsd", 20 | "sample": "node sample" 21 | }, 22 | "engines": { 23 | "node": ">= 14.x" 24 | }, 25 | "peerDependencies": { 26 | "@hapi/hapi": ">=18.x", 27 | "aws-xray-sdk-core": "^3.10.3" 28 | }, 29 | "keywords": [ 30 | "amazon", 31 | "api", 32 | "aws", 33 | "hapi", 34 | "xray", 35 | "x-ray", 36 | "x ray" 37 | ], 38 | "directories": { 39 | "lib": "lib" 40 | }, 41 | "license": "Apache-2.0", 42 | "repository": "https://github.com/aws/aws-xray-sdk-node/tree/master/sdk_contrib/hapi" 43 | } 44 | -------------------------------------------------------------------------------- /sdk_contrib/hapi/sample/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hapi = require('@hapi/hapi'); 4 | const xrayPlugin = require('./xray-plugin'); 5 | 6 | const init = async () => { 7 | const server = Hapi.server({ 8 | port: 3010, 9 | host: 'localhost', 10 | }); 11 | 12 | server.route({ 13 | method: 'GET', 14 | path: '/', 15 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 16 | handler: (request, h) => { 17 | return 'Hello World!'; 18 | }, 19 | }); 20 | 21 | await server.register(xrayPlugin); 22 | 23 | await server.start(); 24 | console.log('Server running on %s', server.info.uri); 25 | }; 26 | 27 | process.on('unhandledRejection', (err) => { 28 | console.log(err); 29 | process.exit(1); 30 | }); 31 | 32 | init(); 33 | -------------------------------------------------------------------------------- /sdk_contrib/hapi/sample/xray-plugin.js: -------------------------------------------------------------------------------- 1 | const awsXray = require('aws-xray-sdk-core'); 2 | const hapiXray = require('../lib'); 3 | 4 | module.exports = { 5 | plugin: hapiXray, 6 | options: { 7 | segmentName: 'hapi-xray-sample', 8 | captureAWS: true, 9 | captureHTTP: true, 10 | capturePromises: true, 11 | plugins: [awsXray.plugins.ECSPlugin], 12 | logger: console, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /sdk_contrib/hapi/test-d/index.test-d.ts.js: -------------------------------------------------------------------------------- 1 | import * as AWSXRay from 'aws-xray-sdk-core'; 2 | import * as hapi from '@hapi/hapi'; 3 | import { expectType } from 'tsd'; 4 | import * as hapiXray from '../lib'; 5 | 6 | const server = new hapi.Server(); 7 | hapiXray.plugin.register(server); 8 | 9 | server.route({ 10 | method: 'GET', 11 | path: '/', 12 | handler: (request) => { 13 | (expectType < AWSXRay.Segment) | (undefined > request.segment); 14 | return { data: 'ok' }; 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /sdk_contrib/hapi/test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "extends": [ 6 | "plugin:mocha/recommended" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /sdk_contrib/koa/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json" 3 | } 4 | -------------------------------------------------------------------------------- /sdk_contrib/koa/NOTICE.txt: -------------------------------------------------------------------------------- 1 | AWS X-Ray SDK Koa for JavaScript 2 | Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | This product includes software developed at 5 | Amazon Web Services, Inc. (http://aws.amazon.com/). 6 | -------------------------------------------------------------------------------- /sdk_contrib/koa/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Requirements 3 | 4 | AWS X-Ray SDK Core (aws-xray-sdk-core) 5 | Koa 2.x or greater 6 | 7 | ## AWS X-Ray and Koa 8 | 9 | The AWS X-Ray Koa package automatically records information for incoming and outgoing 10 | requests and responses, via the middleware functions in this package. To configure sampling, 11 | dynamic naming, and more see the [set up section](https://github.com/aws/aws-xray-sdk-node/tree/master/packages/core#setup). 12 | 13 | The AWS X-Ray SDK Core has two modes - `manual` and `automatic`. 14 | Automatic mode uses the `cls-hooked` package and automatically 15 | tracks the current segment and subsegment. This is the default mode. 16 | Manual mode requires that you pass around the segment reference. 17 | 18 | In automatic mode, you can get the current segment/subsegment at any time: 19 | var segment = AWSXRay.getSegment(); 20 | 21 | In manual mode, you can get the base segment off of the context object: 22 | var segment = ctx.segment; 23 | 24 | ## Middleware Usage 25 | 26 | The Koa X-Ray SDK provides one middlewares: `xrayKoa.openSegment()`. 27 | This middleware will wrap all of the defined routes that you'd like to trace. 28 | In automatic mode, the `openSegment` middleware *must* be the last middleware added 29 | before defining routes, otherwise issues with the `cls-hooked` 30 | context may occur. 31 | 32 | ## Automatic mode examples 33 | ```js 34 | var AWSXRay = require('aws-xray-sdk-core'); 35 | var xrayKoa = require('aws-xray-sdk-koa2'); 36 | var app = new Koa(); 37 | 38 | //... 39 | 40 | app.use(xrayKoa.openSegment('defaultName')); 41 | 42 | router.get('/myRoute', (ctx) => { 43 | const segment = AWSXRay.getSegment(); 44 | //Do whatever 45 | }); 46 | ``` 47 | 48 | ## Manual mode examples 49 | ```js 50 | var AWSXRay = require('aws-xray-sdk-core'); 51 | var xrayKoa = require('aws-xray-sdk-koa2'); 52 | var app = new Koa(); 53 | 54 | //... 55 | 56 | var AWSXRay = require('aws-xray-sdk'); 57 | 58 | app.use(xrayKoa.openSegment('defaultName')); //Required at the start of your routes 59 | 60 | router.get('/myRoute', (ctx) => { 61 | const segment = ctx.segment; 62 | //Do whatever 63 | }); 64 | ``` 65 | 66 | -------------------------------------------------------------------------------- /sdk_contrib/koa/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as AWSXRay from 'aws-xray-sdk-core'; 2 | 3 | declare global { 4 | namespace Koa { 5 | interface Request { 6 | segment?: AWSXRay.Segment; 7 | } 8 | } 9 | } 10 | 11 | export * from './koa_mw'; 12 | -------------------------------------------------------------------------------- /sdk_contrib/koa/lib/index.js: -------------------------------------------------------------------------------- 1 | // Convenience file to require the SDK from the root of the repository 2 | module.exports = require('./koa_mw'); 3 | -------------------------------------------------------------------------------- /sdk_contrib/koa/lib/koa_mw.d.ts: -------------------------------------------------------------------------------- 1 | import { Middleware } from 'koa'; 2 | 3 | export function openSegment(defaultName: string): Middleware; 4 | -------------------------------------------------------------------------------- /sdk_contrib/koa/lib/koa_mw.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Koa middleware module. 3 | * 4 | * Exposes Koa middleware functions to enable automated data capturing on a web service. 5 | * To enable on a Node.js/Koa application, use 'app.use(AWSXRayKoa.openSegment())' before defining your routes. 6 | * Use AWSXRay.getSegment() to access the current sub/segment. 7 | * Otherwise, for manual mode this appends the Segment object to the context object as ctx.segment. 8 | * @module koa_mw 9 | */ 10 | 11 | const AWSXRay = require('aws-xray-sdk-core'); 12 | 13 | const mwUtils = AWSXRay.middleware; 14 | const IncomingRequestData = mwUtils.IncomingRequestData; 15 | const Segment = AWSXRay.Segment; 16 | 17 | const koaMW = { 18 | /** 19 | * Use 'app.use(AWSXRayKoa.openSegment())' before defining your routes. 20 | * Use AWSXRay.getSegment() to access the current sub/segment. 21 | * Otherwise, for manual mode, this appends the Segment object to the request object as ctx.segment. 22 | * @param {string} defaultName - The default name for the segment. 23 | * @alias module:koa_mw.openSegment 24 | * @returns {function} 25 | */ 26 | 27 | openSegment: function openSegment(defaultName) { 28 | if (!defaultName || typeof defaultName !== 'string') { 29 | throw new Error( 30 | 'Default segment name was not supplied. Please provide a string.' 31 | ); 32 | } 33 | 34 | mwUtils.setDefaultName(defaultName); 35 | 36 | return async (ctx, next) => { 37 | const amznTraceHeader = mwUtils.processHeaders(ctx); 38 | const name = mwUtils.resolveName(ctx.host); 39 | const segment = new Segment( 40 | name, 41 | amznTraceHeader.Root || amznTraceHeader.root, 42 | amznTraceHeader.Parent || amznTraceHeader.parent 43 | ); 44 | 45 | mwUtils.resolveSampling(amznTraceHeader, segment, ctx); 46 | segment.addIncomingRequestData(new IncomingRequestData(ctx.req)); 47 | 48 | mwUtils.middlewareLog('Starting koa segment', ctx.url, segment); 49 | 50 | if (AWSXRay.isAutomaticMode()) { 51 | const ns = AWSXRay.getNamespace(); 52 | ns.bindEmitter(ctx.req); 53 | ns.bindEmitter(ctx.res); 54 | 55 | return ns.runAndReturn(async function () { 56 | let error; 57 | AWSXRay.setSegment(segment); 58 | ctx.segment = segment; 59 | try { 60 | if (next) { 61 | await next(); 62 | } 63 | } catch (err) { 64 | error = err; 65 | } 66 | exports._processResponse(ctx, segment, error); 67 | }); 68 | } else { 69 | let error; 70 | ctx.segment = segment; 71 | try { 72 | if (next) { 73 | await next(); 74 | } 75 | } catch (err) { 76 | error = err; 77 | } 78 | exports._processResponse(ctx, segment, error); 79 | } 80 | }; 81 | }, 82 | }; 83 | 84 | exports._processResponse = (ctx, segment, err) => { 85 | if (ctx.status >= 400) { 86 | if (ctx.status === 429) { 87 | segment.addThrottleFlag(); 88 | } 89 | segment[AWSXRay.utils.getCauseTypeFromHttpStatus(ctx.status)] = true; 90 | } 91 | 92 | if (segment.http && ctx.res) { 93 | segment.http.close(ctx.res); 94 | } 95 | segment.close(err); 96 | const message = err ? 'Closed koa segment with error' : 'Closed koa segment successfully'; 97 | mwUtils.middlewareLog(message, ctx.url, segment); 98 | if (err) { 99 | throw err; 100 | } 101 | }; 102 | 103 | module.exports = koaMW; 104 | -------------------------------------------------------------------------------- /sdk_contrib/koa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-xray-sdk-koa2", 3 | "version": "3.10.3", 4 | "description": "AWS X-Ray Middleware for koa (Javascript)", 5 | "author": "Amazon Web Services", 6 | "contributors": [ 7 | "Sandra McMullen ", 8 | "Eric Swann " 9 | ], 10 | "main": "lib/index.js", 11 | "files": [ 12 | "lib" 13 | ], 14 | "types": "lib/index.d.ts", 15 | "scripts": { 16 | "lint": "eslint .", 17 | "lint:fix": "eslint --fix .", 18 | "test": "mocha --recursive --exit ./test/ -R spec && tsd", 19 | "test-d": "tsd" 20 | }, 21 | "engines": { 22 | "node": ">= 14.x" 23 | }, 24 | "peerDependencies": { 25 | "aws-xray-sdk-core": "^3.10.3", 26 | "koa": "^2.0.0" 27 | }, 28 | "keywords": [ 29 | "amazon", 30 | "api", 31 | "aws", 32 | "koa", 33 | "xray", 34 | "x-ray", 35 | "x ray" 36 | ], 37 | "directories": { 38 | "lib": "lib" 39 | }, 40 | "license": "Apache-2.0", 41 | "repository": "https://github.com/aws/aws-xray-sdk-node/tree/master/sdk_contrib/koa" 42 | } 43 | -------------------------------------------------------------------------------- /sdk_contrib/koa/test-d/index.test-d.ts.js: -------------------------------------------------------------------------------- 1 | import * as AWSXRay from 'aws-xray-sdk-core'; 2 | import * as koa from 'koa'; 3 | import { expectType } from 'tsd'; 4 | import * as xrayKoa from '../lib'; 5 | 6 | const app = new koa(); 7 | 8 | app.use(xrayKoa.openSegment('defaultName')); 9 | 10 | app.get('/', function (ctx) { 11 | (expectType < AWSXRay.Segment) | (undefined > ctx.segment); 12 | ctx.res.render('index'); 13 | }); 14 | -------------------------------------------------------------------------------- /sdk_contrib/koa/test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "extends": [ 6 | "plugin:mocha/recommended" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /sdk_contrib/koa/test/test_utils.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | const util = require('util'); 3 | 4 | const TestUtils = {}; 5 | 6 | TestUtils.TestEmitter = function TestEmitter() { 7 | EventEmitter.call(this); 8 | }; 9 | 10 | util.inherits(TestUtils.TestEmitter, EventEmitter); 11 | 12 | TestUtils.onEvent = function onEvent(event, fcn) { 13 | this.emitter.on(event, fcn.bind(this)); 14 | return this; 15 | }; 16 | 17 | module.exports = TestUtils; 18 | -------------------------------------------------------------------------------- /smoke_test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-smoke", 3 | "version": "1.0.0", 4 | "description": "Smoke Test for AWS XRay SDK", 5 | "main": "index.js", 6 | "engines": { 7 | "node": ">= 14.x" 8 | }, 9 | "dependencies": { 10 | "aws-xray-sdk": "*", 11 | "chai": "^4.3.4", 12 | "mocha": "^8.0.1", 13 | "typescript": "4.2.4" 14 | }, 15 | "scripts": { 16 | "test-smoke": "mocha ./test/ -R spec" 17 | }, 18 | "license": "Apache-2.0" 19 | } 20 | -------------------------------------------------------------------------------- /smoke_test/test/smoke.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('chai').assert; 2 | const AWSXRay = require('aws-xray-sdk'); 3 | const Segment = AWSXRay.Segment; 4 | 5 | describe('Smoke Test', () => { 6 | it('Segment', () => { 7 | const segment = new Segment('test'); 8 | assert.equal(segment.name, 'test'); 9 | }); 10 | }); 11 | --------------------------------------------------------------------------------