diff --git a/.github/actions/instrumentations/test/action.yml b/.github/actions/instrumentations/test/action.yml new file mode 100644 index 00000000000..db42b987c87 --- /dev/null +++ b/.github/actions/instrumentations/test/action.yml @@ -0,0 +1,13 @@ +name: Instrumentation Tests +description: Run instrumentation tests +runs: + using: composite + steps: + - uses: ./.github/actions/node/oldest-maintenance-lts + - uses: ./.github/actions/install + - run: yarn test:instrumentations:ci + shell: bash + - uses: ./.github/actions/node/active-lts + - run: yarn test:instrumentations:ci + shell: bash + - uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0 diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml deleted file mode 100644 index 9d07fe7393e..00000000000 --- a/.github/workflows/actionlint.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Actionlint - -on: - pull_request: - push: - branches: [master] - schedule: - - cron: 0 4 * * * - - cron: 20 4 * * * - - cron: 40 4 * * * - -jobs: - actionlint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts - # NOTE: Ok this next bit seems unnecessary, right? The problem is that - # this repo is currently incompatible with npm, at least with the - # devDependencies. While this is intended to be corrected, it hasn't yet, - # so the easiest thing to do here is just use a fresh package.json. This - # is needed because actionlint runs an `npm install` at the beginning. - - name: Clear package.json - run: | - rm package.json - npm init -y - - name: actionlint - id: actionlint - uses: raven-actions/actionlint@01fce4f43a270a612932cb1c64d40505a029f821 # v2.0.0 - with: - matcher: true - fail-on-error: true - shellcheck: false # TODO should we enable this? - - name: actionlint Summary - if: ${{ steps.actionlint.outputs.exit-code != 0 }} - run: | - echo "Used actionlint version ${{ steps.actionlint.outputs.version-semver }}" - echo "Used actionlint release ${{ steps.actionlint.outputs.version-tag }}" - echo "actionlint ended with ${{ steps.actionlint.outputs.exit-code }} exit code" - echo "actionlint ended because '${{ steps.actionlint.outputs.exit-message }}'" - echo "actionlint found ${{ steps.actionlint.outputs.total-errors }} errors" - echo "actionlint checked ${{ steps.actionlint.outputs.total-files }} files" - echo "actionlint cache used: ${{ steps.actionlint.outputs.cache-hit }}" - exit ${{ steps.actionlint.outputs.exit-code }} diff --git a/.github/workflows/tracing.yml b/.github/workflows/apm-capabilities.yml similarity index 95% rename from .github/workflows/tracing.yml rename to .github/workflows/apm-capabilities.yml index 73a773d2127..ad5ff2f43cb 100644 --- a/.github/workflows/tracing.yml +++ b/.github/workflows/apm-capabilities.yml @@ -1,4 +1,4 @@ -name: Tracing +name: APM Capabilities on: pull_request: @@ -14,7 +14,7 @@ concurrency: cancel-in-progress: true jobs: - macos: + tracing-macos: runs-on: macos-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -23,7 +23,7 @@ jobs: - run: yarn test:trace:core:ci - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 - ubuntu: + tracing-ubuntu: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -38,7 +38,7 @@ jobs: - run: yarn test:trace:core:ci - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 - windows: + tracing-windows: runs-on: windows-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/.github/workflows/plugins.yml b/.github/workflows/apm-integrations.yml similarity index 86% rename from .github/workflows/plugins.yml rename to .github/workflows/apm-integrations.yml index 73d0695ae08..dbf31b4c1a0 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/apm-integrations.yml @@ -1,4 +1,4 @@ -name: Plugins +name: APM Integrations on: pull_request: @@ -15,7 +15,6 @@ concurrency: # TODO: upstream jobs - jobs: aerospike: strategy: @@ -196,22 +195,6 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/plugins/test - bluebird: - runs-on: ubuntu-latest - env: - PLUGINS: bluebird - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - - body-parser: - runs-on: ubuntu-latest - env: - PLUGINS: body-parser - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - bunyan: runs-on: ubuntu-latest env: @@ -294,14 +277,6 @@ jobs: suffix: plugins-${{ github.job }}-${{ matrix.node-version }} - uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1 - cookie-parser: - runs-on: ubuntu-latest - env: - PLUGINS: cookie-parser - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - couchbase: strategy: matrix: @@ -341,14 +316,6 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/plugins/test-and-upstream - cucumber: - runs-on: ubuntu-latest - env: - PLUGINS: cucumber - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - # TODO: fix performance issues and test more Node versions cypress: runs-on: ubuntu-latest @@ -428,21 +395,6 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/plugins/test - express-mongo-sanitize: - runs-on: ubuntu-latest - services: - mongodb: - image: circleci/mongo - ports: - - 27017:27017 - env: - PLUGINS: express-mongo-sanitize - PACKAGE_NAMES: express-mongo-sanitize - SERVICES: mongo - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - fastify: runs-on: ubuntu-latest env: @@ -467,14 +419,6 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/plugins/test - generic-pool: - runs-on: ubuntu-latest - env: - PLUGINS: generic-pool - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - google-cloud-pubsub: runs-on: ubuntu-latest services: @@ -489,14 +433,6 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/plugins/test - google-cloud-vertexai: - runs-on: ubuntu-latest - env: - PLUGINS: google-cloud-vertexai - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - graphql: runs-on: ubuntu-latest env: @@ -564,23 +500,6 @@ jobs: suffix: plugins-${{ github.job }} - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 - # TODO: fix performance issues and test more Node versions - jest: - runs-on: ubuntu-latest - env: - PLUGINS: jest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/testagent/start - - uses: ./.github/actions/node/active-lts - - uses: ./.github/actions/install - - run: yarn test:plugins:ci - - if: always() - uses: ./.github/actions/testagent/logs - with: - suffix: plugins-${{ github.job }} - - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 - kafkajs: runs-on: ubuntu-latest services: @@ -608,14 +527,6 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/plugins/test - knex: - runs-on: ubuntu-latest - env: - PLUGINS: knex - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - koa: runs-on: ubuntu-latest env: @@ -624,26 +535,6 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/plugins/test-and-upstream - langchain: - runs-on: ubuntu-latest - env: - PLUGINS: langchain - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/testagent/start - - uses: ./.github/actions/node/oldest-maintenance-lts - - uses: ./.github/actions/install - - run: yarn test:plugins:ci - shell: bash - - uses: ./.github/actions/node/active-lts - - run: yarn test:plugins:ci - shell: bash - - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 - - if: always() - uses: ./.github/actions/testagent/logs - with: - suffix: plugins-${{ github.job }} - limitd-client: runs-on: ubuntu-latest services: @@ -701,14 +592,6 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/plugins/test - mocha: - runs-on: ubuntu-latest - env: - PLUGINS: mocha - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - moleculer: runs-on: ubuntu-latest env: @@ -859,14 +742,6 @@ jobs: suffix: plugins-${{ github.job }}-${{ matrix.version }}-${{ matrix.range_clean }} - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 - openai: - runs-on: ubuntu-latest - env: - PLUGINS: openai - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - opensearch: runs-on: ubuntu-latest services: @@ -970,22 +845,6 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/plugins/test - promise: - runs-on: ubuntu-latest - env: - PLUGINS: promise - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test-and-upstream - - promise-js: - runs-on: ubuntu-latest - env: - PLUGINS: promise-js - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - protobufjs: runs-on: ubuntu-latest env: @@ -995,14 +854,6 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/plugins/test-and-upstream - q: - runs-on: ubuntu-latest - env: - PLUGINS: q - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - redis: runs-on: ubuntu-latest services: @@ -1130,22 +981,6 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/plugins/test - url: - runs-on: ubuntu-latest - env: - PLUGINS: url - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - - when: - runs-on: ubuntu-latest - env: - PLUGINS: when - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - winston: runs-on: ubuntu-latest env: diff --git a/.github/workflows/appsec.yml b/.github/workflows/appsec.yml index a737f8e178d..dbc29fdd4e9 100644 --- a/.github/workflows/appsec.yml +++ b/.github/workflows/appsec.yml @@ -255,13 +255,16 @@ jobs: - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 integration: + strategy: + matrix: + version: [oldest, maintenance, active, latest] runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/install - - uses: ./.github/actions/node/oldest-maintenance-lts - - run: yarn test:integration:appsec - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node + with: + version: ${{ matrix.version }} - run: yarn test:integration:appsec passport: diff --git a/.github/workflows/ci-visibility-performance.yml b/.github/workflows/ci-visibility-performance.yml deleted file mode 100644 index a42f6f19dbe..00000000000 --- a/.github/workflows/ci-visibility-performance.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: CI Visibility performance and correctness tests - -on: - pull_request: - push: - branches: - - master - schedule: - - cron: 0 4 * * * - - cron: 20 4 * * * - - cron: 40 4 * * * - -concurrency: - group: ${{ github.workflow }}-${{ github.ref || github.run_id }} - cancel-in-progress: true - -jobs: - ci-visibility-tests: - name: CI Visibility performance and correctness tests - runs-on: ubuntu-latest - steps: - - uses: actions/create-github-app-token@db3cdf40984fe6fd25ae19ac2bf2f4886ae8d959 # v2.0.5 - id: app-token - with: - app-id: ${{ vars.APP_ID }} - private-key: ${{ secrets.PRIVATE_KEY }} - repositories: | - dd-trace-js - test-environment - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - token: ${{ steps.app-token.outputs.token }} - - uses: ./.github/actions/node/oldest-maintenance-lts - - name: CI Visibility Performance Overhead Test - run: yarn bench:e2e:ci-visibility - env: - GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml deleted file mode 100644 index 52eb15caac5..00000000000 --- a/.github/workflows/core.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Core - -on: - pull_request: - push: - branches: [master] - schedule: - - cron: 0 4 * * * - - cron: 20 4 * * * - - cron: 40 4 * * * - -concurrency: - group: ${{ github.workflow }}-${{ github.ref || github.run_id }} - cancel-in-progress: true - -jobs: - shimmer: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/oldest-maintenance-lts - - uses: ./.github/actions/install - - run: yarn test:shimmer:ci - - uses: ./.github/actions/node/active-lts - - run: yarn test:shimmer:ci - - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 diff --git a/.github/workflows/datadog-static-analysis.yml b/.github/workflows/datadog-static-analysis.yml deleted file mode 100644 index aa716d53843..00000000000 --- a/.github/workflows/datadog-static-analysis.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Datadog Static Analysis - -on: - pull_request: - push: - branches: [master] - schedule: - - cron: 0 4 * * * - - cron: 20 4 * * * - - cron: 40 4 * * * - -jobs: - static-analysis: - runs-on: ubuntu-latest - name: Datadog Static Analyzer - steps: - - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Check code meets quality and security standards - id: datadog-static-analysis - uses: DataDog/datadog-static-analyzer-github-action@v1 - with: - dd_api_key: ${{ secrets.DD_API_KEY }} - dd_app_key: ${{ secrets.DD_APP_KEY }} - dd_site: datadoghq.com - cpu_count: 2 diff --git a/.github/workflows/instrumentations.yml b/.github/workflows/instrumentations.yml deleted file mode 100644 index 0a0968413c5..00000000000 --- a/.github/workflows/instrumentations.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Instrumentations - -on: - pull_request: - push: - branches: [master] - schedule: - - cron: 0 4 * * * - - cron: 20 4 * * * - - cron: 40 4 * * * - -concurrency: - group: ${{ github.workflow }}-${{ github.ref || github.run_id }} - cancel-in-progress: true - -# TODO: upstream jobs - - -jobs: - instrumentations-misc: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/testagent/start - - uses: ./.github/actions/node/oldest-maintenance-lts - - uses: ./.github/actions/install - - run: yarn test:instrumentations:misc:ci - shell: bash - - uses: ./.github/actions/node/newest-maintenance-lts - - run: yarn test:instrumentations:misc:ci - shell: bash - - uses: ./.github/actions/node/active-lts - - run: yarn test:instrumentations:misc:ci - shell: bash - - uses: ./.github/actions/node/latest - - run: yarn test:instrumentations:misc:ci - shell: bash - - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 - - if: always() - uses: ./.github/actions/testagent/logs - with: - suffix: test-${{ github.job }} - - # These ones don't have a plugin directory, but exist in the root - # instrumentations directory, so they need to be run somewhere. This seems to - # be a reasonable place to run them for now. - - express-session: - runs-on: ubuntu-latest - env: - PLUGINS: express-session - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - - multer: - runs-on: ubuntu-latest - env: - PLUGINS: multer - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - - passport: - runs-on: ubuntu-latest - env: - PLUGINS: passport - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - - passport-http: - runs-on: ubuntu-latest - env: - PLUGINS: passport-http - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test - - passport-local: - runs-on: ubuntu-latest - env: - PLUGINS: passport-local - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test diff --git a/.github/workflows/llmobs.yml b/.github/workflows/llmobs.yml index f9e3a8ef416..e495f3a595e 100644 --- a/.github/workflows/llmobs.yml +++ b/.github/workflows/llmobs.yml @@ -42,9 +42,11 @@ jobs: - uses: ./.github/actions/testagent/start - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install + - run: yarn test:plugins:ci - run: yarn test:llmobs:plugins:ci shell: bash - uses: ./.github/actions/node/active-lts + - run: yarn test:plugins:ci - run: yarn test:llmobs:plugins:ci shell: bash - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 @@ -62,9 +64,11 @@ jobs: - uses: ./.github/actions/testagent/start - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install + - run: yarn test:plugins:ci - run: yarn test:llmobs:plugins:ci shell: bash - uses: ./.github/actions/node/active-lts + - run: yarn test:plugins:ci - run: yarn test:llmobs:plugins:ci shell: bash - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 @@ -102,9 +106,11 @@ jobs: - uses: ./.github/actions/testagent/start - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install + - run: yarn test:plugins:ci - run: yarn test:llmobs:plugins:ci shell: bash - uses: ./.github/actions/node/active-lts + - run: yarn test:plugins:ci - run: yarn test:llmobs:plugins:ci shell: bash - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 diff --git a/.github/workflows/package-size.yml b/.github/workflows/package-size.yml deleted file mode 100644 index 82032f16607..00000000000 --- a/.github/workflows/package-size.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Package Size - -on: - pull_request: - schedule: - - cron: 0 4 * * * - - cron: 20 4 * * * - - cron: 40 4 * * * - -concurrency: - group: ${{ github.workflow }}-${{ github.ref || github.run_id }} - cancel-in-progress: true - -jobs: - package-size-report: - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts - - uses: ./.github/actions/install - - name: Compute module size tree and report - uses: qard/heaviest-objects-in-the-universe@e2af4ff3a88e5fe507bd2de1943b015ba2ddda66 # v1.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/platform.yml b/.github/workflows/platform.yml new file mode 100644 index 00000000000..32b0f3ba669 --- /dev/null +++ b/.github/workflows/platform.yml @@ -0,0 +1,325 @@ +name: Platform + +on: + pull_request: + push: + branches: [master] + schedule: + - cron: 0 4 * * * + - cron: 20 4 * * * + - cron: 40 4 * * * + +concurrency: + group: ${{ github.workflow }}-${{ github.ref || github.run_id }} + cancel-in-progress: true + +# TODO: upstream jobs + +jobs: + core: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/node/oldest-maintenance-lts + - uses: ./.github/actions/install + - run: yarn test:core:ci + - uses: ./.github/actions/node/active-lts + - run: yarn test:core:ci + - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 + + instrumentation-bluebird: + runs-on: ubuntu-latest + env: + PLUGINS: bluebird + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-body-parser: + runs-on: ubuntu-latest + env: + PLUGINS: body-parser + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-child_process: + runs-on: ubuntu-latest + env: + PLUGINS: child_process + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-cookie-parser: + runs-on: ubuntu-latest + env: + PLUGINS: cookie-parser + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-express-mongo-sanitize: + runs-on: ubuntu-latest + services: + mongodb: + image: circleci/mongo + ports: + - 27017:27017 + env: + PLUGINS: express-mongo-sanitize + SERVICES: mongo + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-express-session: + runs-on: ubuntu-latest + env: + PLUGINS: express-session + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-express: + runs-on: ubuntu-latest + env: + PLUGINS: express + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-fs: + runs-on: ubuntu-latest + env: + PLUGINS: fs + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-generic-pool: + runs-on: ubuntu-latest + env: + PLUGINS: generic-pool + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-http: + runs-on: ubuntu-latest + env: + PLUGINS: http + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-knex: + runs-on: ubuntu-latest + env: + PLUGINS: knex + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-mongoose: + runs-on: ubuntu-latest + services: + mongodb: + image: circleci/mongo + ports: + - 27017:27017 + env: + PLUGINS: mongoose + SERVICES: mongo + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-multer: + runs-on: ubuntu-latest + env: + PLUGINS: multer + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-mysql2: + runs-on: ubuntu-latest + services: + mysql: + image: mariadb:10.4 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' + MYSQL_DATABASE: 'db' + ports: + - 3306:3306 + env: + PLUGINS: mysql2 + SERVICES: mysql2 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-passport: + runs-on: ubuntu-latest + env: + PLUGINS: passport + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-passport-http: + runs-on: ubuntu-latest + env: + PLUGINS: passport-http + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-passport-local: + runs-on: ubuntu-latest + env: + PLUGINS: passport-local + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-pg: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:9.5 + env: + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + env: + PG_TEST_NATIVE: 'true' + PLUGINS: pg + SERVICES: postgres + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-promise-js: + runs-on: ubuntu-latest + env: + PLUGINS: promise-js + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-promise: + runs-on: ubuntu-latest + env: + PLUGINS: promise + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-q: + runs-on: ubuntu-latest + env: + PLUGINS: q + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-url: + runs-on: ubuntu-latest + env: + PLUGINS: url + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentation-when: + runs-on: ubuntu-latest + env: + PLUGINS: when + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/instrumentations/test + + instrumentations-misc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/testagent/start + - uses: ./.github/actions/node/oldest-maintenance-lts + - uses: ./.github/actions/install + - run: yarn test:instrumentations:misc:ci + shell: bash + - uses: ./.github/actions/node/newest-maintenance-lts + - run: yarn test:instrumentations:misc:ci + shell: bash + - uses: ./.github/actions/node/active-lts + - run: yarn test:instrumentations:misc:ci + shell: bash + - uses: ./.github/actions/node/latest + - run: yarn test:instrumentations:misc:ci + shell: bash + - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 + - if: always() + uses: ./.github/actions/testagent/logs + with: + suffix: test-${{ github.job }} + + # TODO: Split this up as it runs tests for multiple different teams. + integration: + strategy: + # when one version fails, say 14, all the other versions are stopped + # setting fail-fast to false in an attempt to prevent this from happening + fail-fast: false + matrix: + version: [oldest, maintenance, active, latest] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/node + with: + version: ${{ matrix.version }} + # Disable core dumps since some integration tests intentionally abort and core dump generation takes around 5-10s + - uses: ./.github/actions/install + - run: sudo sysctl -w kernel.core_pattern='|/bin/false' + - run: yarn test:integration + + # We'll run these separately for earlier (i.e. unsupported) versions + integration-guardrails: + strategy: + matrix: + version: [14.0.0, 14, 16.0.0, eol, 18.0.0, 18.1.0, 20.0.0, 22.0.0] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/node + with: + version: ${{ matrix.version }} + - uses: ./.github/actions/install + - run: node node_modules/.bin/mocha --colors --timeout 30000 integration-tests/init.spec.js + + integration-guardrails-unsupported: + strategy: + matrix: + version: ['0.8', '0.10', '0.12', '4', '6', '8', '10', '12'] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/node + with: + version: ${{ matrix.version }} + - run: node ./init + - run: node ./init + env: + DD_INJECTION_ENABLED: 'true' + + shimmer: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/node/oldest-maintenance-lts + - uses: ./.github/actions/install + - run: yarn test:shimmer:ci + - uses: ./.github/actions/node/active-lts + - run: yarn test:shimmer:ci + - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 diff --git a/.github/workflows/project.yml b/.github/workflows/project.yml index b225853b466..f19b9485bfe 100644 --- a/.github/workflows/project.yml +++ b/.github/workflows/project.yml @@ -4,181 +4,80 @@ on: pull_request: push: branches: [master] - schedule: - - cron: 0 4 * * * - - cron: 20 4 * * * - - cron: 40 4 * * * concurrency: group: ${{ github.workflow }}-${{ github.ref || github.run_id }} cancel-in-progress: true jobs: - integration: - strategy: - # when one version fails, say 14, all the other versions are stopped - # setting fail-fast to false in an attempt to prevent this from happening - fail-fast: false - matrix: - version: [oldest, maintenance, active, latest] + actionlint: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node - with: - version: ${{ matrix.version }} - # Disable core dumps since some integration tests intentionally abort and core dump generation takes around 5-10s - - uses: ./.github/actions/install - - run: sudo sysctl -w kernel.core_pattern='|/bin/false' - - run: yarn test:integration - - # We'll run these separately for earlier (i.e. unsupported) versions - integration-guardrails: - strategy: - matrix: - version: [14.0.0, 14, 16.0.0, eol, 18.0.0, 18.1.0, 20.0.0, 22.0.0] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node - with: - version: ${{ matrix.version }} - - uses: ./.github/actions/install - - run: node node_modules/.bin/mocha --colors --timeout 30000 integration-tests/init.spec.js - - integration-guardrails-unsupported: - strategy: - matrix: - version: ['0.8', '0.10', '0.12', '4', '6', '8', '10', '12'] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node - with: - version: ${{ matrix.version }} - - run: node ./init - - run: node ./init - env: - DD_INJECTION_ENABLED: 'true' - - integration-playwright: - strategy: - matrix: - version: [oldest, latest] - runs-on: ubuntu-latest - env: - DD_SERVICE: dd-trace-js-integration-tests - DD_CIVISIBILITY_AGENTLESS_ENABLED: 1 - DD_API_KEY: ${{ secrets.DD_API_KEY }} - OPTIONS_OVERRIDE: 1 - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node - with: - version: ${{ matrix.version }} - - uses: ./.github/actions/install - # Install system dependencies for playwright - - run: npx playwright install-deps - - run: yarn test:integration:playwright - env: - NODE_OPTIONS: '-r ./ci/init' - - integration-ci: - strategy: - matrix: - version: [oldest, latest] - framework: [cucumber, selenium, jest, mocha] - runs-on: ubuntu-latest - env: - DD_SERVICE: dd-trace-js-integration-tests - DD_CIVISIBILITY_AGENTLESS_ENABLED: 1 - DD_API_KEY: ${{ secrets.DD_API_KEY }} - OPTIONS_OVERRIDE: 1 - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node - with: - version: ${{ matrix.version }} - - name: Install Google Chrome + - uses: ./.github/actions/node/active-lts + # NOTE: Ok this next bit seems unnecessary, right? The problem is that + # this repo is currently incompatible with npm, at least with the + # devDependencies. While this is intended to be corrected, it hasn't yet, + # so the easiest thing to do here is just use a fresh package.json. This + # is needed because actionlint runs an `npm install` at the beginning. + - name: Clear package.json run: | - sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' - wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - - if [ $? -ne 0 ]; then echo "Failed to add Google key"; exit 1; fi - sudo apt-get update - sudo apt-get install -y google-chrome-stable - if [ $? -ne 0 ]; then echo "Failed to install Google Chrome"; exit 1; fi - if: ${{ matrix.framework == 'selenium' }} - - name: Install ChromeDriver + rm package.json + npm init -y + - name: actionlint + id: actionlint + uses: raven-actions/actionlint@01fce4f43a270a612932cb1c64d40505a029f821 # v2.0.0 + with: + matcher: true + fail-on-error: true + shellcheck: false # TODO should we enable this? + - name: actionlint Summary + if: ${{ steps.actionlint.outputs.exit-code != 0 }} run: | - export CHROME_VERSION=$(google-chrome --version) - CHROME_DRIVER_DOWNLOAD_URL=$(node --experimental-fetch scripts/get-chrome-driver-download-url.js) - wget -q "$CHROME_DRIVER_DOWNLOAD_URL" - if [ $? -ne 0 ]; then echo "Failed to download ChromeDriver"; exit 1; fi - unzip chromedriver-linux64.zip - sudo mv chromedriver-linux64/chromedriver /usr/bin/chromedriver - sudo chmod +x /usr/bin/chromedriver - if: ${{ matrix.framework == 'selenium' }} - - uses: ./.github/actions/install - - run: yarn test:integration:${{ matrix.framework }} - env: - NODE_OPTIONS: '-r ./ci/init' + echo "Used actionlint version ${{ steps.actionlint.outputs.version-semver }}" + echo "Used actionlint release ${{ steps.actionlint.outputs.version-tag }}" + echo "actionlint ended with ${{ steps.actionlint.outputs.exit-code }} exit code" + echo "actionlint ended because '${{ steps.actionlint.outputs.exit-message }}'" + echo "actionlint found ${{ steps.actionlint.outputs.total-errors }} errors" + echo "actionlint checked ${{ steps.actionlint.outputs.total-files }} files" + echo "actionlint cache used: ${{ steps.actionlint.outputs.cache-hit }}" + exit ${{ steps.actionlint.outputs.exit-code }} - integration-cypress: - strategy: - matrix: - version: [eol, oldest, latest] - # 6.7.0 is the minimum version we support in <=5 - # 10.2.0 is the minimum version we support in >=6 - # The logic to decide whether the tests run lives in integration-tests/cypress/cypress.spec.js - cypress-version: [6.7.0, 10.2.0, latest] - module-type: ['commonJS', 'esm'] + lint: runs-on: ubuntu-latest - env: - DD_SERVICE: dd-trace-js-integration-tests - DD_CIVISIBILITY_AGENTLESS_ENABLED: 1 - DD_API_KEY: ${{ secrets.DD_API_KEY }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node - with: - version: ${{ matrix.version }} + - uses: ./.github/actions/node/active-lts - uses: ./.github/actions/install - - run: yarn config set ignore-engines true - - run: yarn test:integration:cypress --ignore-engines - env: - CYPRESS_VERSION: ${{ matrix.cypress-version }} - NODE_OPTIONS: '-r ./ci/init' - CYPRESS_MODULE_TYPE: ${{ matrix.module-type }} - OPTIONS_OVERRIDE: 1 - + - run: yarn lint - integration-vitest: + package-size-report: runs-on: ubuntu-latest - strategy: - matrix: - version: [oldest, latest] - env: - DD_SERVICE: dd-trace-js-integration-tests - DD_CIVISIBILITY_AGENTLESS_ENABLED: 1 - DD_API_KEY: ${{ secrets.DD_API_KEY }} - OPTIONS_OVERRIDE: 1 + permissions: + pull-requests: write steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node - with: - version: ${{ matrix.version }} + - uses: ./.github/actions/node/active-lts - uses: ./.github/actions/install - - run: yarn test:integration:vitest - env: - NODE_OPTIONS: '-r ./ci/init' + - name: Compute module size tree and report + uses: qard/heaviest-objects-in-the-universe@e2af4ff3a88e5fe507bd2de1943b015ba2ddda66 # v1.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} - lint: + static-analysis: runs-on: ubuntu-latest + name: Datadog Static Analyzer steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts - - uses: ./.github/actions/install - - run: yarn lint + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Check code meets quality and security standards + id: datadog-static-analysis + uses: DataDog/datadog-static-analyzer-github-action@v1 + with: + dd_api_key: ${{ secrets.DD_API_KEY }} + dd_app_key: ${{ secrets.DD_APP_KEY }} + dd_site: datadoghq.com + cpu_count: 2 typescript: runs-on: ubuntu-latest @@ -196,3 +95,37 @@ jobs: - uses: ./.github/actions/node/active-lts - uses: ./.github/actions/install - run: node scripts/verify-ci-config.js + + yarn-dedupe: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Node.js + uses: ./.github/actions/node/active-lts + + - name: Install dependencies + uses: ./.github/actions/install + + - name: Run yarn dependencies:dedupe + run: yarn dependencies:dedupe + + - name: Check if yarn.lock was modified + run: | + if git diff --exit-code yarn.lock; then + echo "✅ yarn.lock is already properly deduplicated" + else + echo "❌ The yarn.lock file needs deduplication!" + echo "" + echo "The yarn dedupe command has modified your yarn.lock file." + echo "This means there were duplicate dependencies that could be optimized." + echo "" + echo "To fix this issue:" + echo "1. Run 'yarn dependencies:dedupe' locally" + echo "2. Commit the updated yarn.lock file" + echo "3. Push your changes" + echo "" + echo "This helps keep the dependency tree clean." + exit 1 + fi diff --git a/.github/workflows/test-optimization.yml b/.github/workflows/test-optimization.yml new file mode 100644 index 00000000000..99dc3a318e2 --- /dev/null +++ b/.github/workflows/test-optimization.yml @@ -0,0 +1,183 @@ +name: Test Optimization + +on: + pull_request: + push: + branches: + - master + schedule: + - cron: 0 4 * * * + - cron: 20 4 * * * + - cron: 40 4 * * * + +concurrency: + group: ${{ github.workflow }}-${{ github.ref || github.run_id }} + cancel-in-progress: true + +jobs: + benchmarks-e2e: + name: Performance and correctness tests + runs-on: ubuntu-latest + steps: + - uses: actions/create-github-app-token@db3cdf40984fe6fd25ae19ac2bf2f4886ae8d959 # v2.0.5 + id: app-token + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.PRIVATE_KEY }} + repositories: | + dd-trace-js + test-environment + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + token: ${{ steps.app-token.outputs.token }} + - uses: ./.github/actions/node/oldest-maintenance-lts + - name: CI Visibility Performance Overhead Test + run: yarn bench:e2e:ci-visibility + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + + integration-playwright: + strategy: + matrix: + node-version: [oldest, latest] + playwright-version: [oldest, latest] + name: integration-playwright (${{ matrix.playwright-version}}, node-${{ matrix.node-version }}) + runs-on: ubuntu-latest + env: + DD_SERVICE: dd-trace-js-integration-tests + DD_CIVISIBILITY_AGENTLESS_ENABLED: 1 + DD_API_KEY: ${{ secrets.DD_API_KEY }} + OPTIONS_OVERRIDE: 1 + PLAYWRIGHT_VERSION: ${{ matrix.playwright-version }} + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/node + with: + version: ${{ matrix.node-version }} + - uses: ./.github/actions/install + # Install system dependencies for playwright + - run: npx playwright install-deps + - run: yarn test:integration:playwright + env: + NODE_OPTIONS: '-r ./ci/init' + + integration-ci: + strategy: + matrix: + version: [oldest, latest] + framework: [cucumber, selenium, jest, mocha] + runs-on: ubuntu-latest + env: + DD_SERVICE: dd-trace-js-integration-tests + DD_CIVISIBILITY_AGENTLESS_ENABLED: 1 + DD_API_KEY: ${{ secrets.DD_API_KEY }} + OPTIONS_OVERRIDE: 1 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/node + with: + version: ${{ matrix.version }} + - name: Install Google Chrome + run: | + sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' + wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + if [ $? -ne 0 ]; then echo "Failed to add Google key"; exit 1; fi + sudo apt-get update + sudo apt-get install -y google-chrome-stable + if [ $? -ne 0 ]; then echo "Failed to install Google Chrome"; exit 1; fi + if: ${{ matrix.framework == 'selenium' }} + - name: Install ChromeDriver + run: | + export CHROME_VERSION=$(google-chrome --version) + CHROME_DRIVER_DOWNLOAD_URL=$(node --experimental-fetch scripts/get-chrome-driver-download-url.js) + wget -q "$CHROME_DRIVER_DOWNLOAD_URL" + if [ $? -ne 0 ]; then echo "Failed to download ChromeDriver"; exit 1; fi + unzip chromedriver-linux64.zip + sudo mv chromedriver-linux64/chromedriver /usr/bin/chromedriver + sudo chmod +x /usr/bin/chromedriver + if: ${{ matrix.framework == 'selenium' }} + - uses: ./.github/actions/install + - run: yarn test:integration:${{ matrix.framework }} + env: + NODE_OPTIONS: '-r ./ci/init' + + integration-cypress: + strategy: + matrix: + version: [eol, oldest, latest] + # 6.7.0 is the minimum version we support in <=5 + # 10.2.0 is the minimum version we support in >=6 + # The logic to decide whether the tests run lives in integration-tests/cypress/cypress.spec.js + cypress-version: [6.7.0, 10.2.0, latest] + module-type: ['commonJS', 'esm'] + runs-on: ubuntu-latest + env: + DD_SERVICE: dd-trace-js-integration-tests + DD_CIVISIBILITY_AGENTLESS_ENABLED: 1 + DD_API_KEY: ${{ secrets.DD_API_KEY }} + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/node + with: + version: ${{ matrix.version }} + - uses: ./.github/actions/install + - run: yarn config set ignore-engines true + - run: yarn test:integration:cypress --ignore-engines + env: + CYPRESS_VERSION: ${{ matrix.cypress-version }} + NODE_OPTIONS: '-r ./ci/init' + CYPRESS_MODULE_TYPE: ${{ matrix.module-type }} + OPTIONS_OVERRIDE: 1 + + integration-vitest: + runs-on: ubuntu-latest + strategy: + matrix: + version: [oldest, latest] + env: + DD_SERVICE: dd-trace-js-integration-tests + DD_CIVISIBILITY_AGENTLESS_ENABLED: 1 + DD_API_KEY: ${{ secrets.DD_API_KEY }} + OPTIONS_OVERRIDE: 1 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/node + with: + version: ${{ matrix.version }} + - uses: ./.github/actions/install + - run: yarn test:integration:vitest + env: + NODE_OPTIONS: '-r ./ci/init' + + plugin-cucumber: + runs-on: ubuntu-latest + env: + PLUGINS: cucumber + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/plugins/test + + # TODO: fix performance issues and test more Node versions + plugin-jest: + runs-on: ubuntu-latest + env: + PLUGINS: jest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/testagent/start + - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/install + - run: yarn test:plugins:ci + - if: always() + uses: ./.github/actions/testagent/logs + with: + suffix: plugins-${{ github.job }} + - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 + + plugin-mocha: + runs-on: ubuntu-latest + env: + PLUGINS: mocha + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/plugins/test diff --git a/.github/workflows/yarn-dedupe.yml b/.github/workflows/yarn-dedupe.yml deleted file mode 100644 index 629a552da74..00000000000 --- a/.github/workflows/yarn-dedupe.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Yarn Dedupe Check - -on: - pull_request: - push: - branches: [master] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref || github.run_id }} - cancel-in-progress: true - -jobs: - yarn-dedupe: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - - name: Setup Node.js - uses: ./.github/actions/node/active-lts - - - name: Install dependencies - uses: ./.github/actions/install - - - name: Run yarn dependencies:dedupe - run: yarn dependencies:dedupe - - - name: Check if yarn.lock was modified - run: | - if git diff --exit-code yarn.lock; then - echo "✅ yarn.lock is already properly deduplicated" - else - echo "❌ The yarn.lock file needs deduplication!" - echo "" - echo "The yarn dedupe command has modified your yarn.lock file." - echo "This means there were duplicate dependencies that could be optimized." - echo "" - echo "To fix this issue:" - echo "1. Run 'yarn dependencies:dedupe' locally" - echo "2. Commit the updated yarn.lock file" - echo "3. Push your changes" - echo "" - echo "This helps keep the dependency tree clean." - exit 1 - fi \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 11204635252..9977b40e5bf 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,6 +5,15 @@ stages: - single-step-instrumentation-tests - macrobenchmarks +# TODO(BridgeAR) Activate the gitlab CI job +# +# check_config_inversion_local_file: +# rules: +# - when: on_success +# extends: .check_config_inversion_local_file +# variables: +# LOCAL_JSON_PATH: "packages/dd-trace/src/supported-configurations.json" + include: - local: ".gitlab/one-pipeline.locked.yml" - local: ".gitlab/benchmarks.yml" diff --git a/CODEOWNERS b/CODEOWNERS index 98b8b1df305..1e99bb2817c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -38,13 +38,14 @@ /packages/dd-trace/test/service-naming/ @Datadog/apm-idm-js # CI +/.github/workflows/apm-capabilities.yml @DataDog/apm-sdk-api-js +/.github/workflows/apm-integrations.yml @Datadog/apm-idm-js /.github/workflows/appsec.yml @DataDog/asm-js -/.github/workflows/ci-visibility-performance.yml @DataDog/ci-app-libraries /.github/workflows/codeql-analysis.yml @DataDog/dd-trace-js -/.github/workflows/core.yml @DataDog/dd-trace-js +/.github/workflows/debugger.yml @DataDog/debugger /.github/workflows/lambda.yml @DataDog/serverless-aws -/.github/workflows/package-size.yml @DataDog/dd-trace-js -/.github/workflows/plugins.yml @DataDog/dd-trace-js +/.github/workflows/llmobs.yml @DataDog/ml-observability +/.github/workflows/platform.yml @DataDog/dd-trace-js /.github/workflows/pr-labels.yml @DataDog/dd-trace-js /.github/workflows/profiling.yml @DataDog/profiling-js /.github/workflows/project.yml @DataDog/dd-trace-js @@ -52,9 +53,10 @@ /.github/workflows/release-dev.yml @DataDog/dd-trace-js /.github/workflows/release-latest.yml @DataDog/dd-trace-js /.github/workflows/release-proposal.yml @DataDog/dd-trace-js -/.github/workflows/serverless-integration-test.yml @DataDog/serverless-aws @DataDog/serverless +/.github/workflows/release-validate.yml @DataDog/dd-trace-js +/.github/workflows/stale.yml @DataDog/dd-trace-js @DataDog/dd-trace-js /.github/workflows/system-tests.yml @DataDog/dd-trace-js @DataDog/asm-js /.github/workflows/test-k8s-lib-injection.yaml @DataDog/dd-trace-js -/.github/workflows/tracing.yml @DataDog/dd-trace-js +/.github/workflows/test-optimization.yml @DataDog/ci-app-libraries /.gitlab/benchmarks.yml @DataDog/dd-trace-js /.gitlab-ci.yml @DataDog/dd-trace-js diff --git a/ci/cypress/plugin.js b/ci/cypress/plugin.js index 8aa4f980767..a53439910ab 100644 --- a/ci/cypress/plugin.js +++ b/ci/cypress/plugin.js @@ -1,3 +1,11 @@ +const { NODE_MAJOR } = require('../../version') + +// These polyfills are here because cypress@6.7.0, which we still support for v5, runs its plugin code +// with Node.js@12. +if (NODE_MAJOR < 18) { + require('./polyfills') +} + require('../init') module.exports = require('../../packages/datadog-plugin-cypress/src/plugin') diff --git a/ci/cypress/polyfills.js b/ci/cypress/polyfills.js new file mode 100644 index 00000000000..a22f6a52ad5 --- /dev/null +++ b/ci/cypress/polyfills.js @@ -0,0 +1,23 @@ +if (!Object.hasOwn) { + Object.defineProperty(Object, 'hasOwn', { + // eslint-disable-next-line prefer-object-has-own + value: (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop), + writable: true, + configurable: true, + }) +} + +if (!Array.prototype.at) { + // eslint-disable-next-line no-extend-native + Object.defineProperty(Array.prototype, 'at', { + value: function (n) { + const len = this.length + if (len === 0) return + let index = Math.trunc(n) + if (index < 0) index += len + return (index < 0 || index >= len) ? undefined : this[index] + }, + writable: true, + configurable: true + }) +} diff --git a/ci/init.js b/ci/init.js index d7ef64daf3d..5928c826ae0 100644 --- a/ci/init.js +++ b/ci/init.js @@ -2,12 +2,13 @@ const tracer = require('../packages/dd-trace') const { isTrue, isFalse } = require('../packages/dd-trace/src/util') const log = require('../packages/dd-trace/src/log') +const { getEnvironmentVariable } = require('../packages/dd-trace/src/config-helper') -const isJestWorker = !!process.env.JEST_WORKER_ID -const isCucumberWorker = !!process.env.CUCUMBER_WORKER_ID -const isMochaWorker = !!process.env.MOCHA_WORKER_ID +const isJestWorker = !!getEnvironmentVariable('JEST_WORKER_ID') +const isCucumberWorker = !!getEnvironmentVariable('CUCUMBER_WORKER_ID') +const isMochaWorker = !!getEnvironmentVariable('MOCHA_WORKER_ID') -const isPlaywrightWorker = !!process.env.DD_PLAYWRIGHT_WORKER +const isPlaywrightWorker = !!getEnvironmentVariable('DD_PLAYWRIGHT_WORKER') const packageManagers = [ 'npm', @@ -25,17 +26,17 @@ const options = { flushInterval: isJestWorker ? 0 : 5000 } -let shouldInit = !isFalse(process.env.DD_CIVISIBILITY_ENABLED) +let shouldInit = !isFalse(getEnvironmentVariable('DD_CIVISIBILITY_ENABLED')) if (isPackageManager()) { log.debug('dd-trace is not initialized in a package manager.') shouldInit = false } -const isAgentlessEnabled = isTrue(process.env.DD_CIVISIBILITY_AGENTLESS_ENABLED) +const isAgentlessEnabled = isTrue(getEnvironmentVariable('DD_CIVISIBILITY_AGENTLESS_ENABLED')) if (isAgentlessEnabled) { - if (process.env.DATADOG_API_KEY || process.env.DD_API_KEY) { + if (getEnvironmentVariable('DD_API_KEY')) { options.experimental = { exporter: 'datadog' } diff --git a/eslint-rules/eslint-env-aliases.mjs b/eslint-rules/eslint-env-aliases.mjs new file mode 100644 index 00000000000..f176f1e9c1c --- /dev/null +++ b/eslint-rules/eslint-env-aliases.mjs @@ -0,0 +1,60 @@ +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __filename = fileURLToPath(import.meta.url) + +const supportedConfigsPath = path.resolve( + path.dirname(__filename), + '../packages/dd-trace/src/supported-configurations.json' +) +const { aliases } = JSON.parse(fs.readFileSync(supportedConfigsPath, 'utf8')) + +const aliasToCanonical = {} +for (const canonical of Object.keys(aliases)) { + for (const alias of aliases[canonical]) { + aliasToCanonical[alias] = canonical + } +} + +function report (context, node, alias) { + const canonical = aliasToCanonical[alias] + context.report({ + node, + message: `Use canonical environment variable name '${canonical}' instead of alias '${alias}'`, + fix (fixer) { + return fixer.replaceText(node, `'${canonical}'`) + } + }) +} + +export default { + meta: { + type: 'suggestion', + docs: { + description: 'Disallow usage of environment variable aliases instead of canonical names' + }, + fixable: 'code', + schema: [] + }, + create (context) { + return { + Literal (node) { + // Check if the string literal is an alias + if (typeof node.value === 'string' && Object.hasOwn(aliasToCanonical, node.value)) { + report(context, node, node.value) + } + }, + + // Also check for template literals when they contain only a string + TemplateLiteral (node) { + if (node.expressions.length === 0 && node.quasis.length === 1) { + const value = node.quasis[0].value.cooked + if (Object.hasOwn(aliasToCanonical, value)) { + report(context, node, value) + } + } + } + } + } +} diff --git a/eslint-rules/eslint-process-env.mjs b/eslint-rules/eslint-process-env.mjs new file mode 100644 index 00000000000..5a80dbb670f --- /dev/null +++ b/eslint-rules/eslint-process-env.mjs @@ -0,0 +1,57 @@ +export default { + meta: { + type: 'problem', + docs: { + description: 'Disallow usage of process.env outside config.js' + }, + schema: [] + }, + create (context) { + const isProcessEnvObject = (node) => { + return node?.type === 'MemberExpression' && + node.object?.name === 'process' && + node.property?.name === 'env' + } + + const report = (node) => { + context.report({ + node, + message: 'Usage of process.env is only allowed in config-helper.js' + }) + } + + return { + // Handle direct member expressions: process.env.FOO + MemberExpression (node) { + if (node.object?.type === 'MemberExpression' && + isProcessEnvObject(node.object)) { + report(node) + } + }, + + // Handle destructuring: const { FOO } = process.env + VariableDeclarator (node) { + if (isProcessEnvObject(node.init)) { + report(node) + } + }, + + // Handle spread operator: { ...process.env } + SpreadElement (node) { + if (isProcessEnvObject(node.argument)) { + report(node) + } + }, + + // Handle any function call with process.env as an argument + CallExpression (node) { + for (const arg of node.arguments) { + if (isProcessEnvObject(arg)) { + report(node) + break + } + } + } + } + } +} diff --git a/eslint.config.mjs b/eslint.config.mjs index b54bfd4dc27..c23d2a86f2b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,6 +9,9 @@ import eslintPluginN from 'eslint-plugin-n' import eslintPluginUnicorn from 'eslint-plugin-unicorn' import globals from 'globals' +import eslintProcessEnv from './eslint-rules/eslint-process-env.mjs' +import eslintEnvAliases from './eslint-rules/eslint-env-aliases.mjs' + const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const compat = new FlatCompat({ baseDirectory: __dirname }) @@ -18,6 +21,8 @@ const SRC_FILES = [ '*.mjs', 'ext/**/*.js', 'ext/**/*.mjs', + 'ci/**/*.js', + 'ci/**/*.mjs', 'packages/*/src/**/*.js', 'packages/*/src/**/*.mjs' ] @@ -82,13 +87,24 @@ export default [ }, rules: { - '@stylistic/max-len': ['error', { code: 120, tabWidth: 2, ignoreUrls: true }], + '@stylistic/max-len': ['error', { code: 120, tabWidth: 2, ignoreUrls: true, ignoreRegExpLiterals: true }], '@stylistic/object-curly-newline': ['error', { multiline: true, consistent: true }], '@stylistic/object-curly-spacing': ['error', 'always'], + '@stylistic/comma-dangle': ['error', { + arrays: 'only-multiline', + objects: 'only-multiline', + imports: 'always-multiline', + exports: 'always-multiline', + functions: 'only-multiline', + importAttributes: 'always-multiline', + dynamicImports: 'always-multiline' + }], + 'comma-dangle': 'off', // Override (turned on by @eslint/js/recommended) 'import/no-extraneous-dependencies': 'error', 'n/no-restricted-require': ['error', ['diagnostics_channel']], 'no-console': 'error', - 'no-prototype-builtins': 'off', // Override (turned on by @eslint/js/recommnded) + 'no-mixed-operators': 'off', // Override (turned on by standard) + 'no-prototype-builtins': 'off', // Override (turned on by @eslint/js/recommended) 'no-unused-expressions': 'off', // Override (turned on by standard) 'no-var': 'error', // Override (set to warn in standard) 'require-await': 'error' @@ -97,7 +113,17 @@ export default [ { name: 'dd-trace/src/all', files: SRC_FILES, + plugins: { + 'eslint-rules': { + rules: { + 'eslint-process-env': eslintProcessEnv, + 'eslint-env-aliases': eslintEnvAliases + } + } + }, rules: { + 'eslint-rules/eslint-process-env': 'error', + 'eslint-rules/eslint-env-aliases': 'error', 'n/no-restricted-require': ['error', [ { name: 'diagnostics_channel', @@ -109,6 +135,18 @@ export default [ } ]], + 'no-await-in-loop': 'error', + 'no-else-return': ['error', { allowElseIf: true }], + 'no-implicit-coercion': ['error', { boolean: true, number: true, string: true, allow: ['!!'] }], + 'no-useless-assignment': 'error', + 'operator-assignment': 'error', + 'prefer-exponentiation-operator': 'error', + 'prefer-object-has-own': 'error', + 'prefer-object-spread': 'error', + + // Too strict for now. Slowly migrate to this rule by using rest parameters. + // 'prefer-rest-params': 'error', + ...eslintPluginUnicorn.configs.recommended.rules, // Overriding recommended unicorn rules @@ -117,14 +155,8 @@ export default [ 'unicorn/explicit-length-check': 'off', // 68 errors 'unicorn/filename-case': ['off', { case: 'kebabCase' }], // 59 errors 'unicorn/no-array-for-each': 'off', // 122 errors - 'unicorn/no-null': 'off', // too strict - 'unicorn/prefer-array-flat': 'off', // 7 errors | Difficult to fix 'unicorn/prefer-at': 'off', // 17 errors | Difficult to fix - 'unicorn/prefer-spread': 'off', // 13 errors | Difficult to fix - 'unicorn/prefer-string-replace-all': 'off', // 33 errors - 'unicorn/prefer-switch': 'off', // 8 errors 'unicorn/prevent-abbreviations': 'off', // too strict - 'unicorn/switch-case-braces': 'off', // too strict // These rules could potentially evaluated again at a much later point 'unicorn/no-array-callback-reference': 'off', @@ -134,17 +166,20 @@ export default [ 'unicorn/prefer-code-point': 'off', // Should be activated, but needs a refactor of some code // The following rules should not be activated! - 'unicorn/prefer-top-level-await': 'off', // Only useful when using ESM - 'unicorn/prefer-math-trunc': 'off', // Math.trunc is not a 1-to-1 replacement for most of our usage 'unicorn/import-style': 'off', // Questionable benefit 'unicorn/no-array-reduce': 'off', // Questionable benefit 'unicorn/no-hex-escape': 'off', // Questionable benefit 'unicorn/no-new-array': 'off', // new Array is often used for performance reasons + 'unicorn/no-null': 'off', // We do not control external APIs and it is hard to differentiate these 'unicorn/prefer-event-target': 'off', // Benefit only outside of Node.js 'unicorn/prefer-global-this': 'off', // Questionable benefit in Node.js alone + 'unicorn/prefer-math-trunc': 'off', // Math.trunc is not a 1-to-1 replacement for most of our usage 'unicorn/prefer-module': 'off', // We use CJS 'unicorn/prefer-node-protocol': 'off', // May not be used due to guardrails - 'unicorn/prefer-reflect-apply': 'off' // Questionable benefit and more than 500 matches + 'unicorn/prefer-reflect-apply': 'off', // Questionable benefit and more than 500 matches + 'unicorn/prefer-switch': 'off', // Questionable benefit + 'unicorn/prefer-top-level-await': 'off', // Only useful when using ESM + 'unicorn/switch-case-braces': 'off', // Questionable benefit } }, { diff --git a/initialize.mjs b/initialize.mjs index ec7342def32..42787ce3d02 100644 --- a/initialize.mjs +++ b/initialize.mjs @@ -17,7 +17,7 @@ import { fileURLToPath } from 'node:url' import { load as origLoad, resolve as origResolve, - getSource as origGetSource + getSource as origGetSource, } from 'import-in-the-middle/hook.mjs' let hasInsertedInit = false @@ -31,7 +31,7 @@ ${result.source}` return result } -const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(x => +x) +const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(Number) const brokenLoaders = NODE_MAJOR === 18 && NODE_MINOR === 0 const iitmExclusions = [/langsmith/, /openai\/_shims/, /openai\/resources\/chat\/completions\/messages/] diff --git a/integration-tests/graphql.spec.js b/integration-tests/appsec/graphql.spec.js similarity index 99% rename from integration-tests/graphql.spec.js rename to integration-tests/appsec/graphql.spec.js index 4a66f163a83..053d6dca988 100644 --- a/integration-tests/graphql.spec.js +++ b/integration-tests/appsec/graphql.spec.js @@ -9,7 +9,7 @@ const { FakeAgent, createSandbox, spawnProc -} = require('./helpers') +} = require('../helpers') describe('graphql', () => { let sandbox, cwd, agent, webFile, proc, appPort diff --git a/integration-tests/standalone-asm.spec.js b/integration-tests/appsec/standalone-asm.spec.js similarity index 99% rename from integration-tests/standalone-asm.spec.js rename to integration-tests/appsec/standalone-asm.spec.js index 6da2e90ccc0..bb57a1260dc 100644 --- a/integration-tests/standalone-asm.spec.js +++ b/integration-tests/appsec/standalone-asm.spec.js @@ -9,8 +9,8 @@ const { spawnProc, curlAndAssertMessage, curl -} = require('./helpers') -const { USER_KEEP, AUTO_REJECT, AUTO_KEEP } = require('../ext/priority') +} = require('../helpers') +const { USER_KEEP, AUTO_REJECT, AUTO_KEEP } = require('../../ext/priority') describe('Standalone ASM', () => { let sandbox, cwd, startupTestFile, agent, proc, env diff --git a/integration-tests/ci-visibility/features/support/steps.js b/integration-tests/ci-visibility/features/support/steps.js index 5882703295c..23320b6ed46 100644 --- a/integration-tests/ci-visibility/features/support/steps.js +++ b/integration-tests/ci-visibility/features/support/steps.js @@ -1,5 +1,7 @@ const assert = require('assert') -const { When, Then, Before } = require('@cucumber/cucumber') +const { When, Then, Before, After } = require('@cucumber/cucumber') +const tracer = require('dd-trace') + class Greeter { sayFarewell () { return 'farewell' @@ -22,6 +24,18 @@ Before('@skip', function () { return 'skipped' }) +After(function () { + tracer.scope().active().addTags({ + 'custom_tag.after': 'hello after' + }) +}) + +Before(function () { + tracer.scope().active().addTags({ + 'custom_tag.before': 'hello before' + }) +}) + Then('I should have heard {string}', function (expectedResponse) { assert.equal(this.whatIHeard, expectedResponse) }) @@ -39,6 +53,9 @@ When('the greeter says yeah', function () { }) When('the greeter says greetings', function () { + tracer.scope().active().addTags({ + 'custom_tag.when': 'hello when' + }) this.whatIHeard = new Greeter().sayGreetings() }) diff --git a/integration-tests/ci-visibility/playwright-tests/landing-page-test.js b/integration-tests/ci-visibility/playwright-tests/landing-page-test.js index 7ee22886c7b..5115ee7939f 100644 --- a/integration-tests/ci-visibility/playwright-tests/landing-page-test.js +++ b/integration-tests/ci-visibility/playwright-tests/landing-page-test.js @@ -1,14 +1,36 @@ const { test, expect } = require('@playwright/test') +const tracer = require('dd-trace') test.beforeEach(async ({ page }) => { await page.goto(process.env.PW_BASE_URL) }) +// active span is only supported from >=1.38.0, at which point we add DD_PLAYWRIGHT_WORKER to the env +function setActiveTestSpanTags (tags) { + if (process.env.DD_PLAYWRIGHT_WORKER) { + tracer.scope().active().addTags(tags) + } + return null +} + test.describe('highest-level-describe', () => { test.describe(' leading and trailing spaces ', () => { // even empty describe blocks should be allowed test.describe(' ', () => { + test.beforeEach(async ({ page }) => { + setActiveTestSpanTags({ + 'custom_tag.beforeEach': 'hello beforeEach' + }) + }) + test.afterEach(async ({ page }) => { + setActiveTestSpanTags({ + 'custom_tag.afterEach': 'hello afterEach' + }) + }) test('should work with passing tests', async ({ page }) => { + setActiveTestSpanTags({ + 'custom_tag.it': 'hello it' + }) await expect(page.locator('.hello-world')).toHaveText([ 'Hello World' ]) diff --git a/integration-tests/ci-visibility/test-custom-tags/custom-tags.js b/integration-tests/ci-visibility/test-custom-tags/custom-tags.js index 5d283ec7f65..dbc4ae5d5b4 100644 --- a/integration-tests/ci-visibility/test-custom-tags/custom-tags.js +++ b/integration-tests/ci-visibility/test-custom-tags/custom-tags.js @@ -1,21 +1,21 @@ const { expect } = require('chai') const sum = require('../test/sum') -const ddTrace = require('dd-trace') +const tracer = require('dd-trace') -describe('ci visibility', () => { +describe('test optimization custom tags', () => { beforeEach(() => { - const testSpan = ddTrace.scope().active() + const testSpan = tracer.scope().active() testSpan.setTag('custom_tag.beforeEach', 'true') }) it('can report tests', () => { - const testSpan = ddTrace.scope().active() + const testSpan = tracer.scope().active() testSpan.setTag('custom_tag.it', 'true') expect(sum(1, 2)).to.equal(3) }) afterEach(() => { - const testSpan = ddTrace.scope().active() + const testSpan = tracer.scope().active() testSpan.setTag('custom_tag.afterEach', 'true') }) }) diff --git a/integration-tests/cucumber/cucumber.spec.js b/integration-tests/cucumber/cucumber.spec.js index c65ff07b740..692eed059a7 100644 --- a/integration-tests/cucumber/cucumber.spec.js +++ b/integration-tests/cucumber/cucumber.spec.js @@ -232,11 +232,18 @@ versions.forEach(version => { assert.propertyVal(meta, CUCUMBER_IS_PARALLEL, 'true') } assert.exists(metrics[DD_HOST_CPU_COUNT]) + if (!meta[TEST_NAME].includes('Say skip')) { + assert.propertyVal(meta, 'custom_tag.before', 'hello before') + assert.propertyVal(meta, 'custom_tag.after', 'hello after') + } }) stepEvents.forEach(stepEvent => { assert.equal(stepEvent.content.name, 'cucumber.step') assert.property(stepEvent.content.meta, 'cucumber.step') + if (stepEvent.content.meta['cucumber.step'] === 'the greeter says greetings') { + assert.propertyVal(stepEvent.content.meta, 'custom_tag.when', 'hello when') + } }) }, 5000) diff --git a/integration-tests/debugger/basic.spec.js b/integration-tests/debugger/basic.spec.js index 770e674916d..8c2ce36d08f 100644 --- a/integration-tests/debugger/basic.spec.js +++ b/integration-tests/debugger/basic.spec.js @@ -10,7 +10,7 @@ const { version } = require('../../package.json') describe('Dynamic Instrumentation', function () { describe('Default env', function () { - const t = setup() + const t = setup({ dependencies: ['fastify'] }) it('base case: target app should work as expected if no test probe has been added', async function () { const response = await t.axios.get(t.breakpoint.url) @@ -727,7 +727,10 @@ describe('Dynamic Instrumentation', function () { }) describe('DD_TRACING_ENABLED=true, DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED=true', function () { - const t = setup({ env: { DD_TRACING_ENABLED: true, DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED: true } }) + const t = setup({ + env: { DD_TRACING_ENABLED: true, DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED: true }, + dependencies: ['fastify'] + }) describe('input messages', function () { it( @@ -738,7 +741,10 @@ describe('Dynamic Instrumentation', function () { }) describe('DD_TRACING_ENABLED=true, DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED=false', function () { - const t = setup({ env: { DD_TRACING_ENABLED: true, DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED: false } }) + const t = setup({ + env: { DD_TRACING_ENABLED: true, DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED: false }, + dependencies: ['fastify'] + }) describe('input messages', function () { it( @@ -749,7 +755,10 @@ describe('Dynamic Instrumentation', function () { }) describe('DD_TRACING_ENABLED=false', function () { - const t = setup({ env: { DD_TRACING_ENABLED: false } }) + const t = setup({ + env: { DD_TRACING_ENABLED: false }, + dependencies: ['fastify'] + }) describe('input messages', function () { it( diff --git a/integration-tests/debugger/ddtags.spec.js b/integration-tests/debugger/ddtags.spec.js index 717e3fa0037..31e37218f95 100644 --- a/integration-tests/debugger/ddtags.spec.js +++ b/integration-tests/debugger/ddtags.spec.js @@ -16,7 +16,8 @@ describe('Dynamic Instrumentation', function () { DD_GIT_COMMIT_SHA: 'test-commit-sha', DD_GIT_REPOSITORY_URL: 'test-repository-url' }, - testApp: 'target-app/basic.js' + testApp: 'target-app/basic.js', + dependencies: ['fastify'] }) it('should add the expected ddtags as a query param to /debugger/v1/input', function (done) { @@ -51,7 +52,7 @@ describe('Dynamic Instrumentation', function () { }) describe('with undefined values', function () { - const t = setup({ testApp: 'target-app/basic.js' }) + const t = setup({ testApp: 'target-app/basic.js', dependencies: ['fastify'] }) it('should not include undefined values in the ddtags query param', function (done) { t.triggerBreakpoint() diff --git a/integration-tests/debugger/redact.spec.js b/integration-tests/debugger/redact.spec.js index 4cce3389f3c..d6b4b374f20 100644 --- a/integration-tests/debugger/redact.spec.js +++ b/integration-tests/debugger/redact.spec.js @@ -7,7 +7,10 @@ const { once } = require('node:events') // Default settings is tested in unit tests, so we only need to test the env vars here describe('Dynamic Instrumentation snapshot PII redaction', function () { describe('DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS=foo,bar', function () { - const t = setup({ env: { DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS: 'foo,bar' } }) + const t = setup({ + env: { DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS: 'foo,bar' }, + dependencies: ['fastify'] + }) it('should respect DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS', async function () { t.triggerBreakpoint() @@ -29,7 +32,10 @@ describe('Dynamic Instrumentation snapshot PII redaction', function () { }) describe('DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS=secret', function () { - const t = setup({ env: { DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS: 'secret' } }) + const t = setup({ + env: { DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS: 'secret' }, + dependencies: ['fastify'] + }) it('should respect DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS', async function () { t.triggerBreakpoint() diff --git a/integration-tests/debugger/snapshot-global-sample-rate.spec.js b/integration-tests/debugger/snapshot-global-sample-rate.spec.js index aff9c09c0ab..5dc711b60f1 100644 --- a/integration-tests/debugger/snapshot-global-sample-rate.spec.js +++ b/integration-tests/debugger/snapshot-global-sample-rate.spec.js @@ -5,7 +5,8 @@ const { setup } = require('./utils') describe('Dynamic Instrumentation', function () { const t = setup({ - testApp: 'target-app/basic.js' + testApp: 'target-app/basic.js', + dependencies: ['fastify'] }) describe('input messages', function () { diff --git a/integration-tests/debugger/snapshot-pruning.spec.js b/integration-tests/debugger/snapshot-pruning.spec.js index 4ae59ba0990..3df11f1c03b 100644 --- a/integration-tests/debugger/snapshot-pruning.spec.js +++ b/integration-tests/debugger/snapshot-pruning.spec.js @@ -4,7 +4,7 @@ const { assert } = require('chai') const { setup } = require('./utils') describe('Dynamic Instrumentation', function () { - const t = setup() + const t = setup({ dependencies: ['fastify'] }) describe('input messages', function () { describe('with snapshot', function () { diff --git a/integration-tests/debugger/snapshot.spec.js b/integration-tests/debugger/snapshot.spec.js index 98323d6c785..57b101df686 100644 --- a/integration-tests/debugger/snapshot.spec.js +++ b/integration-tests/debugger/snapshot.spec.js @@ -4,7 +4,7 @@ const { assert } = require('chai') const { setup } = require('./utils') describe('Dynamic Instrumentation', function () { - const t = setup() + const t = setup({ dependencies: ['fastify'] }) describe('input messages', function () { describe('with snapshot', function () { diff --git a/integration-tests/debugger/template.spec.js b/integration-tests/debugger/template.spec.js index 60422c99433..58b43b2f2ff 100644 --- a/integration-tests/debugger/template.spec.js +++ b/integration-tests/debugger/template.spec.js @@ -6,7 +6,7 @@ const { NODE_MAJOR } = require('../../version') describe('Dynamic Instrumentation', function () { describe('template evaluation', function () { - const t = setup() + const t = setup({ dependencies: ['fastify'] }) beforeEach(t.triggerBreakpoint) diff --git a/integration-tests/debugger/utils.js b/integration-tests/debugger/utils.js index d2ad65708a6..02d06685297 100644 --- a/integration-tests/debugger/utils.js +++ b/integration-tests/debugger/utils.js @@ -18,7 +18,7 @@ module.exports = { setup } -function setup ({ env, testApp, testAppSource } = {}) { +function setup ({ env, testApp, testAppSource, dependencies } = {}) { let sandbox, cwd, appPort const breakpoints = getBreakpointInfo({ deployedFile: testApp, @@ -74,7 +74,7 @@ function setup ({ env, testApp, testAppSource } = {}) { } before(async function () { - sandbox = await createSandbox(['fastify']) // TODO: Make this dynamic + sandbox = await createSandbox(dependencies) cwd = sandbox.folder // The sandbox uses the `integration-tests` folder as its root t.appFile = join(cwd, 'debugger', breakpoints[0].deployedFile) diff --git a/integration-tests/helpers/index.js b/integration-tests/helpers/index.js index 77129556f5c..0e687a29a8c 100644 --- a/integration-tests/helpers/index.js +++ b/integration-tests/helpers/index.js @@ -147,7 +147,7 @@ async function createSandbox (dependencies = [], isGitRepo = false, integrationTestsPaths = ['./integration-tests/*'], followUpCommand) { // We might use NODE_OPTIONS to init the tracer. We don't want this to affect this operations const { NODE_OPTIONS, ...restOfEnv } = process.env - const noSandbox = String(process.env.DD_NO_INTEGRATION_TESTS_SANDBOX) + const noSandbox = String(process.env.TESTING_NO_INTEGRATION_SANDBOX) if (noSandbox === '1' || noSandbox.toLowerCase() === 'true') { // Execute integration tests without a sandbox. This is useful when you have other components // yarn-linked into dd-trace and want to run the integration tests against them. diff --git a/integration-tests/mocha/mocha.spec.js b/integration-tests/mocha/mocha.spec.js index 4b9c9616982..959266ddb1e 100644 --- a/integration-tests/mocha/mocha.spec.js +++ b/integration-tests/mocha/mocha.spec.js @@ -257,6 +257,41 @@ describe('mocha CommonJS', function () { }) }) + context('custom tagging', () => { + it('can add custom tags to the tests', (done) => { + const eventsPromise = receiver + .gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => { + const events = payloads.flatMap(({ payload }) => payload.events) + const test = events.find(event => event.type === 'test').content + + assert.isNotEmpty(test.meta) + assert.equal(test.meta['custom_tag.beforeEach'], 'true') + assert.equal(test.meta['custom_tag.it'], 'true') + assert.equal(test.meta['custom_tag.afterEach'], 'true') + }) + + childProcess = exec( + runTestsWithCoverageCommand, + { + cwd, + env: { + ...getCiVisAgentlessConfig(receiver.port), + TESTS_TO_RUN: JSON.stringify([ + './test-custom-tags/custom-tags.js' + ]) + }, + stdio: 'inherit' + } + ) + + childProcess.on('exit', () => { + eventsPromise.then(() => { + done() + }).catch(done) + }) + }) + }) + context('when no ci visibility init is used', () => { it('does not crash', (done) => { childProcess = fork(startupTestFile, { diff --git a/integration-tests/pino.spec.js b/integration-tests/pino.spec.js index 4566eae63fb..0a2163ab596 100644 --- a/integration-tests/pino.spec.js +++ b/integration-tests/pino.spec.js @@ -1,4 +1,3 @@ -/* eslint-disable comma-dangle */ 'use strict' const { FakeAgent, spawnProc, createSandbox, curl } = require('./helpers') diff --git a/integration-tests/playwright/playwright.spec.js b/integration-tests/playwright/playwright.spec.js index 227ef4abbb7..8264c666106 100644 --- a/integration-tests/playwright/playwright.spec.js +++ b/integration-tests/playwright/playwright.spec.js @@ -55,11 +55,18 @@ const { DD_HOST_CPU_COUNT } = require('../../packages/dd-trace/src/plugins/util/ const { ERROR_MESSAGE } = require('../../packages/dd-trace/src/constants') const { DD_MAJOR } = require('../../version') +const { PLAYWRIGHT_VERSION } = process.env + const NUM_RETRIES_EFD = 3 -const versions = [DD_MAJOR >= 6 ? '1.38.0' : '1.18.0', 'latest'] +const latest = 'latest' +const oldest = DD_MAJOR >= 6 ? '1.38.0' : '1.18.0' +const versions = [oldest, latest] versions.forEach((version) => { + if (PLAYWRIGHT_VERSION === 'oldest' && version !== oldest) return + if (PLAYWRIGHT_VERSION === 'latest' && version !== latest) return + // TODO: Remove this once we drop suppport for v5 const contextNewVersions = (...args) => { if (satisfies(version, '>=1.38.0') || version === 'latest') { @@ -194,6 +201,16 @@ versions.forEach((version) => { JSON.stringify({ arguments: { browser: 'chromium' }, metadata: {} }) ) assert.exists(testEvent.content.metrics[DD_HOST_CPU_COUNT]) + if (version === 'latest' || satisfies(version, '>=1.38.0')) { + if (testEvent.content.meta[TEST_STATUS] !== 'skip' && + testEvent.content.meta[TEST_SUITE].includes('landing-page-test.js')) { + assert.propertyVal(testEvent.content.meta, 'custom_tag.beforeEach', 'hello beforeEach') + assert.propertyVal(testEvent.content.meta, 'custom_tag.afterEach', 'hello afterEach') + } + if (testEvent.content.meta[TEST_NAME].includes('should work with passing tests')) { + assert.propertyVal(testEvent.content.meta, 'custom_tag.it', 'hello it') + } + } }) stepEvents.forEach(stepEvent => { diff --git a/integration-tests/profiler/profiler.spec.js b/integration-tests/profiler/profiler.spec.js index 2c390846a53..7632c402265 100644 --- a/integration-tests/profiler/profiler.spec.js +++ b/integration-tests/profiler/profiler.spec.js @@ -43,8 +43,11 @@ function expectProfileMessagePromise (agent, timeout, assert.propertyVal(event, 'family', 'node') assert.isString(event.info.profiler.activation) assert.isString(event.info.profiler.ssi.mechanism) - assert.deepPropertyVal(event, 'attachments', fileNames) - for (const [index, fileName] of fileNames.entries()) { + const attachments = event.attachments + assert.isArray(attachments) + // Profiler encodes the files with Promise.all, so their ordering is not guaranteed + assert.sameMembers(attachments, fileNames) + for (const [index, fileName] of attachments.entries()) { assert.propertyVal(files[index + 1], 'originalname', fileName) } } catch (e) { diff --git a/package.json b/package.json index d97b49da5e5..3526c8ef683 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dd-trace", - "version": "5.54.0", + "version": "5.55.0", "description": "Datadog APM tracing client for JavaScript", "main": "index.js", "typings": "index.d.ts", @@ -26,8 +26,8 @@ "test:debugger:ci": "nyc --no-clean --include 'packages/dd-trace/src/debugger/**/*.js' -- npm run test:debugger", "test:trace:core": "tap packages/dd-trace/test/*.spec.js \"packages/dd-trace/test/{ci-visibility,datastreams,encode,exporters,opentelemetry,opentracing,plugins,service-naming,standalone,telemetry}/**/*.spec.js\"", "test:trace:core:ci": "npm run test:trace:core -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/**/*.js\"", - "test:instrumentations": "mocha -r 'packages/dd-trace/test/setup/mocha.js' 'packages/datadog-instrumentations/test/**/*.spec.js'", - "test:instrumentations:ci": "nyc --no-clean --include 'packages/datadog-instrumentations/src/**/*.js' -- npm run test:instrumentations", + "test:instrumentations": "mocha -r 'packages/dd-trace/test/setup/mocha.js' \"packages/datadog-instrumentations/test/@($(echo $PLUGINS)).spec.js\"", + "test:instrumentations:ci": "yarn services && nyc --no-clean --include \"packages/datadog-instrumentations/src/@($(echo $PLUGINS)).js\" --include \"packages/datadog-instrumentations/src/@($(echo $PLUGINS))/**/*.js\" -- npm run test:instrumentations", "test:instrumentations:misc": "mocha -r 'packages/dd-trace/test/setup/mocha.js' 'packages/datadog-instrumentations/test/*/**/*.spec.js'", "test:instrumentations:misc:ci": "nyc --no-clean --include 'packages/datadog-instrumentations/src/**/*.js' -- npm run test:instrumentations:misc", "test:core": "tap \"packages/datadog-core/test/**/*.spec.js\"", @@ -38,8 +38,8 @@ "test:llmobs:sdk:ci": "nyc --no-clean --include \"packages/dd-trace/src/llmobs/**/*.js\" -- npm run test:llmobs:sdk", "test:llmobs:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/llmobs/plugins/@($(echo $PLUGINS))/*.spec.js\"", "test:llmobs:plugins:ci": "yarn services && nyc --no-clean --include \"packages/dd-trace/src/llmobs/**/*.js\" -- npm run test:llmobs:plugins", - "test:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-instrumentations/test/@($(echo $PLUGINS)).spec.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/**/*.spec.js\"", - "test:plugins:ci": "yarn services && nyc --no-clean --include \"packages/datadog-instrumentations/src/@($(echo $PLUGINS)).js\" --include \"packages/datadog-instrumentations/src/@($(echo $PLUGINS))/**/*.js\" --include \"packages/datadog-plugin-@($(echo $PLUGINS))/src/**/*.js\" -- npm run test:plugins", + "test:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/**/*.spec.js\"", + "test:plugins:ci": "yarn services && nyc --no-clean --include \"packages/datadog-plugin-@($(echo $PLUGINS))/src/**/*.js\" -- npm run test:plugins", "test:plugins:upstream": "node ./packages/dd-trace/test/plugins/suite.js", "test:profiler": "tap \"packages/dd-trace/test/profiling/**/*.spec.js\"", "test:profiler:ci": "npm run test:profiler -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/profiling/**/*.js\"", @@ -143,7 +143,7 @@ "graphql": "0.13.2", "jszip": "^3.10.1", "mocha": "^10.8.2", - "multer": "^2.0.0", + "multer": "^2.0.1", "nock": "^11.9.1", "nyc": "^15.1.0", "proxyquire": "^1.8.0", diff --git a/packages/datadog-code-origin/index.js b/packages/datadog-code-origin/index.js index 278aac265ab..d87bb2cd4a9 100644 --- a/packages/datadog-code-origin/index.js +++ b/packages/datadog-code-origin/index.js @@ -2,22 +2,40 @@ const { getUserLandFrames } = require('../dd-trace/src/plugins/util/stacktrace') -const limit = Number(process.env._DD_CODE_ORIGIN_MAX_USER_FRAMES) || 8 +const ENTRY_SPAN_STACK_FRAMES_LIMIT = 1 +const EXIT_SPAN_STACK_FRAMES_LIMIT = Number(process.env._DD_CODE_ORIGIN_FOR_SPANS_EXIT_SPAN_MAX_USER_FRAMES) || 8 module.exports = { entryTags, exitTags } +/** + * @param {Function} topOfStackFunc - A function present in the current stack, above which no stack frames should be + * collected. + * @returns {Record} + */ function entryTags (topOfStackFunc) { - return tag('entry', topOfStackFunc) + return tag('entry', topOfStackFunc, ENTRY_SPAN_STACK_FRAMES_LIMIT) } +/** + * @param {Function} topOfStackFunc - A function present in the current stack, above which no stack frames should be + * collected. + * @returns {Record} + */ function exitTags (topOfStackFunc) { - return tag('exit', topOfStackFunc) + return tag('exit', topOfStackFunc, EXIT_SPAN_STACK_FRAMES_LIMIT) } -function tag (type, topOfStackFunc) { +/** + * @param {'entry'|'exit'} type - The type of code origin. + * @param {Function} topOfStackFunc - A function present in the current stack, above which no stack frames should be + * collected. + * @param {number} limit - The maximum number of stack frames to include in the tags. + * @returns {Record} + */ +function tag (type, topOfStackFunc, limit) { const frames = getUserLandFrames(topOfStackFunc, limit) const tags = { '_dd.code_origin.type': type diff --git a/packages/datadog-code-origin/test/helpers.js b/packages/datadog-code-origin/test/helpers.js new file mode 100644 index 00000000000..37428ed2569 --- /dev/null +++ b/packages/datadog-code-origin/test/helpers.js @@ -0,0 +1,27 @@ +'use strict' + +module.exports = { + assertCodeOriginFromTraces (traces, frame) { + const spans = traces[0] + const tags = spans[0].meta + + expect(tags).to.have.property('_dd.code_origin.type', 'entry') + + expect(tags).to.have.property('_dd.code_origin.frames.0.file', frame.file) + expect(tags).to.have.property('_dd.code_origin.frames.0.line', String(frame.line)) + expect(tags).to.have.property('_dd.code_origin.frames.0.column').to.match(/^\d+$/) + if (frame.method) { + expect(tags).to.have.property('_dd.code_origin.frames.0.method', frame.method) + } else { + expect(tags).to.not.have.property('_dd.code_origin.frames.0.method') + } + if (frame.type) { + expect(tags).to.have.property('_dd.code_origin.frames.0.type', frame.type) + } else { + expect(tags).to.not.have.property('_dd.code_origin.frames.0.type') + } + + // The second frame should not be present, because we only collect 1 frame for entry spans + expect(tags).to.not.have.property('_dd.code_origin.frames.1.file') + } +} diff --git a/packages/datadog-core/src/utils/src/kebabcase.js b/packages/datadog-core/src/utils/src/kebabcase.js index 79bf8d16e6d..ae2a1dc1d73 100644 --- a/packages/datadog-core/src/utils/src/kebabcase.js +++ b/packages/datadog-core/src/utils/src/kebabcase.js @@ -7,8 +7,8 @@ module.exports = function kebabcase (str) { return str .trim() - .replace(/([a-z])([A-Z])/g, '$1-$2') // Convert camelCase to kebab-case - .replace(/[\s_]+/g, '-') // Replace spaces and underscores with a single dash - .replace(/^-+|-+$/g, '') // Trim leading and trailing dashes + .replaceAll(/([a-z])([A-Z])/g, '$1-$2') // Convert camelCase to kebab-case + .replaceAll(/[\s_]+/g, '-') // Replace spaces and underscores with a single dash + .replaceAll(/^-+|-+$/g, '') // Trim leading and trailing dashes .toLowerCase() } diff --git a/packages/datadog-core/test/storage.spec.js b/packages/datadog-core/test/storage.spec.js index f2145f8c04f..cf520268c37 100644 --- a/packages/datadog-core/test/storage.spec.js +++ b/packages/datadog-core/test/storage.spec.js @@ -4,7 +4,7 @@ require('../../dd-trace/test/setup/tap') const { expect } = require('chai') const { executionAsyncResource } = require('async_hooks') -const storage = require('../src/storage') +const { storage } = require('../src/storage') describe('storage', () => { let testStorage @@ -16,17 +16,17 @@ describe('storage', () => { }) afterEach(() => { - testStorage('legacy').enterWith(undefined) + testStorage.enterWith(undefined) testStorage2.enterWith(undefined) }) it('should enter a store', done => { const store = 'foo' - testStorage('legacy').enterWith(store) + testStorage.enterWith(store) setImmediate(() => { - expect(testStorage('legacy').getStore()).to.equal(store) + expect(testStorage.getStore()).to.equal(store) done() }) }) @@ -35,11 +35,11 @@ describe('storage', () => { const store = 'foo' const store2 = 'bar' - testStorage('legacy').enterWith(store) + testStorage.enterWith(store) testStorage2.enterWith(store2) setImmediate(() => { - expect(testStorage('legacy').getStore()).to.equal(store) + expect(testStorage.getStore()).to.equal(store) expect(testStorage2.getStore()).to.equal(store2) done() }) @@ -52,7 +52,7 @@ describe('storage', () => { it('should not have its store referenced by the underlying async resource', () => { const resource = executionAsyncResource() - testStorage('legacy').enterWith({ internal: 'internal' }) + testStorage.enterWith({ internal: 'internal' }) for (const sym of Object.getOwnPropertySymbols(resource)) { if (sym.toString() === 'Symbol(kResourceStore)' && resource[sym]) { diff --git a/packages/datadog-instrumentations/src/cassandra-driver.js b/packages/datadog-instrumentations/src/cassandra-driver.js index 9d134721bc6..8b502300925 100644 --- a/packages/datadog-instrumentations/src/cassandra-driver.js +++ b/packages/datadog-instrumentations/src/cassandra-driver.js @@ -34,13 +34,12 @@ addHook({ name: 'cassandra-driver', versions: ['>=3.0.0'] }, cassandra => { const res = batch.apply(this, arguments) if (typeof res === 'function' || !res) { return wrapCallback(finishCh, errorCh, asyncResource, res) - } else { - const promiseAsyncResource = new AsyncResource('bound-anonymous-fn') - return res.then( - promiseAsyncResource.bind(() => finish(finishCh, errorCh)), - promiseAsyncResource.bind(err => finish(finishCh, errorCh, err)) - ) } + const promiseAsyncResource = new AsyncResource('bound-anonymous-fn') + return res.then( + promiseAsyncResource.bind(() => finish(finishCh, errorCh)), + promiseAsyncResource.bind(err => finish(finishCh, errorCh, err)) + ) } catch (e) { finish(finishCh, errorCh, e) throw e diff --git a/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js b/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js index a876bf7ba44..8346d66574b 100644 --- a/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +++ b/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js @@ -379,10 +379,9 @@ function wrapKafkaCallback (callback, { startCh, commitCh, finishCh, errorCh }, finishCh.publish(ctx) throw err }) - } else { - finishCh.publish(ctx) - return result } + finishCh.publish(ctx) + return result } catch (error) { ctx.error = error errorCh.publish(ctx) diff --git a/packages/datadog-instrumentations/src/cookie-parser.js b/packages/datadog-instrumentations/src/cookie-parser.js index 09b3e18b71f..dadf649ae05 100644 --- a/packages/datadog-instrumentations/src/cookie-parser.js +++ b/packages/datadog-instrumentations/src/cookie-parser.js @@ -10,7 +10,7 @@ function publishRequestCookieAndNext (req, res, next) { if (cookieParserReadCh.hasSubscribers && req) { const abortController = new AbortController() - const mergedCookies = Object.assign({}, req.cookies, req.signedCookies) + const mergedCookies = { ...req.cookies, ...req.signedCookies } cookieParserReadCh.publish({ req, res, abortController, cookies: mergedCookies }) diff --git a/packages/datadog-instrumentations/src/couchbase.js b/packages/datadog-instrumentations/src/couchbase.js index 7319d235339..f4024883146 100644 --- a/packages/datadog-instrumentations/src/couchbase.js +++ b/packages/datadog-instrumentations/src/couchbase.js @@ -43,14 +43,11 @@ function wrapMaybeInvoke (_maybeInvoke) { function wrapQuery (query) { const wrapped = function (q, params, callback) { - callback = AsyncResource.bind(arguments[arguments.length - 1]) - - if (typeof callback === 'function') { - arguments[arguments.length - 1] = callback + if (typeof arguments[arguments.length - 1] === 'function') { + arguments[arguments.length - 1] = AsyncResource.bind(arguments[arguments.length - 1]) } - const res = query.apply(this, arguments) - return res + return query.apply(this, arguments) } return wrapped } diff --git a/packages/datadog-instrumentations/src/cucumber.js b/packages/datadog-instrumentations/src/cucumber.js index 8d594434bd0..6a0a250e4b3 100644 --- a/packages/datadog-instrumentations/src/cucumber.js +++ b/packages/datadog-instrumentations/src/cucumber.js @@ -1,9 +1,10 @@ 'use strict' const { createCoverageMap } = require('istanbul-lib-coverage') -const { addHook, channel, AsyncResource } = require('./helpers/instrument') +const { addHook, channel } = require('./helpers/instrument') const shimmer = require('../../datadog-shimmer') const log = require('../../dd-trace/src/log') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') const testStartCh = channel('ci:cucumber:test:start') const testRetryCh = channel('ci:cucumber:test:retry') @@ -63,8 +64,6 @@ let eventDataCollector = null let pickleByFile = {} const pickleResultByFile = {} -const sessionAsyncResource = new AsyncResource('bound-anonymous-fn') - let skippableSuites = [] let itrCorrelationId = '' let isForcedToRun = false @@ -160,9 +159,7 @@ function getErrorFromCucumberResult (cucumberResult) { function getChannelPromise (channelToPublishTo, isParallel = false) { return new Promise(resolve => { - sessionAsyncResource.runInAsyncScope(() => { - channelToPublishTo.publish({ onDone: resolve, isParallel }) - }) + channelToPublishTo.publish({ onDone: resolve, isParallel }) }) } @@ -247,7 +244,7 @@ function wrapRun (pl, isLatestVersion) { testName: this.pickle.name, testFileAbsolutePath, testSourceLine, - isParallel: !!process.env.CUCUMBER_WORKER_ID + isParallel: !!getEnvironmentVariable('CUCUMBER_WORKER_ID') } const ctx = testStartPayload numAttemptToCtx.set(numAttempt, ctx) @@ -540,15 +537,13 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin } const processArgv = process.argv.slice(2).join(' ') - const command = process.env.npm_lifecycle_script || `cucumber-js ${processArgv}` + const command = getEnvironmentVariable('npm_lifecycle_script') || `cucumber-js ${processArgv}` if (isFlakyTestRetriesEnabled && !options.retry && numTestRetries > 0) { options.retry = numTestRetries } - sessionAsyncResource.runInAsyncScope(() => { - sessionStartCh.publish({ command, frameworkVersion }) - }) + sessionStartCh.publish({ command, frameworkVersion }) if (!errorSkippableRequest && skippedSuites.length) { itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion }) @@ -576,19 +571,17 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin global.__coverage__ = fromCoverageMapToCoverage(originalCoverageMap) } - sessionAsyncResource.runInAsyncScope(() => { - sessionFinishCh.publish({ - status: success ? 'pass' : 'fail', - isSuitesSkipped, - testCodeCoverageLinesTotal, - numSkippedSuites: skippedSuites.length, - hasUnskippableSuites: isUnskippable, - hasForcedToRunSuites: isForcedToRun, - isEarlyFlakeDetectionEnabled, - isEarlyFlakeDetectionFaulty, - isTestManagementTestsEnabled, - isParallel - }) + sessionFinishCh.publish({ + status: success ? 'pass' : 'fail', + isSuitesSkipped, + testCodeCoverageLinesTotal, + numSkippedSuites: skippedSuites.length, + hasUnskippableSuites: isUnskippable, + hasForcedToRunSuites: isForcedToRun, + isEarlyFlakeDetectionEnabled, + isEarlyFlakeDetectionFaulty, + isTestManagementTestsEnabled, + isParallel }) eventDataCollector = null return success @@ -676,6 +669,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa if (isAttemptToFix && lastTestStatus !== 'skip') { for (let retryIndex = 0; retryIndex < testManagementAttemptToFixRetries; retryIndex++) { numRetriesByPickleId.set(pickle.id, retryIndex + 1) + // eslint-disable-next-line no-await-in-loop runTestCaseResult = await runTestCaseFunction.apply(this, arguments) } } @@ -684,6 +678,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa if (isEarlyFlakeDetectionEnabled && lastTestStatus !== 'skip' && (isNew || isModified)) { for (let retryIndex = 0; retryIndex < earlyFlakeDetectionNumRetries; retryIndex++) { numRetriesByPickleId.set(pickle.id, retryIndex + 1) + // eslint-disable-next-line no-await-in-loop runTestCaseResult = await runTestCaseFunction.apply(this, arguments) } } @@ -758,9 +753,7 @@ function getWrappedParseWorkerMessage (parseWorkerMessageFunction, isNewVersion) if (Array.isArray(message)) { const [messageCode, payload] = message if (messageCode === CUCUMBER_WORKER_TRACE_PAYLOAD_CODE) { - sessionAsyncResource.runInAsyncScope(() => { - workerReportTraceCh.publish(payload) - }) + workerReportTraceCh.publish(payload) return } } @@ -932,7 +925,7 @@ addHook({ shimmer.wrap( workerPackage.Worker.prototype, 'runTestCase', - runTestCase => getWrappedRunTestCase(runTestCase, true, !!process.env.CUCUMBER_WORKER_ID) + runTestCase => getWrappedRunTestCase(runTestCase, true, !!getEnvironmentVariable('CUCUMBER_WORKER_ID')) ) return workerPackage }) diff --git a/packages/datadog-instrumentations/src/dns.js b/packages/datadog-instrumentations/src/dns.js index 5050495a4d0..ac37784c58d 100644 --- a/packages/datadog-instrumentations/src/dns.js +++ b/packages/datadog-instrumentations/src/dns.js @@ -39,12 +39,12 @@ addHook({ name: names }, dns => { }) function patchResolveShorthands (prototype) { - Object.keys(rrtypes) - .filter(method => !!prototype[method]) - .forEach(method => { + for (const method of Object.keys(rrtypes)) { + if (prototype[method]) { rrtypeMap.set(prototype[method], rrtypes[method]) shimmer.wrap(prototype, method, fn => wrap('apm:dns:resolve', fn, 2, rrtypes[method])) - }) + } + } } function wrap (prefix, fn, expectedArgs, rrtype) { diff --git a/packages/datadog-instrumentations/src/elasticsearch.js b/packages/datadog-instrumentations/src/elasticsearch.js index ef38cd8dd27..6bdc64b65e8 100644 --- a/packages/datadog-instrumentations/src/elasticsearch.js +++ b/packages/datadog-instrumentations/src/elasticsearch.js @@ -91,18 +91,17 @@ function createWrapRequest (name) { return cb.apply(null, arguments) })) return request.apply(this, arguments) + } + const promise = request.apply(this, arguments) + if (promise && typeof promise.then === 'function') { + const onResolve = asyncResource.bind(() => finish(params)) + const onReject = asyncResource.bind(e => finish(params, e)) + + promise.then(onResolve, onReject) } else { - const promise = request.apply(this, arguments) - if (promise && typeof promise.then === 'function') { - const onResolve = asyncResource.bind(() => finish(params)) - const onReject = asyncResource.bind(e => finish(params, e)) - - promise.then(onResolve, onReject) - } else { - finish(params) - } - return promise + finish(params) } + return promise } catch (err) { err.stack // trigger getting the stack at the original throwing point errorCh.publish(err) diff --git a/packages/datadog-instrumentations/src/fastify.js b/packages/datadog-instrumentations/src/fastify.js index 726e8284f92..bf85eb75e9c 100644 --- a/packages/datadog-instrumentations/src/fastify.js +++ b/packages/datadog-instrumentations/src/fastify.js @@ -59,21 +59,19 @@ function wrapAddHook (addHook) { return parsingResource.runInAsyncScope(() => { return done.apply(this, arguments) }) - } else { - return done.apply(this, arguments) } + return done.apply(this, arguments) } return fn.apply(this, arguments) - } else { - const promise = fn.apply(this, arguments) - - if (promise && typeof promise.catch === 'function') { - return promise.catch(err => publishError(err, req)) - } + } + const promise = fn.apply(this, arguments) - return promise + if (promise && typeof promise.catch === 'function') { + return promise.catch(err => publishError(err, req)) } + + return promise } catch (e) { throw publishError(e, req) } diff --git a/packages/datadog-instrumentations/src/google-cloud-pubsub.js b/packages/datadog-instrumentations/src/google-cloud-pubsub.js index b62e2b97e78..e8009479a0f 100644 --- a/packages/datadog-instrumentations/src/google-cloud-pubsub.js +++ b/packages/datadog-instrumentations/src/google-cloud-pubsub.js @@ -79,21 +79,20 @@ function wrapMethod (method) { }) return method.apply(this, arguments) - } else { - return method.apply(this, arguments) - .then( - response => { - requestFinishCh.publish(ctx) - return response - }, - error => { - ctx.error = error - requestErrorCh.publish(ctx) - requestFinishCh.publish(ctx) - throw error - } - ) } + return method.apply(this, arguments) + .then( + response => { + requestFinishCh.publish(ctx) + return response + }, + error => { + ctx.error = error + requestErrorCh.publish(ctx) + requestFinishCh.publish(ctx) + throw error + } + ) }) } } @@ -133,9 +132,8 @@ addHook({ name: '@google-cloud/pubsub', versions: ['>=1.2'], file: 'build/src/le if (receiveStartCh.hasSubscribers) { ctx.message = message return receiveStartCh.runStores(ctx, dispense, this, ...arguments) - } else { - return dispense.apply(this, arguments) } + return dispense.apply(this, arguments) }) shimmer.wrap(LeaseManager.prototype, 'remove', remove => function (message) { diff --git a/packages/datadog-instrumentations/src/hapi.js b/packages/datadog-instrumentations/src/hapi.js index afecf4c3284..a7f04a6e470 100644 --- a/packages/datadog-instrumentations/src/hapi.js +++ b/packages/datadog-instrumentations/src/hapi.js @@ -42,7 +42,10 @@ function wrapExt (ext) { if (events !== null && typeof events === 'object') { arguments[0] = wrapEvents(events) } else { - arguments[1] = wrapExtension(method) + // The method should never be an array. The check is done as a safe guard + // during a refactoring where it was unclear if this would be possible or + // not. + arguments[1] = Array.isArray(method) ? method.map(wrapHandler) : [wrapHandler(method)] } return ext.apply(this, arguments) @@ -75,17 +78,13 @@ function wrapRebuild (rebuild) { } } -function wrapExtension (method) { - return [].concat(method).map(wrapHandler) -} +function wrapEvents (events, flat = false) { + const eventsArray = Array.isArray(events) ? events : [events] -function wrapEvents (events) { - return [].concat(events).map(event => { - if (!event || !event.method) return event + return eventsArray.map(event => { + if (!event?.method) return event - return Object.assign({}, event, { - method: wrapExtension(event.method) - }) + return { ...event, method: wrapHandler(event.method) } }) } @@ -93,7 +92,7 @@ function wrapHandler (handler) { if (typeof handler !== 'function') return handler return shimmer.wrapFunction(handler, handler => function (request, h) { - const req = request && request.raw && request.raw.req + const req = request?.raw?.req if (!req) return handler.apply(this, arguments) diff --git a/packages/datadog-instrumentations/src/helpers/fetch.js b/packages/datadog-instrumentations/src/helpers/fetch.js index b11cf8ce255..7a18bc6f130 100644 --- a/packages/datadog-instrumentations/src/helpers/fetch.js +++ b/packages/datadog-instrumentations/src/helpers/fetch.js @@ -16,12 +16,11 @@ exports.createWrapFetch = function createWrapFetch (Request, ch, onLoad) { const ctx = { req: input } return ch.tracePromise(() => fetch.call(this, input, init), ctx) - } else { - const req = new Request(input, init) - const ctx = { req } - - return ch.tracePromise(() => fetch.call(this, req), ctx) } + const req = new Request(input, init) + const ctx = { req } + + return ch.tracePromise(() => fetch.call(this, req), ctx) } } } diff --git a/packages/datadog-instrumentations/src/helpers/hook.js b/packages/datadog-instrumentations/src/helpers/hook.js index 7905a92eeb6..5ca81bb5408 100644 --- a/packages/datadog-instrumentations/src/helpers/hook.js +++ b/packages/datadog-instrumentations/src/helpers/hook.js @@ -41,9 +41,8 @@ function Hook (modules, hookOptions, onrequire) { if (moduleExports && moduleExports.default) { moduleExports.default = safeHook(moduleExports.default, moduleName, moduleBaseDir) return moduleExports - } else { - return safeHook(moduleExports, moduleName, moduleBaseDir) } + return safeHook(moduleExports, moduleName, moduleBaseDir) }) } diff --git a/packages/datadog-instrumentations/src/helpers/register.js b/packages/datadog-instrumentations/src/helpers/register.js index dc64740313e..735b97b8782 100644 --- a/packages/datadog-instrumentations/src/helpers/register.js +++ b/packages/datadog-instrumentations/src/helpers/register.js @@ -9,11 +9,14 @@ const log = require('../../../dd-trace/src/log') const checkRequireCache = require('./check-require-cache') const telemetry = require('../../../dd-trace/src/guardrails/telemetry') const { isInServerlessEnvironment } = require('../../../dd-trace/src/serverless') +const { getEnvironmentVariables } = require('../../../dd-trace/src/config-helper') + +const envs = getEnvironmentVariables() const { DD_TRACE_DISABLED_INSTRUMENTATIONS = '', DD_TRACE_DEBUG = '' -} = process.env +} = envs const hooks = require('./hooks') const instrumentations = require('./instrumentations') @@ -24,7 +27,7 @@ const disabledInstrumentations = new Set( ) // Check for DD_TRACE__ENABLED environment variables -for (const [key, value] of Object.entries(process.env)) { +for (const [key, value] of Object.entries(envs)) { const match = key.match(/^DD_TRACE_(.+)_ENABLED$/) if (match && (value?.toLowerCase() === 'false' || value === '0')) { const integration = match[1].toLowerCase() @@ -96,9 +99,7 @@ for (const packageName of names) { // That way it would also not be duplicated. The actual name being used has to be identified else wise. // Maybe it is also not important to know what name was actually used? hook[HOOK_SYMBOL] ??= new WeakSet() - let matchesFile = false - - matchesFile = moduleName === fullFilename + let matchesFile = moduleName === fullFilename if (fullFilePattern) { // Some libraries include a hash in their filenames when installed, diff --git a/packages/datadog-instrumentations/src/jest.js b/packages/datadog-instrumentations/src/jest.js index 35da6bb0380..6d809135014 100644 --- a/packages/datadog-instrumentations/src/jest.js +++ b/packages/datadog-instrumentations/src/jest.js @@ -1,6 +1,6 @@ 'use strict' -const { addHook, channel, AsyncResource } = require('./helpers/instrument') +const { addHook, channel } = require('./helpers/instrument') const shimmer = require('../../datadog-shimmer') const log = require('../../dd-trace/src/log') const { @@ -83,8 +83,6 @@ let testManagementAttemptToFixRetries = 0 let isImpactedTestsEnabled = false let modifiedTests = {} -const sessionAsyncResource = new AsyncResource('bound-anonymous-fn') - const testContexts = new WeakMap() const originalTestFns = new WeakMap() const originalHookFns = new WeakMap() @@ -581,19 +579,16 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) { } } if (event.name === 'test_skip' || event.name === 'test_todo') { - const asyncResource = new AsyncResource('bound-anonymous-fn') - asyncResource.runInAsyncScope(() => { - testSkippedCh.publish({ - test: { - name: getJestTestName(event.test), - suite: this.testSuite, - testSourceFile: this.testSourceFile, - displayName: this.displayName, - frameworkVersion: jestVersion, - testStartLine: getTestLineStart(event.test.asyncError, this.testSuite) - }, - isDisabled: this.testManagementTestsForThisSuite?.disabled?.includes(getJestTestName(event.test)) - }) + testSkippedCh.publish({ + test: { + name: getJestTestName(event.test), + suite: this.testSuite, + testSourceFile: this.testSourceFile, + displayName: this.displayName, + frameworkVersion: jestVersion, + testStartLine: getTestLineStart(event.test.asyncError, this.testSuite) + }, + isDisabled: this.testManagementTestsForThisSuite?.disabled?.includes(getJestTestName(event.test)) }) } } @@ -711,9 +706,7 @@ function cliWrapper (cli, jestVersion) { return runCLI.apply(this, arguments) } - sessionAsyncResource.runInAsyncScope(() => { - libraryConfigurationCh.publish({ onDone }) - }) + libraryConfigurationCh.publish({ onDone }) try { const { err, libraryConfig } = await configurationPromise @@ -737,9 +730,7 @@ function cliWrapper (cli, jestVersion) { onDone = resolve }) - sessionAsyncResource.runInAsyncScope(() => { - knownTestsCh.publish({ onDone }) - }) + knownTestsCh.publish({ onDone }) try { const { err, knownTests: receivedKnownTests } = await knownTestsPromise @@ -760,9 +751,7 @@ function cliWrapper (cli, jestVersion) { onDone = resolve }) - sessionAsyncResource.runInAsyncScope(() => { - skippableSuitesCh.publish({ onDone }) - }) + skippableSuitesCh.publish({ onDone }) try { const { err, skippableSuites: receivedSkippableSuites } = await skippableSuitesPromise @@ -779,9 +768,7 @@ function cliWrapper (cli, jestVersion) { onDone = resolve }) - sessionAsyncResource.runInAsyncScope(() => { - testManagementTestsCh.publish({ onDone }) - }) + testManagementTestsCh.publish({ onDone }) try { const { err, testManagementTests: receivedTestManagementTests } = await testManagementTestsPromise @@ -798,9 +785,7 @@ function cliWrapper (cli, jestVersion) { onDone = resolve }) - sessionAsyncResource.runInAsyncScope(() => { - impactedTestsCh.publish({ onDone }) - }) + impactedTestsCh.publish({ onDone }) try { const { err, modifiedTests: receivedModifiedTests } = await impactedTestsPromise @@ -813,9 +798,7 @@ function cliWrapper (cli, jestVersion) { } const processArgv = process.argv.slice(2).join(' ') - sessionAsyncResource.runInAsyncScope(() => { - testSessionStartCh.publish({ command: `jest ${processArgv}`, frameworkVersion: jestVersion }) - }) + testSessionStartCh.publish({ command: `jest ${processArgv}`, frameworkVersion: jestVersion }) const result = await runCLI.apply(this, arguments) @@ -864,23 +847,22 @@ function cliWrapper (cli, jestVersion) { }, FLUSH_TIMEOUT).unref() }) - sessionAsyncResource.runInAsyncScope(() => { - testSessionFinishCh.publish({ - status, - isSuitesSkipped, - isSuitesSkippingEnabled, - isCodeCoverageEnabled, - testCodeCoverageLinesTotal, - numSkippedSuites, - hasUnskippableSuites, - hasForcedToRunSuites, - error, - isEarlyFlakeDetectionEnabled, - isEarlyFlakeDetectionFaulty, - isTestManagementTestsEnabled, - onDone - }) + testSessionFinishCh.publish({ + status, + isSuitesSkipped, + isSuitesSkippingEnabled, + isCodeCoverageEnabled, + testCodeCoverageLinesTotal, + numSkippedSuites, + hasUnskippableSuites, + hasForcedToRunSuites, + error, + isEarlyFlakeDetectionEnabled, + isEarlyFlakeDetectionFaulty, + isTestManagementTestsEnabled, + onDone }) + const waitingResult = await Promise.race([flushPromise, timeoutPromise]) if (waitingResult === 'timeout') { @@ -1006,45 +988,40 @@ function jestAdapterWrapper (jestAdapter, jestVersion) { if (!environment) { return adapter.apply(this, arguments) } - const asyncResource = new AsyncResource('bound-anonymous-fn') - return asyncResource.runInAsyncScope(() => { - testSuiteStartCh.publish({ - testSuite: environment.testSuite, - testEnvironmentOptions: environment.testEnvironmentOptions, - testSourceFile: environment.testSourceFile, - displayName: environment.displayName, - frameworkVersion: jestVersion - }) - return adapter.apply(this, arguments).then(suiteResults => { - const { numFailingTests, skipped, failureMessage: errorMessage } = suiteResults - let status = 'pass' - if (skipped) { - status = 'skipped' - } else if (numFailingTests !== 0) { - status = 'fail' - } + testSuiteStartCh.publish({ + testSuite: environment.testSuite, + testEnvironmentOptions: environment.testEnvironmentOptions, + testSourceFile: environment.testSourceFile, + displayName: environment.displayName, + frameworkVersion: jestVersion + }) + return adapter.apply(this, arguments).then(suiteResults => { + const { numFailingTests, skipped, failureMessage: errorMessage } = suiteResults + let status = 'pass' + if (skipped) { + status = 'skipped' + } else if (numFailingTests !== 0) { + status = 'fail' + } - /** - * Child processes do not each request ITR configuration, so the jest's parent process - * needs to pass them the configuration. This is done via _ddTestCodeCoverageEnabled, which - * controls whether coverage is reported. - */ - if (environment.testEnvironmentOptions?._ddTestCodeCoverageEnabled) { - const root = environment.repositoryRoot || environment.rootDir + /** + * Child processes do not each request ITR configuration, so the jest's parent process + * needs to pass them the configuration. This is done via _ddTestCodeCoverageEnabled, which + * controls whether coverage is reported. + */ + if (environment.testEnvironmentOptions?._ddTestCodeCoverageEnabled) { + const root = environment.repositoryRoot || environment.rootDir - const coverageFiles = getCoveredFilenamesFromCoverage(environment.global.__coverage__) - .map(filename => getTestSuitePath(filename, root)) + const coverageFiles = getCoveredFilenamesFromCoverage(environment.global.__coverage__) + .map(filename => getTestSuitePath(filename, root)) - asyncResource.runInAsyncScope(() => { - testSuiteCodeCoverageCh.publish({ coverageFiles, testSuite: environment.testSourceFile }) - }) - } - testSuiteFinishCh.publish({ status, errorMessage }) - return suiteResults - }).catch(error => { - testSuiteFinishCh.publish({ status: 'fail', error }) - throw error - }) + testSuiteCodeCoverageCh.publish({ coverageFiles, testSuite: environment.testSourceFile }) + } + testSuiteFinishCh.publish({ status, errorMessage }) + return suiteResults + }).catch(error => { + testSuiteFinishCh.publish({ status: 'fail', error }) + throw error }) }) if (jestAdapter.default) { @@ -1064,9 +1041,7 @@ addHook({ function configureTestEnvironment (readConfigsResult) { const { configs } = readConfigsResult - sessionAsyncResource.runInAsyncScope(() => { - testSessionConfigurationCh.publish(configs.map(config => config.testEnvironmentOptions)) - }) + testSessionConfigurationCh.publish(configs.map(config => config.testEnvironmentOptions)) // We can't directly use isCodeCoverageEnabled when reporting coverage in `jestAdapterWrapper` // because `jestAdapterWrapper` runs in a different process. We have to go through `testEnvironmentOptions` configs.forEach(config => { @@ -1315,21 +1290,15 @@ addHook({ shimmer.wrap(ChildProcessWorker.prototype, '_onMessage', _onMessage => function () { const [code, data] = arguments[0] if (code === JEST_WORKER_TRACE_PAYLOAD_CODE) { // datadog trace payload - sessionAsyncResource.runInAsyncScope(() => { - workerReportTraceCh.publish(data) - }) + workerReportTraceCh.publish(data) return } if (code === JEST_WORKER_COVERAGE_PAYLOAD_CODE) { // datadog coverage payload - sessionAsyncResource.runInAsyncScope(() => { - workerReportCoverageCh.publish(data) - }) + workerReportCoverageCh.publish(data) return } if (code === JEST_WORKER_LOGS_PAYLOAD_CODE) { // datadog logs payload - sessionAsyncResource.runInAsyncScope(() => { - workerReportLogsCh.publish(data) - }) + workerReportLogsCh.publish(data) return } return _onMessage.apply(this, arguments) diff --git a/packages/datadog-instrumentations/src/koa.js b/packages/datadog-instrumentations/src/koa.js index f139eb1c494..4308a78f9fe 100644 --- a/packages/datadog-instrumentations/src/koa.js +++ b/packages/datadog-instrumentations/src/koa.js @@ -112,10 +112,9 @@ function wrapMiddleware (fn, layer) { throw err } ) - } else { - fulfill(ctx) - return result } + fulfill(ctx) + return result } catch (e) { fulfill(ctx, e) throw e diff --git a/packages/datadog-instrumentations/src/mariadb.js b/packages/datadog-instrumentations/src/mariadb.js index 8fae49d76ef..bcc6e8f0172 100644 --- a/packages/datadog-instrumentations/src/mariadb.js +++ b/packages/datadog-instrumentations/src/mariadb.js @@ -1,10 +1,12 @@ 'use strict' -const { channel, addHook, AsyncResource } = require('./helpers/instrument') +const { channel, addHook } = require('./helpers/instrument') const shimmer = require('../../datadog-shimmer') const commandAddCh = channel('apm:mariadb:command:add') +const connectionStartCh = channel('apm:mariadb:connection:start') +const connectionFinishCh = channel('apm:mariadb:connection:finish') const startCh = channel('apm:mariadb:query:start') const finishCh = channel('apm:mariadb:query:finish') const errorCh = channel('apm:mariadb:query:error') @@ -82,7 +84,7 @@ function createWrapQueryCallback (options) { const ctx = { sql, conf: options } if (typeof cb !== 'function') { - arguments.length = arguments.length + 1 + arguments.length += 1 } arguments[arguments.length - 1] = shimmer.wrapFunction(cb, cb => function (err) { @@ -135,8 +137,13 @@ function wrapPoolGetConnectionMethod (getConnection) { const cb = arguments[arguments.length - 1] if (typeof cb !== 'function') return getConnection.apply(this, arguments) - const callbackResource = new AsyncResource('bound-anonymous-fn') - arguments[arguments.length - 1] = callbackResource.bind(cb) + const ctx = {} + + arguments[arguments.length - 1] = function () { + return connectionFinishCh.runStores(ctx, cb, this, ...arguments) + } + + connectionStartCh.publish(ctx) return getConnection.apply(this, arguments) } diff --git a/packages/datadog-instrumentations/src/mocha.js b/packages/datadog-instrumentations/src/mocha.js index fec27587bc7..5449d769b03 100644 --- a/packages/datadog-instrumentations/src/mocha.js +++ b/packages/datadog-instrumentations/src/mocha.js @@ -1,4 +1,6 @@ -if (process.env.MOCHA_WORKER_ID) { +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') + +if (getEnvironmentVariable('MOCHA_WORKER_ID')) { require('./mocha/worker') } else { require('./mocha/main') diff --git a/packages/datadog-instrumentations/src/mocha/main.js b/packages/datadog-instrumentations/src/mocha/main.js index 7202499df7f..f9a74f4c54b 100644 --- a/packages/datadog-instrumentations/src/mocha/main.js +++ b/packages/datadog-instrumentations/src/mocha/main.js @@ -1,10 +1,11 @@ 'use strict' const { createCoverageMap } = require('istanbul-lib-coverage') -const { addHook, channel, AsyncResource } = require('../helpers/instrument') +const { addHook, channel } = require('../helpers/instrument') const shimmer = require('../../../datadog-shimmer') const { isMarkedAsUnskippable } = require('../../../datadog-plugin-jest/src/util') const log = require('../../../dd-trace/src/log') +const { getEnvironmentVariable } = require('../../../dd-trace/src/config-helper') const { getTestSuitePath, MOCHA_WORKER_TRACE_PAYLOAD_CODE, @@ -37,7 +38,6 @@ const { require('./common') -const testSessionAsyncResource = new AsyncResource('bound-anonymous-fn') const patched = new WeakSet() const unskippableSuites = [] @@ -65,6 +65,8 @@ const testSuiteCodeCoverageCh = channel('ci:mocha:test-suite:code-coverage') const libraryConfigurationCh = channel('ci:mocha:library-configuration') const knownTestsCh = channel('ci:mocha:known-tests') const skippableSuitesCh = channel('ci:mocha:test-suite:skippable') +const mochaGlobalRunCh = channel('ci:mocha:global:run') + const testManagementTestsCh = channel('ci:mocha:test-management-tests') const impactedTestsCh = channel('ci:mocha:modified-tests') const workerReportTraceCh = channel('ci:mocha:worker-report:trace') @@ -100,18 +102,18 @@ function getFilteredSuites (originalSuites) { } function getOnStartHandler (isParallel, frameworkVersion) { - return testSessionAsyncResource.bind(function () { + return function () { const processArgv = process.argv.slice(2).join(' ') const command = `mocha ${processArgv}` testSessionStartCh.publish({ command, frameworkVersion }) if (!isParallel && skippedSuites.length) { itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion }) } - }) + } } function getOnEndHandler (isParallel) { - return testSessionAsyncResource.bind(function () { + return function () { let status = 'pass' let error if (this.stats) { @@ -196,47 +198,12 @@ function getOnEndHandler (isParallel) { isTestManagementEnabled: config.isTestManagementTestsEnabled, isParallel }) - }) + } } function getExecutionConfiguration (runner, isParallel, onFinishRequest) { - const mochaRunAsyncResource = new AsyncResource('bound-anonymous-fn') - - const onReceivedTestManagementTests = ({ err, testManagementTests: receivedTestManagementTests }) => { - if (err) { - config.testManagementTests = {} - config.isTestManagementTestsEnabled = false - config.testManagementAttemptToFixRetries = 0 - } else { - config.testManagementTests = receivedTestManagementTests - } - if (config.isImpactedTestsEnabled) { - impactedTestsCh.publish({ - onDone: mochaRunAsyncResource.bind(onReceivedImpactedTests) - }) - } else if (config.isSuitesSkippingEnabled) { - skippableSuitesCh.publish({ - onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites) - }) - } else { - onFinishRequest() - } - } - - const onReceivedImpactedTests = ({ err, modifiedTests: receivedModifiedTests }) => { - if (err) { - config.modifiedTests = [] - config.isImpactedTestsEnabled = false - } else { - config.modifiedTests = receivedModifiedTests - } - if (config.isSuitesSkippingEnabled) { - skippableSuitesCh.publish({ - onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites) - }) - } else { - onFinishRequest() - } + const ctx = { + isParallel } const onReceivedSkippableSuites = ({ err, skippableSuites, itrCorrelationId: responseItrCorrelationId }) => { @@ -260,7 +227,47 @@ function getExecutionConfiguration (runner, isParallel, onFinishRequest) { skippedSuites = [...filteredSuites.skippedSuites] - onFinishRequest() + mochaGlobalRunCh.runStores(ctx, () => { + onFinishRequest() + }) + } + + const onReceivedImpactedTests = ({ err, modifiedTests: receivedModifiedTests }) => { + if (err) { + config.modifiedTests = [] + config.isImpactedTestsEnabled = false + } else { + config.modifiedTests = receivedModifiedTests + } + if (config.isSuitesSkippingEnabled) { + ctx.onDone = onReceivedSkippableSuites + skippableSuitesCh.runStores(ctx, () => {}) + } else { + mochaGlobalRunCh.runStores(ctx, () => { + onFinishRequest() + }) + } + } + + const onReceivedTestManagementTests = ({ err, testManagementTests: receivedTestManagementTests }) => { + if (err) { + config.testManagementTests = {} + config.isTestManagementTestsEnabled = false + config.testManagementAttemptToFixRetries = 0 + } else { + config.testManagementTests = receivedTestManagementTests + } + if (config.isImpactedTestsEnabled) { + ctx.onDone = onReceivedImpactedTests + impactedTestsCh.runStores(ctx, () => {}) + } else if (config.isSuitesSkippingEnabled) { + ctx.onDone = onReceivedSkippableSuites + skippableSuitesCh.runStores(ctx, () => {}) + } else { + mochaGlobalRunCh.runStores(ctx, () => { + onFinishRequest() + }) + } } const onReceivedKnownTests = ({ err, knownTests }) => { @@ -272,27 +279,27 @@ function getExecutionConfiguration (runner, isParallel, onFinishRequest) { config.knownTests = knownTests } if (config.isTestManagementTestsEnabled) { - testManagementTestsCh.publish({ - onDone: mochaRunAsyncResource.bind(onReceivedTestManagementTests) - }) + ctx.onDone = onReceivedTestManagementTests + testManagementTestsCh.runStores(ctx, () => {}) } if (config.isImpactedTestsEnabled) { - impactedTestsCh.publish({ - onDone: mochaRunAsyncResource.bind(onReceivedImpactedTests) - }) + ctx.onDone = onReceivedImpactedTests + impactedTestsCh.runStores(ctx, () => {}) } else if (config.isSuitesSkippingEnabled) { - skippableSuitesCh.publish({ - onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites) - }) + ctx.onDone = onReceivedSkippableSuites + skippableSuitesCh.runStores(ctx, () => {}) } else { - onFinishRequest() + mochaGlobalRunCh.runStores(ctx, () => { + onFinishRequest() + }) } } const onReceivedConfiguration = ({ err, libraryConfig }) => { if (err || !skippableSuitesCh.hasSubscribers || !knownTestsCh.hasSubscribers) { - return onFinishRequest() + return mochaGlobalRunCh.runStores(ctx, () => { + onFinishRequest() + }) } - config.isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled config.earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries config.earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold @@ -306,30 +313,27 @@ function getExecutionConfiguration (runner, isParallel, onFinishRequest) { config.flakyTestRetriesCount = !isParallel && libraryConfig.flakyTestRetriesCount if (config.isKnownTestsEnabled) { - knownTestsCh.publish({ - onDone: mochaRunAsyncResource.bind(onReceivedKnownTests) - }) + ctx.onDone = onReceivedKnownTests + knownTestsCh.runStores(ctx, () => {}) } else if (config.isTestManagementTestsEnabled) { - testManagementTestsCh.publish({ - onDone: mochaRunAsyncResource.bind(onReceivedTestManagementTests) - }) + ctx.onDone = onReceivedTestManagementTests + testManagementTestsCh.runStores(ctx, () => {}) } else if (config.isImpactedTestsEnabled) { - impactedTestsCh.publish({ - onDone: mochaRunAsyncResource.bind(onReceivedImpactedTests) - }) + ctx.onDone = onReceivedImpactedTests + impactedTestsCh.runStores(ctx, () => {}) } else if (config.isSuitesSkippingEnabled) { - skippableSuitesCh.publish({ - onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites) - }) + ctx.onDone = onReceivedSkippableSuites + skippableSuitesCh.runStores(ctx, () => {}) } else { - onFinishRequest() + mochaGlobalRunCh.runStores(ctx, () => { + onFinishRequest() + }) } } - libraryConfigurationCh.publish({ - onDone: mochaRunAsyncResource.bind(onReceivedConfiguration), - isParallel - }) + ctx.onDone = onReceivedConfiguration + + libraryConfigurationCh.runStores(ctx, () => {}) } // In this hook we delay the execution with options.delay to grab library configuration, @@ -342,7 +346,7 @@ addHook({ }, (Mocha) => { shimmer.wrap(Mocha.prototype, 'run', run => function () { // Workers do not need to request any data, just run the tests - if (!testFinishCh.hasSubscribers || process.env.MOCHA_WORKER_ID || this.options.parallel) { + if (!testFinishCh.hasSubscribers || getEnvironmentVariable('MOCHA_WORKER_ID') || this.options.parallel) { return run.apply(this, arguments) } @@ -517,7 +521,7 @@ addHook({ if (ctx) { testSuiteFinishCh.publish({ status, ...ctx.currentStore }, () => {}) } else { - log.warn(() => `No AsyncResource found for suite ${suite.file}`) + log.warn(() => `No ctx found for suite ${suite.file}`) } }) diff --git a/packages/datadog-instrumentations/src/mysql.js b/packages/datadog-instrumentations/src/mysql.js index 10eb4113e07..5710b81d34a 100644 --- a/packages/datadog-instrumentations/src/mysql.js +++ b/packages/datadog-instrumentations/src/mysql.js @@ -1,6 +1,6 @@ 'use strict' -const { channel, addHook, AsyncResource } = require('./helpers/instrument') +const { channel, addHook } = require('./helpers/instrument') const shimmer = require('../../datadog-shimmer') addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connection => { @@ -57,11 +57,20 @@ addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connect }) addHook({ name: 'mysql', file: 'lib/Pool.js', versions: ['>=2'] }, Pool => { + const connectionStartCh = channel('apm:mysql:connection:start') + const connectionFinishCh = channel('apm:mysql:connection:finish') const startPoolQueryCh = channel('datadog:mysql:pool:query:start') const finishPoolQueryCh = channel('datadog:mysql:pool:query:finish') shimmer.wrap(Pool.prototype, 'getConnection', getConnection => function (cb) { - arguments[0] = AsyncResource.bind(cb) + arguments[0] = function () { + return connectionFinishCh.runStores(ctx, cb, this, ...arguments) + } + + const ctx = {} + + connectionStartCh.publish(ctx) + return getConnection.apply(this, arguments) }) diff --git a/packages/datadog-instrumentations/src/nyc.js b/packages/datadog-instrumentations/src/nyc.js index 1f2bc92e160..93aa3ae1ad8 100644 --- a/packages/datadog-instrumentations/src/nyc.js +++ b/packages/datadog-instrumentations/src/nyc.js @@ -1,5 +1,6 @@ const { addHook, channel } = require('./helpers/instrument') const shimmer = require('../../datadog-shimmer') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') const codeCoverageWrapCh = channel('ci:nyc:wrap') @@ -11,7 +12,7 @@ addHook({ shimmer.wrap(nycPackage.prototype, 'wrap', wrap => function () { // Only relevant if the config `all` is set to true try { - if (JSON.parse(process.env.NYC_CONFIG).all) { + if (JSON.parse(getEnvironmentVariable('NYC_CONFIG')).all) { codeCoverageWrapCh.publish(this) } } catch { diff --git a/packages/datadog-instrumentations/src/openai.js b/packages/datadog-instrumentations/src/openai.js index 10f43076a82..060ccb35297 100644 --- a/packages/datadog-instrumentations/src/openai.js +++ b/packages/datadog-instrumentations/src/openai.js @@ -187,12 +187,12 @@ function addStreamedChunk (content, chunk) { } } -function convertBufferstoObjects (chunks = []) { +function convertBufferstoObjects (chunks) { return Buffer .concat(chunks) // combine the buffers .toString() // stringify .split(/(?=data:)/) // split on "data:" - .map(chunk => chunk.replace(/\n/g, '').slice(6)) // remove newlines and 'data: ' from the front + .map(chunk => chunk.replaceAll('\n', '').slice(6)) // remove newlines and 'data: ' from the front .slice(0, -1) // remove the last [DONE] message .map(JSON.parse) // parse all of the returned objects } diff --git a/packages/datadog-instrumentations/src/otel-sdk-trace.js b/packages/datadog-instrumentations/src/otel-sdk-trace.js index 4e0d5310a31..90c9f30f475 100644 --- a/packages/datadog-instrumentations/src/otel-sdk-trace.js +++ b/packages/datadog-instrumentations/src/otel-sdk-trace.js @@ -3,10 +3,11 @@ const { addHook } = require('./helpers/instrument') const shimmer = require('../../datadog-shimmer') const tracer = require('../../dd-trace') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') -const otelSdkEnabled = process.env.DD_TRACE_OTEL_ENABLED || -process.env.OTEL_SDK_DISABLED - ? !process.env.OTEL_SDK_DISABLED +const otelSdkEnabled = getEnvironmentVariable('DD_TRACE_OTEL_ENABLED') || +getEnvironmentVariable('OTEL_SDK_DISABLED') + ? !getEnvironmentVariable('OTEL_SDK_DISABLED') : undefined if (otelSdkEnabled) { diff --git a/packages/datadog-instrumentations/src/pg.js b/packages/datadog-instrumentations/src/pg.js index 2fc9b7abf26..c2cc51f1512 100644 --- a/packages/datadog-instrumentations/src/pg.js +++ b/packages/datadog-instrumentations/src/pg.js @@ -178,9 +178,8 @@ function wrapPoolQuery (query) { cb(error) return - } else { - return Promise.reject(error) } + return Promise.reject(error) } if (typeof cb === 'function') { @@ -192,7 +191,7 @@ function wrapPoolQuery (query) { const retval = query.apply(this, arguments) - if (retval && retval.then) { + if (retval?.then) { retval.then(() => { finish() }).catch(() => { diff --git a/packages/datadog-instrumentations/src/playwright.js b/packages/datadog-instrumentations/src/playwright.js index 131190cdac0..cd626f4e5ac 100644 --- a/packages/datadog-instrumentations/src/playwright.js +++ b/packages/datadog-instrumentations/src/playwright.js @@ -1,6 +1,6 @@ const satisfies = require('semifies') -const { addHook, channel, AsyncResource } = require('./helpers/instrument') +const { addHook, channel } = require('./helpers/instrument') const shimmer = require('../../datadog-shimmer') const { parseAnnotations, @@ -33,7 +33,6 @@ const testSuiteToCtx = new Map() const testSuiteToTestStatuses = new Map() const testSuiteToErrors = new Map() const testsToTestStatuses = new Map() -const testSessionAsyncResource = new AsyncResource('bound-anonymous-fn') let applyRepeatEachIndex = null @@ -109,7 +108,12 @@ function deepCloneSuite (suite, filterTest, tags = []) { function getTestsBySuiteFromTestGroups (testGroups) { return testGroups.reduce((acc, { requireFile, tests }) => { - acc[requireFile] = acc[requireFile] ? acc[requireFile].concat(tests) : tests + if (acc[requireFile]) { + acc[requireFile].push(...tests) + } else { + // Copy the tests, otherwise we modify the original tests + acc[requireFile] = [...tests] + } return acc }, {}) } @@ -238,9 +242,7 @@ function getTestByTestId (dispatcher, testId) { function getChannelPromise (channelToPublishTo, params) { return new Promise(resolve => { - testSessionAsyncResource.runInAsyncScope(() => { - channelToPublishTo.publish({ onDone: resolve, ...params }) - }) + channelToPublishTo.publish({ onDone: resolve, ...params }) }) } @@ -417,7 +419,7 @@ function dispatcherRunWrapperNew (run) { // Not available from >=1.44.0 this._ddAllTests = testGroups.flatMap(g => g.tests) } - remainingTestsByFile = getTestsBySuiteFromTestGroups(arguments[0]) + remainingTestsByFile = getTestsBySuiteFromTestGroups(testGroups) return run.apply(this, arguments) } } @@ -511,9 +513,7 @@ function runnerHook (runnerExport, playwrightVersion) { const processArgv = process.argv.slice(2).join(' ') const command = `playwright ${processArgv}` - testSessionAsyncResource.runInAsyncScope(() => { - testSessionStartCh.publish({ command, frameworkVersion: playwrightVersion, rootDir }) - }) + testSessionStartCh.publish({ command, frameworkVersion: playwrightVersion, rootDir }) try { const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh) @@ -630,13 +630,11 @@ function runnerHook (runnerExport, playwrightVersion) { const flushWait = new Promise(resolve => { onDone = resolve }) - testSessionAsyncResource.runInAsyncScope(() => { - testSessionFinishCh.publish({ - status: STATUS_TO_TEST_STATUS[sessionStatus], - isEarlyFlakeDetectionEnabled, - isTestManagementTestsEnabled, - onDone - }) + testSessionFinishCh.publish({ + status: STATUS_TO_TEST_STATUS[sessionStatus], + isEarlyFlakeDetectionEnabled, + isTestManagementTestsEnabled, + onDone }) await flushWait @@ -754,8 +752,7 @@ addHook({ } if (isImpactedTestsEnabled) { - for (const test of allTests) { - const isNew = isKnownTestsEnabled && isNewTest(test) + await Promise.all(allTests.map(async (test) => { const { isModified } = await getChannelPromise(isModifiedCh, { filePath: test._requireFile, modifiedTests @@ -764,6 +761,7 @@ addHook({ test._ddIsModified = true } if (isEarlyFlakeDetectionEnabled && test.expectedStatus !== 'skipped') { + const isNew = isKnownTestsEnabled && isNewTest(test) const fileSuite = getSuiteType(test, 'file') const projectSuite = getSuiteType(test, 'project') // If something change in the file, all tests in the file are impacted @@ -778,7 +776,7 @@ addHook({ projectSuite._addSuite(copyFileSuite) } } - } + })) } if (isKnownTestsEnabled) { @@ -939,9 +937,8 @@ addHook({ if (window.DD_RUM && window.DD_RUM.stopSession) { window.DD_RUM.stopSession() return true - } else { - return false } + return false }) if (isRumActive) { diff --git a/packages/datadog-instrumentations/src/protobufjs.js b/packages/datadog-instrumentations/src/protobufjs.js index 79cbb4ee3a1..44ff70dba77 100644 --- a/packages/datadog-instrumentations/src/protobufjs.js +++ b/packages/datadog-instrumentations/src/protobufjs.js @@ -102,11 +102,10 @@ addHook({ wrapProtobufClasses(root) return root }) - } else { - // If result is not a promise, directly wrap the protobuf classes - wrapProtobufClasses(result) - return result } + // If result is not a promise, directly wrap the protobuf classes + wrapProtobufClasses(result) + return result }) shimmer.wrap(protobuf.Root.prototype, 'loadSync', original => function () { diff --git a/packages/datadog-instrumentations/src/redis.js b/packages/datadog-instrumentations/src/redis.js index 8f7606d4265..d3be4b7209d 100644 --- a/packages/datadog-instrumentations/src/redis.js +++ b/packages/datadog-instrumentations/src/redis.js @@ -45,7 +45,7 @@ function wrapCommandQueueClass (cls) { try { const parsed = new URL(createClientUrl) if (parsed) { - this._url = { host: parsed.hostname, port: +parsed.port || 6379 } + this._url = { host: parsed.hostname, port: Number(parsed.port) || 6379 } } } catch { // ignore diff --git a/packages/datadog-instrumentations/src/restify.js b/packages/datadog-instrumentations/src/restify.js index 83fcec69667..fba76415781 100644 --- a/packages/datadog-instrumentations/src/restify.js +++ b/packages/datadog-instrumentations/src/restify.js @@ -20,25 +20,21 @@ function wrapSetupRequest (setupRequest) { } function wrapMethod (method) { - return function (path, ...rest) { - const middleware = wrapMiddleware(rest) + return function (path, ...middlewares) { + const wrappedMiddlewares = middlewares.map(wrapFn) - return method.apply(this, [path].concat(middleware)) + return method.apply(this, [path, ...wrappedMiddlewares]) } } function wrapHandler (method) { - return function () { - return method.apply(this, wrapMiddleware(arguments)) + return function (...middlewares) { + return method.apply(this, middlewares.map(wrapFn)) } } -function wrapMiddleware (middleware) { - return Array.prototype.map.call(middleware, wrapFn) -} - function wrapFn (fn) { - if (Array.isArray(fn)) return wrapMiddleware(fn) + if (Array.isArray(fn)) return fn.map(wrapFn) return shimmer.wrapFunction(fn, fn => function (req, res, next) { if (typeof next === 'function') { @@ -51,11 +47,11 @@ function wrapFn (fn) { try { const result = fn.apply(this, arguments) - if (result !== null && typeof result === 'object' && typeof result.then === 'function') { - return result.then(function () { + if (typeof result?.then === 'function') { + return result.then(function (result) { nextChannel.publish({ req }) finishChannel.publish({ req }) - return arguments[0] + return result }).catch(function (error) { errorChannel.publish({ req, error }) nextChannel.publish({ req }) diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index ef70c65ae08..39f9eda8747 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -13,10 +13,6 @@ function isFastSlash (layer, matchers) { return layer.regexp?.fast_slash ?? matchers.some(matcher => matcher.path === '/') } -function flatten (arr) { - return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val), []) -} - // TODO: Move this function to a shared file between Express and Router function createWrapRouterMethod (name) { const enterChannel = channel(`apm:${name}:middleware:enter`) @@ -74,8 +70,8 @@ function createWrapRouterMethod (name) { }) } - function wrapStack (stack, offset, matchers) { - [].concat(stack).slice(offset).forEach(layer => { + function wrapStack (layers, matchers) { + for (const layer of layers) { if (layer.__handle) { // express-async-errors layer.__handle = wrapLayerHandle(layer, layer.__handle) } else { @@ -93,7 +89,7 @@ function createWrapRouterMethod (name) { layer.route[method] = wrapMethod(layer.route[method]) }) } - }) + } } function wrapNext (req, next) { @@ -110,7 +106,7 @@ function createWrapRouterMethod (name) { } function extractMatchers (fn) { - const arg = flatten([].concat(fn)) + const arg = Array.isArray(fn) ? fn : [fn] if (typeof arg[0] === 'function') { return [] @@ -139,7 +135,10 @@ function createWrapRouterMethod (name) { function wrapMethod (original) { return shimmer.wrapFunction(original, original => function methodWithTrace (fn) { - const offset = this.stack ? [].concat(this.stack).length : 0 + let offset = 0 + if (this.stack) { + offset = Array.isArray(this.stack) ? this.stack.length : 1 + } const router = original.apply(this, arguments) if (typeof this.stack === 'function') { @@ -150,7 +149,9 @@ function createWrapRouterMethod (name) { routeAddedChannel.publish({ topOfStackFunc: methodWithTrace, layer: this.stack[0] }) } - wrapStack(this.stack, offset, extractMatchers(fn)) + if (this.stack.length > offset) { + wrapStack(this.stack.slice(offset), extractMatchers(fn)) + } return router }) @@ -204,7 +205,7 @@ const visitedParams = new WeakSet() function wrapHandleRequest (original) { return function wrappedHandleRequest (req, res, next) { - if (routerParamStartCh.hasSubscribers && Object.keys(req.params).length && !visitedParams.has(req.params)) { + if (routerParamStartCh.hasSubscribers && !visitedParams.has(req.params) && Object.keys(req.params).length) { visitedParams.add(req.params) const abortController = new AbortController() diff --git a/packages/datadog-instrumentations/src/tedious.js b/packages/datadog-instrumentations/src/tedious.js index 2c5e37ffb37..b363fad1b1a 100644 --- a/packages/datadog-instrumentations/src/tedious.js +++ b/packages/datadog-instrumentations/src/tedious.js @@ -63,7 +63,6 @@ function getQueryOrProcedure (request) { return [request.parametersByName.statement.value, request.parametersByName.statement, 'value'] } else if (request.parametersByName.stmt) { return [request.parametersByName.stmt.value, request.parametersByName.stmt, 'value'] - } else { - return [request.sqlTextOrProcedure, request, 'sqlTextOrProcedure'] } + return [request.sqlTextOrProcedure, request, 'sqlTextOrProcedure'] } diff --git a/packages/datadog-instrumentations/src/vitest.js b/packages/datadog-instrumentations/src/vitest.js index f9ed3d0112e..85a31735115 100644 --- a/packages/datadog-instrumentations/src/vitest.js +++ b/packages/datadog-instrumentations/src/vitest.js @@ -1,4 +1,4 @@ -const { addHook, channel, AsyncResource } = require('./helpers/instrument') +const { addHook, channel } = require('./helpers/instrument') const shimmer = require('../../datadog-shimmer') const log = require('../../dd-trace/src/log') @@ -38,7 +38,6 @@ const modifiedTasks = new WeakSet() let isRetryReasonEfd = false let isRetryReasonAttemptToFix = false const switchedStatuses = new WeakSet() -const sessionAsyncResource = new AsyncResource('bound-anonymous-fn') const BREAKPOINT_HIT_GRACE_PERIOD_MS = 400 @@ -116,9 +115,7 @@ function isBaseSequencer (vitestPackage) { function getChannelPromise (channelToPublishTo) { return new Promise(resolve => { - sessionAsyncResource.runInAsyncScope(() => { - channelToPublishTo.publish({ onDone: resolve }) - }) + channelToPublishTo.publish({ onDone: resolve }) }) } @@ -194,9 +191,6 @@ function getSortWrapper (sort) { let isImpactedTestsEnabled = false let testManagementAttemptToFixRetries = 0 let isDiEnabled = false - let knownTests = {} - let testManagementTests = {} - let modifiedTests = {} try { const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh) @@ -233,9 +227,8 @@ function getSortWrapper (sort) { const knownTestsResponse = await getChannelPromise(knownTestsCh) if (knownTestsResponse.err) { isEarlyFlakeDetectionEnabled = false - isKnownTestsEnabled = false } else { - knownTests = knownTestsResponse.knownTests + const knownTests = knownTestsResponse.knownTests const getFilePaths = this.ctx.getTestFilepaths || this.ctx._globTestFilepaths const testFilepaths = await getFilePaths.call(this.ctx) @@ -249,7 +242,6 @@ function getSortWrapper (sort) { }) if (isEarlyFlakeDetectionFaulty) { isEarlyFlakeDetectionEnabled = false - isKnownTestsEnabled = false log.warn('New test detection is disabled because the number of new tests is too high.') } else { // TODO: use this to pass session and module IDs to the worker, instead of polluting process.env @@ -282,7 +274,7 @@ function getSortWrapper (sort) { isTestManagementTestsEnabled = false log.error('Could not get test management tests.') } else { - testManagementTests = receivedTestManagementTests + const testManagementTests = receivedTestManagementTests try { const workspaceProject = this.ctx.getCoreWorkspaceProject() workspaceProject._provided._ddIsTestManagementTestsEnabled = isTestManagementTestsEnabled @@ -295,12 +287,10 @@ function getSortWrapper (sort) { } if (isImpactedTestsEnabled) { - const { err, modifiedTests: receivedModifiedTests } = await getChannelPromise(impactedTestsCh) + const { err, modifiedTests } = await getChannelPromise(impactedTestsCh) if (err) { - isImpactedTestsEnabled = false log.error('Could not get modified tests.') } else { - modifiedTests = receivedModifiedTests try { const workspaceProject = this.ctx.getCoreWorkspaceProject() workspaceProject._provided._ddIsImpactedTestsEnabled = isImpactedTestsEnabled @@ -338,16 +328,14 @@ function getSortWrapper (sort) { error = new Error(`Test suites failed: ${failedSuites.length}.`) } - sessionAsyncResource.runInAsyncScope(() => { - testSessionFinishCh.publish({ - status: getSessionStatus(this.state), - testCodeCoverageLinesTotal, - error, - isEarlyFlakeDetectionEnabled, - isEarlyFlakeDetectionFaulty, - isTestManagementTestsEnabled, - onFinish - }) + testSessionFinishCh.publish({ + status: getSessionStatus(this.state), + testCodeCoverageLinesTotal, + error, + isEarlyFlakeDetectionEnabled, + isEarlyFlakeDetectionFaulty, + isTestManagementTestsEnabled, + onFinish }) await flushPromise @@ -364,10 +352,8 @@ function getCreateCliWrapper (vitestPackage, frameworkVersion) { if (!testSessionStartCh.hasSubscribers) { return oldCreateCli.apply(this, arguments) } - sessionAsyncResource.runInAsyncScope(() => { - const processArgv = process.argv.slice(2).join(' ') - testSessionStartCh.publish({ command: `vitest ${processArgv}`, frameworkVersion }) - }) + const processArgv = process.argv.slice(2).join(' ') + testSessionStartCh.publish({ command: `vitest ${processArgv}`, frameworkVersion }) return oldCreateCli.apply(this, arguments) }) diff --git a/packages/datadog-plugin-avsc/src/schema_iterator.js b/packages/datadog-plugin-avsc/src/schema_iterator.js index 096b64631e9..c35ebf9604a 100644 --- a/packages/datadog-plugin-avsc/src/schema_iterator.js +++ b/packages/datadog-plugin-avsc/src/schema_iterator.js @@ -104,21 +104,21 @@ class SchemaExtractor { } } return true - } else { - if (!builder.shouldExtractSchema(schemaName, depth)) { - return false - } - if (schema.fields?.[Symbol.iterator]) { - for (const field of schema.fields) { - if (!this.extractProperty(field, schemaName, field.name, builder, depth)) { - log.warn('DSM: Unable to extract field with name: %s from Avro schema with name: %s', field.name, - schemaName) - } + } + if (!builder.shouldExtractSchema(schemaName, depth)) { + return false + } + if (schema.fields?.[Symbol.iterator]) { + for (const field of schema.fields) { + if (!this.extractProperty(field, schemaName, field.name, builder, depth)) { + log.warn('DSM: Unable to extract field with name: %s from Avro schema with name: %s', field.name, + schemaName) } - } else { - log.warn('DSM: schema.fields is not iterable from Avro schema with name: %s', schemaName) } + } else { + log.warn('DSM: schema.fields is not iterable from Avro schema with name: %s', schemaName) } + return true } diff --git a/packages/datadog-plugin-aws-sdk/src/base.js b/packages/datadog-plugin-aws-sdk/src/base.js index 14e12e7eb40..9be675dde28 100644 --- a/packages/datadog-plugin-aws-sdk/src/base.js +++ b/packages/datadog-plugin-aws-sdk/src/base.js @@ -6,6 +6,7 @@ const { storage } = require('../../datadog-core') const { isTrue } = require('../../dd-trace/src/util') const coalesce = require('koalas') const { tagsFromRequest, tagsFromResponse } = require('../../dd-trace/src/payload-tagging') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') class BaseAwsSdkPlugin extends ClientPlugin { static get id () { return 'aws' } @@ -132,7 +133,7 @@ class BaseAwsSdkPlugin extends ClientPlugin { isEnabled (request) { const serviceId = this.serviceIdentifier.toUpperCase() - const envVarValue = process.env[`DD_TRACE_AWS_SDK_${serviceId}_ENABLED`] + const envVarValue = getEnvironmentVariable(`DD_TRACE_AWS_SDK_${serviceId}_ENABLED`) return envVarValue ? isTrue(envVarValue) : true } @@ -142,11 +143,12 @@ class BaseAwsSdkPlugin extends ClientPlugin { const operation = response.request.operation const extraTags = this.generateTags(params, operation, response) || {} - const tags = Object.assign({ + const tags = { 'aws.response.request_id': response.requestId, 'resource.name': operation, - 'span.kind': 'client' - }, extraTags) + 'span.kind': 'client', + ...extraTags + } span.addTags(tags) @@ -211,19 +213,21 @@ function normalizeConfig (config, serviceIdentifier) { const batchPropagationEnabled = isTrue( coalesce( specificConfig.batchPropagationEnabled, - process.env[`DD_TRACE_AWS_SDK_${serviceId}_BATCH_PROPAGATION_ENABLED`], + getEnvironmentVariable(`DD_TRACE_AWS_SDK_${serviceId}_BATCH_PROPAGATION_ENABLED`), config.batchPropagationEnabled, - process.env.DD_TRACE_AWS_SDK_BATCH_PROPAGATION_ENABLED, + getEnvironmentVariable('DD_TRACE_AWS_SDK_BATCH_PROPAGATION_ENABLED'), false ) ) // Merge the specific config back into the main config - return Object.assign({}, config, specificConfig, { + return { + ...config, + ...specificConfig, splitByAwsService: config.splitByAwsService !== false, batchPropagationEnabled, hooks - }) + } } const noop = () => {} diff --git a/packages/datadog-plugin-aws-sdk/src/services/cloudwatchlogs.js b/packages/datadog-plugin-aws-sdk/src/services/cloudwatchlogs.js index 8ad4c8f1b26..8f3ae595bba 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/cloudwatchlogs.js +++ b/packages/datadog-plugin-aws-sdk/src/services/cloudwatchlogs.js @@ -6,15 +6,13 @@ class CloudwatchLogs extends BaseAwsSdkPlugin { static get id () { return 'cloudwatchlogs' } generateTags (params, operation) { - const tags = {} + if (!params?.logGroupName) return {} - if (!params || !params.logGroupName) return tags - - return Object.assign(tags, { + return { 'resource.name': `${operation} ${params.logGroupName}`, 'aws.cloudwatch.logs.log_group_name': params.logGroupName, loggroupname: params.logGroupName - }) + } } } diff --git a/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js b/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js index f848519c499..16523e4a236 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +++ b/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js @@ -14,40 +14,32 @@ class DynamoDb extends BaseAwsSdkPlugin { const tags = {} if (params) { - if (params.TableName) { - Object.assign(tags, { - 'resource.name': `${operation} ${params.TableName}`, - 'aws.dynamodb.table_name': params.TableName, - tablename: params.TableName - }) - } + let tableName = params.TableName - // batch operations have different format, collect table name for batch + // Collect table name for batch operations which have different format // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#batchGetItem-property` // dynamoDB batch TableName if (params.RequestItems !== null && typeof params.RequestItems === 'object') { const requestItemsKeys = Object.keys(params.RequestItems) if (requestItemsKeys.length === 1) { - const tableName = requestItemsKeys[0] - - // also add span type to match serverless convention - Object.assign(tags, { - 'resource.name': `${operation} ${tableName}`, - 'aws.dynamodb.table_name': tableName, - tablename: tableName - }) + tableName = requestItemsKeys[0] } } + if (tableName) { + // Also add span type to match serverless convention + tags['resource.name'] = `${operation} ${tableName}` + tags['aws.dynamodb.table_name'] = tableName + tags.tablename = tableName + } + // TODO: DynamoDB.DocumentClient does batches on multiple tables // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#batchGet-property // it may be useful to have a different resource naming convention here to show all table names } - // also add span type to match serverless convention - Object.assign(tags, { - 'span.type': 'dynamodb' - }) + // Also add span type to match serverless convention + tags['span.type'] = 'dynamodb' return tags } diff --git a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js index a9c9533b69b..d18db454090 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +++ b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js @@ -35,11 +35,10 @@ class Kinesis extends BaseAwsSdkPlugin { obj.needsFinish = true const options = { childOf: responseExtraction.maybeChildOf, - tags: Object.assign( - {}, - this.requestTags.get(request) || {}, - { 'span.kind': 'server' } - ) + tags: { + ...this.requestTags.get(request), + 'span.kind': 'server' + } } span = plugin.tracer.startSpan('aws.response', options) this.enter(span, store) diff --git a/packages/datadog-plugin-aws-sdk/src/services/lambda.js b/packages/datadog-plugin-aws-sdk/src/services/lambda.js index 3776f049623..6fac59fa3f5 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/lambda.js +++ b/packages/datadog-plugin-aws-sdk/src/services/lambda.js @@ -7,15 +7,13 @@ class Lambda extends BaseAwsSdkPlugin { static get id () { return 'lambda' } generateTags (params, operation, response) { - const tags = {} + if (!params?.FunctionName) return {} - if (!params || !params.FunctionName) return tags - - return Object.assign(tags, { + return { 'resource.name': `${operation} ${params.FunctionName}`, functionname: params.FunctionName, 'aws.lambda': params.FunctionName - }) + } } requestInject (span, request) { diff --git a/packages/datadog-plugin-aws-sdk/src/services/redshift.js b/packages/datadog-plugin-aws-sdk/src/services/redshift.js index 2d28a400376..34d0db9a1aa 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/redshift.js +++ b/packages/datadog-plugin-aws-sdk/src/services/redshift.js @@ -6,15 +6,13 @@ class Redshift extends BaseAwsSdkPlugin { static get id () { return 'redshift' } generateTags (params, operation, response) { - const tags = {} + if (!params?.ClusterIdentifier) return {} - if (!params || !params.ClusterIdentifier) return tags - - return Object.assign(tags, { + return { 'resource.name': `${operation} ${params.ClusterIdentifier}`, 'aws.redshift.cluster_identifier': params.ClusterIdentifier, clusteridentifier: params.ClusterIdentifier - }) + } } } diff --git a/packages/datadog-plugin-aws-sdk/src/services/s3.js b/packages/datadog-plugin-aws-sdk/src/services/s3.js index d860223d67b..a88ca528428 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/s3.js +++ b/packages/datadog-plugin-aws-sdk/src/services/s3.js @@ -11,15 +11,13 @@ class S3 extends BaseAwsSdkPlugin { static get isPayloadReporter () { return true } generateTags (params, operation, response) { - const tags = {} + if (!params?.Bucket) return {} - if (!params || !params.Bucket) return tags - - return Object.assign(tags, { + return { 'resource.name': `${operation} ${params.Bucket}`, 'aws.s3.bucket_name': params.Bucket, bucketname: params.Bucket - }) + } } addSpanPointers (span, response) { diff --git a/packages/datadog-plugin-aws-sdk/src/services/sns.js b/packages/datadog-plugin-aws-sdk/src/services/sns.js index e0d9d62fa78..095a1ca2609 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/sns.js +++ b/packages/datadog-plugin-aws-sdk/src/services/sns.js @@ -74,8 +74,7 @@ class Sns extends BaseAwsSdkPlugin { injectToMessage (span, params, topicArn, injectTraceContext) { if (!params.MessageAttributes) { params.MessageAttributes = {} - } - if (Object.keys(params.MessageAttributes).length >= 10) { // SNS quota + } else if (Object.keys(params.MessageAttributes).length >= 10) { // SNS quota log.info('Message attributes full, skipping trace context injection') return } diff --git a/packages/datadog-plugin-aws-sdk/src/services/sqs.js b/packages/datadog-plugin-aws-sdk/src/services/sqs.js index 0fb8a4b135e..0faea7935cd 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/sqs.js +++ b/packages/datadog-plugin-aws-sdk/src/services/sqs.js @@ -27,11 +27,10 @@ class Sqs extends BaseAwsSdkPlugin { obj.needsFinish = true const options = { childOf: contextExtraction.datadogContext, - tags: Object.assign( - {}, - this.requestTags.get(request) || {}, - { 'span.kind': 'server' } - ) + tags: { + ...this.requestTags.get(request), + 'span.kind': 'server' + } } parsedMessageAttributes = contextExtraction.parsedAttributes span = this.tracer.startSpan('aws.response', options) @@ -89,20 +88,18 @@ class Sqs extends BaseAwsSdkPlugin { } generateTags (params, operation, response) { - const tags = {} - - if (!params || (!params.QueueName && !params.QueueUrl)) return tags + if (!params || (!params.QueueName && !params.QueueUrl)) return {} // 'https://sqs.us-east-1.amazonaws.com/123456789012/my-queue'; let queueName = params.QueueName if (params.QueueUrl) { queueName = params.QueueUrl.split('/').at(-1) } - Object.assign(tags, { + const tags = { 'resource.name': `${operation} ${params.QueueName || params.QueueUrl}`, 'aws.sqs.queue_name': params.QueueName || params.QueueUrl, queuename: queueName - }) + } switch (operation) { case 'receiveMessage': diff --git a/packages/datadog-plugin-cucumber/src/index.js b/packages/datadog-plugin-cucumber/src/index.js index a02b8004ae5..11fac91a06e 100644 --- a/packages/datadog-plugin-cucumber/src/index.js +++ b/packages/datadog-plugin-cucumber/src/index.js @@ -2,6 +2,7 @@ const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin') const { storage } = require('../../datadog-core') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') const { TEST_SKIP_REASON, @@ -58,7 +59,7 @@ const id = require('../../dd-trace/src/id') const BREAKPOINT_HIT_GRACE_PERIOD_MS = 200 const BREAKPOINT_SET_GRACE_PERIOD_MS = 200 -const isCucumberWorker = !!process.env.CUCUMBER_WORKER_ID +const isCucumberWorker = !!getEnvironmentVariable('CUCUMBER_WORKER_ID') function getTestSuiteTags (testSuiteSpan) { const suiteTags = { @@ -134,7 +135,7 @@ class CucumberPlugin extends CiPlugin { finishAllTraceSpans(this.testSessionSpan) this.telemetry.count(TELEMETRY_TEST_SESSION, { provider: this.ciProviderName, - autoInjected: !!process.env.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER + autoInjected: !!getEnvironmentVariable('DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER') }) this.libraryConfig = null diff --git a/packages/datadog-plugin-cypress/src/cypress-plugin.js b/packages/datadog-plugin-cypress/src/cypress-plugin.js index 79f2f6a374f..e9c2c3da545 100644 --- a/packages/datadog-plugin-cypress/src/cypress-plugin.js +++ b/packages/datadog-plugin-cypress/src/cypress-plugin.js @@ -49,6 +49,7 @@ const { } = require('../../dd-trace/src/plugins/util/test') const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util') const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') const { appClosing: appClosingTelemetry } = require('../../dd-trace/src/telemetry') const log = require('../../dd-trace/src/log') @@ -615,7 +616,7 @@ class CypressPlugin { this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session') incrementCountMetric(TELEMETRY_TEST_SESSION, { provider: this.ciProviderName, - autoInjected: !!process.env.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER + autoInjected: !!getEnvironmentVariable('DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER') }) finishAllTraceSpans(this.testSessionSpan) diff --git a/packages/datadog-plugin-cypress/test/app/cypress/plugins/index.js b/packages/datadog-plugin-cypress/test/app/cypress/plugins/index.js index 07594d7af6b..51c263d8ce6 100644 --- a/packages/datadog-plugin-cypress/test/app/cypress/plugins/index.js +++ b/packages/datadog-plugin-cypress/test/app/cypress/plugins/index.js @@ -1,7 +1 @@ -module.exports = (on, config) => { - // We can't use the tracer available in the testing process, because this code is - // run in a different process. We need to init a different tracer reporting to the - // url set by the plugin agent - require('../../../../../dd-trace').init({ startupLogs: false }) - require('../../../../src/plugin')(on, config) -} +module.exports = require('../../../../../../ci/cypress/plugin') diff --git a/packages/datadog-plugin-cypress/test/index.spec.js b/packages/datadog-plugin-cypress/test/index.spec.js index e67929d4be6..ece6ce0b276 100644 --- a/packages/datadog-plugin-cypress/test/index.spec.js +++ b/packages/datadog-plugin-cypress/test/index.spec.js @@ -34,6 +34,9 @@ describe('Plugin', function () { return agent.load() }) beforeEach(function (done) { + // This test does not check test optimization agentless protocol, + // so we'll make the tracer default to reporting to v0.4/traces + agent.setAvailableEndpoints([]) this.timeout(10000) agentListenPort = agent.server.address().port cypressExecutable = require(`../../../versions/cypress@${version}`).get() diff --git a/packages/datadog-plugin-dd-trace-api/src/index.js b/packages/datadog-plugin-dd-trace-api/src/index.js index 4fa4bf77317..0fac2ce4d1b 100644 --- a/packages/datadog-plugin-dd-trace-api/src/index.js +++ b/packages/datadog-plugin-dd-trace-api/src/index.js @@ -3,12 +3,13 @@ const Plugin = require('../../dd-trace/src/plugins/plugin') const telemetryMetrics = require('../../dd-trace/src/telemetry/metrics') const apiMetrics = telemetryMetrics.manager.namespace('tracers') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') // api ==> here const objectMap = new WeakMap() const injectionEnabledTag = - `injection_enabled:${process.env.DD_INJECTION_ENABLED ? 'yes' : 'no'}` + `injection_enabled:${getEnvironmentVariable('DD_INJECTION_ENABLED') ? 'yes' : 'no'}` module.exports = class DdTraceApiPlugin extends Plugin { static get id () { diff --git a/packages/datadog-plugin-elasticsearch/src/index.js b/packages/datadog-plugin-elasticsearch/src/index.js index 307d6014dcf..6d6a3403f1e 100644 --- a/packages/datadog-plugin-elasticsearch/src/index.js +++ b/packages/datadog-plugin-elasticsearch/src/index.js @@ -35,7 +35,7 @@ function getBody (body) { } function quantizePath (path) { - return path && path.replace(/[0-9]+/g, '?') + return path && path.replaceAll(/[0-9]+/g, '?') } module.exports = ElasticsearchPlugin diff --git a/packages/datadog-plugin-express/test/code_origin.spec.js b/packages/datadog-plugin-express/test/code_origin.spec.js index 5cfb6334e05..53c9d8c1dc9 100644 --- a/packages/datadog-plugin-express/test/code_origin.spec.js +++ b/packages/datadog-plugin-express/test/code_origin.spec.js @@ -2,6 +2,7 @@ const axios = require('axios') const agent = require('../../dd-trace/test/plugins/agent') +const { assertCodeOriginFromTraces } = require('../../datadog-code-origin/test/helpers') const { getNextLineNumber } = require('../../dd-trace/test/plugins/helpers') const host = 'localhost' @@ -54,56 +55,28 @@ describe('Plugin', () => { after(() => agent.close({ ritmReset: false, wipe: true })) - it('should add code_origin tag on entry spans when feature is enabled', (done) => { - let routeRegisterLine + it('should add code_origin tag on entry spans when feature is enabled', async function testCase () { + let line - // Wrap in a named function to have at least one frame with a function name - function wrapperFunction () { + // Wrap in a function to have a frame without a function or type name + (() => { app.get('/route_before', (req, res) => res.end()) - routeRegisterLine = String(getNextLineNumber()) - app.get('/user', function userHandler (req, res) { + line = getNextLineNumber() + app.get('/user', (req, res) => { res.end() }) app.get('/route_after', (req, res) => res.end()) - } - - const callWrapperLine = String(getNextLineNumber()) - wrapperFunction() - - listener = app.listen(0, host, () => { - Promise.all([ - agent.assertSomeTraces(traces => { - const spans = traces[0] - // console.log(spans) - const tags = spans[0].meta - - expect(tags).to.have.property('_dd.code_origin.type', 'entry') - - expect(tags).to.have.property('_dd.code_origin.frames.0.file', __filename) - expect(tags).to.have.property('_dd.code_origin.frames.0.line', routeRegisterLine) - expect(tags).to.have.property('_dd.code_origin.frames.0.column').to.match(/^\d+$/) - expect(tags).to.have.property('_dd.code_origin.frames.0.method', 'wrapperFunction') - expect(tags).to.not.have.property('_dd.code_origin.frames.0.type') - - expect(tags).to.have.property('_dd.code_origin.frames.1.file', __filename) - expect(tags).to.have.property('_dd.code_origin.frames.1.line', callWrapperLine) - expect(tags).to.have.property('_dd.code_origin.frames.1.column').to.match(/^\d+$/) - expect(tags).to.not.have.property('_dd.code_origin.frames.1.method') - expect(tags).to.have.property('_dd.code_origin.frames.1.type', 'Context') - - expect(tags).to.not.have.property('_dd.code_origin.frames.2.file') - }), - axios.get(`http://localhost:${listener.address().port}/user`) - ]).then(() => done(), done) - }) + })() + + await assertCodeOrigin('/user', { line }) }) - it('should point to where actual route handler is configured, not the router', function testCase (done) { + it('should point to where actual route handler is configured, not the router', async function testCase () { const router = express.Router() router.get('/route_before', (req, res) => res.end()) - const routeRegisterLine = String(getNextLineNumber()) - router.get('/user', function userHandler (req, res) { + const line = getNextLineNumber() + router.get('/user', (req, res) => { res.end() }) router.get('/route_after', (req, res) => res.end()) @@ -111,91 +84,54 @@ describe('Plugin', () => { app.use('/v1', router) app.get('/route_after', (req, res) => res.end()) - listener = app.listen(0, host, () => { - Promise.all([ - agent.assertSomeTraces(traces => { - const spans = traces[0] - const tags = spans[0].meta - - expect(tags).to.have.property('_dd.code_origin.type', 'entry') - - expect(tags).to.have.property('_dd.code_origin.frames.0.file', __filename) - expect(tags).to.have.property('_dd.code_origin.frames.0.line', routeRegisterLine) - expect(tags).to.have.property('_dd.code_origin.frames.0.column').to.match(/^\d+$/) - expect(tags).to.have.property('_dd.code_origin.frames.0.type', 'Context') - expect(tags).to.have.property('_dd.code_origin.frames.0.method', 'testCase') - - expect(tags).to.not.have.property('_dd.code_origin.frames.1.file') - }), - axios.get(`http://localhost:${listener.address().port}/v1/user`) - ]).then(() => done(), done) - }) + await assertCodeOrigin('/v1/user', { line, method: 'testCase', type: 'Context' }) }) - it('should point to route handler even if passed through a middleware', function testCase (done) { - app.use(function middleware (req, res, next) { + it('should point to route handler even if passed through a middleware', async function testCase () { + app.use((req, res, next) => { next() }) - - const routeRegisterLine = String(getNextLineNumber()) - app.get('/user', function userHandler (req, res) { + const line = getNextLineNumber() + app.get('/user', (req, res) => { res.end() }) - listener = app.listen(0, host, () => { - Promise.all([ - agent.assertSomeTraces(traces => { - const spans = traces[0] - const tags = spans[0].meta - - expect(tags).to.have.property('_dd.code_origin.type', 'entry') - - expect(tags).to.have.property('_dd.code_origin.frames.0.file', __filename) - expect(tags).to.have.property('_dd.code_origin.frames.0.line', routeRegisterLine) - expect(tags).to.have.property('_dd.code_origin.frames.0.column').to.match(/^\d+$/) - expect(tags).to.have.property('_dd.code_origin.frames.0.method', 'testCase') - expect(tags).to.have.property('_dd.code_origin.frames.0.type', 'Context') - - expect(tags).to.not.have.property('_dd.code_origin.frames.1.file') - }), - axios.get(`http://localhost:${listener.address().port}/user`) - ]).then(() => done(), done) - }) + await assertCodeOrigin('/user', { line, method: 'testCase', type: 'Context' }) }) - it('should point to middleware if middleware responds early', function testCase (done) { - const middlewareRegisterLine = String(getNextLineNumber()) - app.use(function middleware (req, res, next) { + it('should point to middleware if middleware responds early', async function testCase () { + const line = getNextLineNumber() + app.use((req, res, next) => { res.end() }) - - app.get('/user', function userHandler (req, res) { + app.get('/user', (req, res) => { res.end() }) - listener = app.listen(0, host, () => { - Promise.all([ - agent.assertSomeTraces(traces => { - const spans = traces[0] - const tags = spans[0].meta - - expect(tags).to.have.property('_dd.code_origin.type', 'entry') - - expect(tags).to.have.property('_dd.code_origin.frames.0.file', __filename) - expect(tags).to.have.property('_dd.code_origin.frames.0.line', middlewareRegisterLine) - expect(tags).to.have.property('_dd.code_origin.frames.0.column').to.match(/^\d+$/) - expect(tags).to.have.property('_dd.code_origin.frames.0.method', 'testCase') - expect(tags).to.have.property('_dd.code_origin.frames.0.type', 'Context') - - expect(tags).to.not.have.property('_dd.code_origin.frames.1.file') - }), - axios.get(`http://localhost:${listener.address().port}/user`) - ]).then(() => done(), done) - }) + await assertCodeOrigin('/user', { line, method: 'testCase', type: 'Context' }) }) }) } }) }) }) + + function assertCodeOrigin (path, frame) { + return new Promise((resolve, reject) => { + listener = app.listen(0, host, async () => { + try { + await Promise.all([ + agent.assertSomeTraces((traces) => { + assertCodeOriginFromTraces(traces, { file: __filename, ...frame }) + }), + axios.get(`http://localhost:${listener.address().port}${path}`) + ]) + } catch (err) { + reject(err) + } + + resolve() + }) + }) + } }) diff --git a/packages/datadog-plugin-fastify/test/code_origin.spec.js b/packages/datadog-plugin-fastify/test/code_origin.spec.js index c27fedaee7f..98e2aa43c46 100644 --- a/packages/datadog-plugin-fastify/test/code_origin.spec.js +++ b/packages/datadog-plugin-fastify/test/code_origin.spec.js @@ -3,27 +3,26 @@ const axios = require('axios') const semver = require('semver') const agent = require('../../dd-trace/test/plugins/agent') +const { assertCodeOriginFromTraces } = require('../../datadog-code-origin/test/helpers') const { getNextLineNumber } = require('../../dd-trace/test/plugins/helpers') const { NODE_MAJOR } = require('../../../version') describe('Plugin', () => { - let fastify - let app + let fastify, app describe('fastify', () => { withVersions('fastify', 'fastify', (version, _, specificVersion) => { if (NODE_MAJOR <= 18 && semver.satisfies(specificVersion, '>=5')) return - afterEach(() => { - app.close() - }) + + afterEach(() => app.close()) withExports('fastify', version, ['default', 'fastify'], '>=3', getExport => { - beforeEach(() => { + beforeEach(async () => { fastify = getExport() app = fastify() if (semver.intersects(version, '>=3')) { - return app.register(require('../../../versions/middie').get()) + await app.register(require('../../../versions/middie').get()) } }) @@ -65,142 +64,61 @@ describe('Plugin', () => { after(() => agent.close({ ritmReset: false, wipe: true })) - it('should add code_origin tag on entry spans when feature is enabled', async () => { - let routeRegisterLine + it('should add code_origin tag on entry spans when feature is enabled', async function testCase () { + let line - // Wrap in a named function to have at least one frame with a function name - function wrapperFunction () { - routeRegisterLine = String(getNextLineNumber()) - app.get('/user', function userHandler (request, reply) { + // Wrap in a function to have a frame without a function or type name + (() => { + line = getNextLineNumber() + app.get('/user', (request, reply) => { reply.send() }) - } - - const callWrapperLine = String(getNextLineNumber()) - wrapperFunction() - - await app.listen(getListenOptions()) - - await Promise.all([ - agent.assertSomeTraces(traces => { - const spans = traces[0] - const tags = spans[0].meta - - expect(tags).to.have.property('_dd.code_origin.type', 'entry') + })() - expect(tags).to.have.property('_dd.code_origin.frames.0.file', __filename) - expect(tags).to.have.property('_dd.code_origin.frames.0.line', routeRegisterLine) - expect(tags).to.have.property('_dd.code_origin.frames.0.column').to.match(/^\d+$/) - expect(tags).to.have.property('_dd.code_origin.frames.0.method', 'wrapperFunction') - expect(tags).to.not.have.property('_dd.code_origin.frames.0.type') - - expect(tags).to.have.property('_dd.code_origin.frames.1.file', __filename) - expect(tags).to.have.property('_dd.code_origin.frames.1.line', callWrapperLine) - expect(tags).to.have.property('_dd.code_origin.frames.1.column').to.match(/^\d+$/) - expect(tags).to.not.have.property('_dd.code_origin.frames.1.method') - expect(tags).to.have.property('_dd.code_origin.frames.1.type', 'Context') - - expect(tags).to.not.have.property('_dd.code_origin.frames.2.file') - }), - axios.get(`http://localhost:${app.server.address().port}/user`) - ]) + await assertCodeOrigin('/user', { line }) }) it('should point to where actual route handler is configured, not the prefix', async () => { - let routeRegisterLine + let line app.register(function v1Handler (app, opts, done) { - routeRegisterLine = String(getNextLineNumber()) - app.get('/user', function userHandler (request, reply) { + line = getNextLineNumber() + app.get('/user', (request, reply) => { reply.send() }) done() }, { prefix: '/v1' }) - await app.listen(getListenOptions()) - - await Promise.all([ - agent.assertSomeTraces(traces => { - const spans = traces[0] - const tags = spans[0].meta - - expect(tags).to.have.property('_dd.code_origin.type', 'entry') - - expect(tags).to.have.property('_dd.code_origin.frames.0.file', __filename) - expect(tags).to.have.property('_dd.code_origin.frames.0.line', routeRegisterLine) - expect(tags).to.have.property('_dd.code_origin.frames.0.column').to.match(/^\d+$/) - expect(tags).to.have.property('_dd.code_origin.frames.0.method', 'v1Handler') - expect(tags).to.not.have.property('_dd.code_origin.frames.0.type') + await app.ready() - expect(tags).to.not.have.property('_dd.code_origin.frames.1.file') - }), - axios.get(`http://localhost:${app.server.address().port}/v1/user`) - ]) + await assertCodeOrigin('/v1/user', { line, method: 'v1Handler' }) }) it('should point to route handler even if passed through a middleware', async function testCase () { - app.use(function middleware (req, res, next) { + app.use((req, res, next) => { next() }) - - const routeRegisterLine = String(getNextLineNumber()) - app.get('/user', function userHandler (request, reply) { + const line = getNextLineNumber() + app.get('/user', (request, reply) => { reply.send() }) - await app.listen(getListenOptions()) - - await Promise.all([ - agent.assertSomeTraces(traces => { - const spans = traces[0] - const tags = spans[0].meta - - expect(tags).to.have.property('_dd.code_origin.type', 'entry') - - expect(tags).to.have.property('_dd.code_origin.frames.0.file', __filename) - expect(tags).to.have.property('_dd.code_origin.frames.0.line', routeRegisterLine) - expect(tags).to.have.property('_dd.code_origin.frames.0.column').to.match(/^\d+$/) - expect(tags).to.have.property('_dd.code_origin.frames.0.method', 'testCase') - expect(tags).to.have.property('_dd.code_origin.frames.0.type', 'Context') - - expect(tags).to.not.have.property('_dd.code_origin.frames.1.file') - }), - axios.get(`http://localhost:${app.server.address().port}/user`) - ]) + await assertCodeOrigin('/user', { line, method: 'testCase', type: 'Context' }) }) // TODO: In Fastify, the route is resolved before the middleware is called, so we actually can get the // line number of where the route handler is defined. However, this might not be the right choice and it // might be better to point to the middleware. it.skip('should point to middleware if middleware responds early', async function testCase () { - const middlewareRegisterLine = String(getNextLineNumber()) - app.use(function middleware (req, res, next) { + const line = getNextLineNumber() + app.use((req, res, next) => { res.end() }) - - app.get('/user', function userHandler (request, reply) { + app.get('/user', (request, reply) => { reply.send() }) - await app.listen(getListenOptions()) - - await Promise.all([ - agent.assertSomeTraces(traces => { - const spans = traces[0] - const tags = spans[0].meta - - expect(tags).to.have.property('_dd.code_origin.type', 'entry') - - expect(tags).to.have.property('_dd.code_origin.frames.0.file', __filename) - expect(tags).to.have.property('_dd.code_origin.frames.0.line', middlewareRegisterLine) - expect(tags).to.have.property('_dd.code_origin.frames.0.column').to.match(/^\d+$/) - expect(tags).to.have.property('_dd.code_origin.frames.0.method', 'testCase') - expect(tags).to.have.property('_dd.code_origin.frames.0.type', 'Context') - - expect(tags).to.not.have.property('_dd.code_origin.frames.1.file') - }), - axios.get(`http://localhost:${app.server.address().port}/user`) - ]) + await assertCodeOrigin('/user', { line, method: 'testCase', type: 'Context' }) }) }) } @@ -208,6 +126,16 @@ describe('Plugin', () => { }) }) }) + + async function assertCodeOrigin (path, frame) { + await app.listen(getListenOptions()) + await Promise.all([ + agent.assertSomeTraces(traces => { + assertCodeOriginFromTraces(traces, { file: __filename, ...frame }) + }), + axios.get(`http://localhost:${app.server.address().port}${path}`) + ]) + } }) // Required by Fastify 1.0.0 diff --git a/packages/datadog-plugin-google-cloud-vertexai/src/tracing.js b/packages/datadog-plugin-google-cloud-vertexai/src/tracing.js index 2b2297a4332..8b0fb04f696 100644 --- a/packages/datadog-plugin-google-cloud-vertexai/src/tracing.js +++ b/packages/datadog-plugin-google-cloud-vertexai/src/tracing.js @@ -82,7 +82,7 @@ class GoogleCloudVertexAITracingPlugin extends TracingPlugin { const generationConfig = instance.generationConfig || {} for (const key of Object.keys(generationConfig)) { - const transformedKey = key.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase() + const transformedKey = key.replaceAll(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase() tags[`vertexai.request.generation_config.${transformedKey}`] = JSON.stringify(generationConfig[key]) } diff --git a/packages/datadog-plugin-graphql/src/index.js b/packages/datadog-plugin-graphql/src/index.js index 1394e12127d..85a3c09e605 100644 --- a/packages/datadog-plugin-graphql/src/index.js +++ b/packages/datadog-plugin-graphql/src/index.js @@ -27,12 +27,13 @@ class GraphQLPlugin extends CompositePlugin { // config validator helpers function validateConfig (config) { - return Object.assign({}, config, { + return { + ...config, depth: getDepth(config), variables: getVariablesFilter(config), collapse: config.collapse === undefined || !!config.collapse, hooks: getHooks(config) - }) + } } function getDepth (config) { diff --git a/packages/datadog-plugin-graphql/src/resolve.js b/packages/datadog-plugin-graphql/src/resolve.js index d8387c37d43..c3f3c13d6c7 100644 --- a/packages/datadog-plugin-graphql/src/resolve.js +++ b/packages/datadog-plugin-graphql/src/resolve.js @@ -16,13 +16,14 @@ class GraphQLResolvePlugin extends TracingPlugin { const computedPathString = path.join('.') if (this.config.collapse) { + if (context.fields[computedPathString]) return + if (!context[collapsedPathSym]) { context[collapsedPathSym] = {} + } else if (context[collapsedPathSym][computedPathString]) { + return } - if (context.fields[computedPathString]) return - if (context[collapsedPathSym][computedPathString]) return - context[collapsedPathSym][computedPathString] = true } @@ -47,10 +48,9 @@ class GraphQLResolvePlugin extends TracingPlugin { const variables = this.config.variables(info.variableValues) fieldNode.arguments - .filter(arg => arg.value && arg.value.kind === 'Variable') - .filter(arg => arg.value.name && variables[arg.value.name.value]) - .map(arg => arg.value.name.value) - .forEach(name => { + .filter(arg => arg.value?.name && arg.value.kind === 'Variable' && variables[arg.value.name.value]) + .forEach(arg => { + const name = arg.value.name.value span.setTag(`graphql.variables.${name}`, variables[name]) }) } @@ -89,7 +89,12 @@ class GraphQLResolvePlugin extends TracingPlugin { // helpers function shouldInstrument (config, path) { - const depth = path.filter(item => typeof item === 'string').length + let depth = 0 + for (const item of path) { + if (typeof item === 'string') { + depth += 1 + } + } return config.depth < 0 || config.depth >= depth } @@ -126,6 +131,7 @@ function getResolverInfo (info, args) { Object.assign(resolverVars, args) } + let hasResolvers = false const directives = info.fieldNodes?.[0]?.directives if (Array.isArray(directives)) { for (const directive of directives) { @@ -134,13 +140,14 @@ function getResolverInfo (info, args) { argList[argument.name.value] = argument.value.value } - if (Object.keys(argList).length) { + if (directive.arguments.length > 0) { + hasResolvers = true resolverVars[directive.name.value] = argList } } } - if (Object.keys(resolverVars).length) { + if (hasResolvers || args && Object.keys(resolverVars).length) { resolverInfo = { [info.fieldName]: resolverVars } } diff --git a/packages/datadog-plugin-http/src/client.js b/packages/datadog-plugin-http/src/client.js index 3947798de7c..5220a2337c3 100644 --- a/packages/datadog-plugin-http/src/client.js +++ b/packages/datadog-plugin-http/src/client.js @@ -63,7 +63,7 @@ class HttpClientPlugin extends ClientPlugin { // Implemented due to aws-sdk issue where request signing is broken if we mutate the headers // Explained further in: // https://github.com/open-telemetry/opentelemetry-js-contrib/issues/1609#issuecomment-1826167348 - options.headers = Object.assign({}, options.headers) + options.headers = { ...options.headers } this.tracer.inject(span, HTTP_HEADERS, options.headers) } @@ -173,13 +173,14 @@ function normalizeClientConfig (config) { const headers = getHeaders(config) const hooks = getHooks(config) - return Object.assign({}, config, { + return { + ...config, validateStatus, filter, propagationFilter, headers, hooks - }) + } } function getStatusValidator (config) { @@ -192,9 +193,7 @@ function getStatusValidator (config) { } function getFilter (config) { - config = Object.assign({}, config, { - blocklist: config.blocklist || [] - }) + config = { ...config, blocklist: config.blocklist || [] } return urlFilter.getFilter(config) } diff --git a/packages/datadog-plugin-http2/src/client.js b/packages/datadog-plugin-http2/src/client.js index 26b8d475104..9d3e7c400b7 100644 --- a/packages/datadog-plugin-http2/src/client.js +++ b/packages/datadog-plugin-http2/src/client.js @@ -124,9 +124,9 @@ function extractSessionDetails (authority, options) { } const protocol = authority.protocol || options.protocol || 'https:' - let port = '' + (authority.port === '' - ? (authority.protocol === 'http:' ? 80 : 443) - : authority.port) + let port = authority.port === '' + ? authority.protocol === 'http:' ? '80' : '443' + : String(authority.port) let host = authority.hostname || authority.host || 'localhost' if (protocol === 'https:' && options) { @@ -174,17 +174,16 @@ function normalizeConfig (config) { const filter = getFilter(config) const headers = getHeaders(config) - return Object.assign({}, config, { + return { + ...config, validateStatus, filter, headers - }) + } } function getFilter (config) { - config = Object.assign({}, config, { - blocklist: config.blocklist || [] - }) + config = { ...config, blocklist: config.blocklist || [] } return urlFilter.getFilter(config) } diff --git a/packages/datadog-plugin-jest/src/index.js b/packages/datadog-plugin-jest/src/index.js index e1fb9c997b2..524ad65179c 100644 --- a/packages/datadog-plugin-jest/src/index.js +++ b/packages/datadog-plugin-jest/src/index.js @@ -1,5 +1,6 @@ const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin') const { storage } = require('../../datadog-core') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') const { TEST_STATUS, @@ -48,7 +49,7 @@ const { TELEMETRY_TEST_SESSION } = require('../../dd-trace/src/ci-visibility/telemetry') -const isJestWorker = !!process.env.JEST_WORKER_ID +const isJestWorker = !!getEnvironmentVariable('JEST_WORKER_ID') // https://github.com/facebook/jest/blob/d6ad15b0f88a05816c2fe034dd6900d28315d570/packages/jest-worker/src/types.ts#L38 const CHILD_MESSAGE_END = 2 @@ -158,7 +159,7 @@ class JestPlugin extends CiPlugin { this.telemetry.count(TELEMETRY_TEST_SESSION, { provider: this.ciProviderName, - autoInjected: !!process.env.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER + autoInjected: !!getEnvironmentVariable('DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER') }) this.tracer._exporter.flush(() => { diff --git a/packages/datadog-plugin-mocha/src/index.js b/packages/datadog-plugin-mocha/src/index.js index 3c6d0b20040..54d99a3e09e 100644 --- a/packages/datadog-plugin-mocha/src/index.js +++ b/packages/datadog-plugin-mocha/src/index.js @@ -2,6 +2,7 @@ const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin') const { storage } = require('../../datadog-core') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') const { TEST_STATUS, @@ -429,7 +430,7 @@ class MochaPlugin extends CiPlugin { finishAllTraceSpans(this.testSessionSpan) this.telemetry.count(TELEMETRY_TEST_SESSION, { provider: this.ciProviderName, - autoInjected: !!process.env.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER + autoInjected: !!getEnvironmentVariable('DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER') }) } this.libraryConfig = null @@ -466,6 +467,10 @@ class MochaPlugin extends CiPlugin { this.tracer._exporter.export(trace) }) }) + + this.addBind('ci:mocha:global:run', (ctx) => { + return ctx.currentStore + }) } startTestSpan (testInfo) { diff --git a/packages/datadog-plugin-mongodb-core/src/index.js b/packages/datadog-plugin-mongodb-core/src/index.js index cd4b56109ea..d9d7330baaa 100644 --- a/packages/datadog-plugin-mongodb-core/src/index.js +++ b/packages/datadog-plugin-mongodb-core/src/index.js @@ -3,6 +3,7 @@ const { isTrue } = require('../../dd-trace/src/util') const DatabasePlugin = require('../../dd-trace/src/plugins/database') const coalesce = require('koalas') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') class MongodbCorePlugin extends DatabasePlugin { static get id () { return 'mongodb-core' } @@ -14,7 +15,7 @@ class MongodbCorePlugin extends DatabasePlugin { configure (config) { super.configure(config) - const heartbeatFromEnv = process.env.DD_TRACE_MONGODB_HEARTBEAT_ENABLED + const heartbeatFromEnv = getEnvironmentVariable('DD_TRACE_MONGODB_HEARTBEAT_ENABLED') this.config.heartbeatEnabled = coalesce( config.heartbeatEnabled, diff --git a/packages/datadog-plugin-mysql/src/index.js b/packages/datadog-plugin-mysql/src/index.js index f540c0e4c53..9d1c1c90ac6 100644 --- a/packages/datadog-plugin-mysql/src/index.js +++ b/packages/datadog-plugin-mysql/src/index.js @@ -1,5 +1,6 @@ 'use strict' +const { storage } = require('../../datadog-core') const CLIENT_PORT_KEY = require('../../dd-trace/src/constants') const DatabasePlugin = require('../../dd-trace/src/plugins/database') @@ -7,6 +8,16 @@ class MySQLPlugin extends DatabasePlugin { static get id () { return 'mysql' } static get system () { return 'mysql' } + constructor () { + super(...arguments) + + this.addSub(`apm:${this.component}:connection:start`, ctx => { + ctx.parentStore = storage('legacy').getStore() + }) + + this.addBind(`apm:${this.component}:connection:finish`, ctx => ctx.parentStore) + } + bindStart (ctx) { const service = this.serviceName({ pluginConfig: this.config, dbConfig: ctx.conf, system: this.system }) const span = this.startSpan(this.operationName(), { diff --git a/packages/datadog-plugin-next/src/index.js b/packages/datadog-plugin-next/src/index.js index 7e6d7bce36a..15ddb81365c 100644 --- a/packages/datadog-plugin-next/src/index.js +++ b/packages/datadog-plugin-next/src/index.js @@ -134,7 +134,7 @@ function normalizeConfig (config) { ? config.validateStatus : code => code < 500 - return Object.assign({}, config, { hooks, validateStatus }) + return { ...config, hooks, validateStatus } } const noop = () => {} diff --git a/packages/datadog-plugin-openai/src/tracing.js b/packages/datadog-plugin-openai/src/tracing.js index 79571588ff0..87124641980 100644 --- a/packages/datadog-plugin-openai/src/tracing.js +++ b/packages/datadog-plugin-openai/src/tracing.js @@ -766,19 +766,17 @@ function usageExtraction (tags, body, methodName, openaiStore) { } else if (body.model && ['chat.completions.create', 'completions.create'].includes(methodName)) { // estimate tokens based on method name for completions and chat completions const { model } = body - let promptEstimated = false - let completionEstimated = false // prompt tokens const payload = openaiStore const promptTokensCount = countPromptTokens(methodName, payload, model) promptTokens = promptTokensCount.promptTokens - promptEstimated = promptTokensCount.promptEstimated + const promptEstimated = promptTokensCount.promptEstimated // completion tokens const completionTokensCount = countCompletionTokens(body, model) completionTokens = completionTokensCount.completionTokens - completionEstimated = completionTokensCount.completionEstimated + const completionEstimated = completionTokensCount.completionEstimated // total tokens totalTokens = promptTokens + completionTokens diff --git a/packages/datadog-plugin-openai/test/index.spec.js b/packages/datadog-plugin-openai/test/index.spec.js index a1f95aad617..bcd69ae3391 100644 --- a/packages/datadog-plugin-openai/test/index.spec.js +++ b/packages/datadog-plugin-openai/test/index.spec.js @@ -7,7 +7,6 @@ const semver = require('semver') const nock = require('nock') const sinon = require('sinon') const { spawn } = require('child_process') -const { useEnv } = require('../../../integration-tests/helpers') const agent = require('../../dd-trace/test/plugins/agent') const { DogStatsDClient } = require('../../dd-trace/src/dogstatsd') @@ -29,16 +28,13 @@ describe('Plugin', () => { withVersions('openai', 'openai', '<5.0.0', version => { const moduleRequirePath = `../../../versions/openai@${version}` - beforeEach(() => { + before(() => { tracer = require(tracerRequirePath) - }) - - beforeEach(() => { return agent.load('openai') }) - afterEach(() => { - return agent.close({ ritmReset: false, wipe: true }) + after(() => { + return agent.close({ ritmReset: false }) }) beforeEach(() => { @@ -74,67 +70,6 @@ describe('Plugin', () => { sinon.restore() }) - describe('with configuration', () => { - useEnv({ - DD_OPENAI_SPAN_CHAR_LIMIT: 0 - }) - - it('should truncate both inputs and outputs', async () => { - if (version === '3.0.0') return - nock('https://api.openai.com:443') - .post('/v1/chat/completions') - .reply(200, { - model: 'gpt-3.5-turbo-0301', - choices: [{ - message: { - role: 'assistant', - content: "In that case, it's best to avoid peanut" - } - }] - }) - - const checkTraces = agent - .assertSomeTraces(traces => { - expect(traces[0][0].meta).to.have.property('openai.request.messages.0.content', - '...') - expect(traces[0][0].meta).to.have.property('openai.request.messages.1.content', - '...') - expect(traces[0][0].meta).to.have.property('openai.request.messages.2.content', '...') - expect(traces[0][0].meta).to.have.property('openai.response.choices.0.message.content', - '...') - }) - - const params = { - model: 'gpt-3.5-turbo', - messages: [ - { - role: 'user', - content: 'Peanut Butter or Jelly?', - name: 'hunter2' - }, - { - role: 'assistant', - content: 'Are you allergic to peanuts?', - name: 'hal' - }, - { - role: 'user', - content: 'Deathly allergic!', - name: 'hunter2' - } - ] - } - - if (semver.satisfies(realVersion, '>=4.0.0')) { - await openai.chat.completions.create(params) - } else { - await openai.createChatCompletion(params) - } - - await checkTraces - }) - }) - describe('without initialization', () => { it('should not error', (done) => { spawn('node', ['no-init'], { diff --git a/packages/datadog-plugin-playwright/src/index.js b/packages/datadog-plugin-playwright/src/index.js index 9ef1890a0aa..70dc68c3645 100644 --- a/packages/datadog-plugin-playwright/src/index.js +++ b/packages/datadog-plugin-playwright/src/index.js @@ -3,6 +3,7 @@ const { storage } = require('../../datadog-core') const id = require('../../dd-trace/src/id') const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') const { TEST_STATUS, @@ -102,7 +103,7 @@ class PlaywrightPlugin extends CiPlugin { finishAllTraceSpans(this.testSessionSpan) this.telemetry.count(TELEMETRY_TEST_SESSION, { provider: this.ciProviderName, - autoInjected: !!process.env.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER + autoInjected: !!getEnvironmentVariable('DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER') }) appClosingTelemetry() this.tracer._exporter.flush(onDone) @@ -384,7 +385,7 @@ class PlaywrightPlugin extends CiPlugin { span.finish() finishAllTraceSpans(span) - if (process.env.DD_PLAYWRIGHT_WORKER) { + if (getEnvironmentVariable('DD_PLAYWRIGHT_WORKER')) { this.tracer._exporter.flush(onDone) } }) diff --git a/packages/datadog-plugin-protobufjs/src/schema_iterator.js b/packages/datadog-plugin-protobufjs/src/schema_iterator.js index cbf12ff0016..5efeaf2bc5a 100644 --- a/packages/datadog-plugin-protobufjs/src/schema_iterator.js +++ b/packages/datadog-plugin-protobufjs/src/schema_iterator.js @@ -102,17 +102,16 @@ class SchemaExtractor { } } return true - } else { - if (!builder.shouldExtractSchema(schemaName, depth)) { - return false - } - for (const field of schema.fieldsArray) { - if (!this.extractProperty(field, schemaName, field.name, builder, depth)) { - log.warn(`DSM: Unable to extract field with name: ${field.name} from Avro schema with name: ${schemaName}`) - } + } + if (!builder.shouldExtractSchema(schemaName, depth)) { + return false + } + for (const field of schema.fieldsArray) { + if (!this.extractProperty(field, schemaName, field.name, builder, depth)) { + log.warn(`DSM: Unable to extract field with name: ${field.name} from Avro schema with name: ${schemaName}`) } - return true } + return true } static extractSchemas (descriptor, dataStreamsProcessor) { diff --git a/packages/datadog-plugin-redis/src/index.js b/packages/datadog-plugin-redis/src/index.js index 3fcde1b99b3..0c51532d5bf 100644 --- a/packages/datadog-plugin-redis/src/index.js +++ b/packages/datadog-plugin-redis/src/index.js @@ -77,9 +77,7 @@ function normalizeConfig (config) { const filter = urlFilter.getFilter(config) - return Object.assign({}, config, { - filter - }) + return { ...config, filter } } function uppercaseAllEntries (entries) { diff --git a/packages/datadog-plugin-vitest/src/index.js b/packages/datadog-plugin-vitest/src/index.js index 594ae83a372..281c542acca 100644 --- a/packages/datadog-plugin-vitest/src/index.js +++ b/packages/datadog-plugin-vitest/src/index.js @@ -1,5 +1,6 @@ const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin') const { storage } = require('../../datadog-core') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') const { TEST_STATUS, @@ -275,11 +276,11 @@ class VitestPlugin extends CiPlugin { this.addBind('ci:vitest:test-suite:start', (ctx) => { const { testSuiteAbsolutePath, frameworkVersion } = ctx - this.command = process.env.DD_CIVISIBILITY_TEST_COMMAND + this.command = getEnvironmentVariable('DD_CIVISIBILITY_TEST_COMMAND') this.frameworkVersion = frameworkVersion const testSessionSpanContext = this.tracer.extract('text_map', { - 'x-datadog-trace-id': process.env.DD_CIVISIBILITY_TEST_SESSION_ID, - 'x-datadog-parent-id': process.env.DD_CIVISIBILITY_TEST_MODULE_ID + 'x-datadog-trace-id': getEnvironmentVariable('DD_CIVISIBILITY_TEST_SESSION_ID'), + 'x-datadog-parent-id': getEnvironmentVariable('DD_CIVISIBILITY_TEST_MODULE_ID') }) const trimmedCommand = DD_MAJOR < 6 ? this.command : 'vitest run' @@ -396,7 +397,7 @@ class VitestPlugin extends CiPlugin { finishAllTraceSpans(this.testSessionSpan) this.telemetry.count(TELEMETRY_TEST_SESSION, { provider: this.ciProviderName, - autoInjected: !!process.env.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER + autoInjected: !!getEnvironmentVariable('DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER') }) this.tracer._exporter.flush(onFinish) }) diff --git a/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js b/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js index 0cc8fbfc274..39b99c303c5 100644 --- a/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +++ b/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js @@ -5,7 +5,6 @@ module.exports = { COMMAND_INJECTION_ANALYZER: require('./command-injection-analyzer'), HARCODED_PASSWORD_ANALYZER: require('./hardcoded-password-analyzer'), HARCODED_SECRET_ANALYZER: require('./hardcoded-secret-analyzer'), - HEADER_INJECTION_ANALYZER: require('./header-injection-analyzer'), HSTS_HEADER_MISSING_ANALYZER: require('./hsts-header-missing-analyzer'), INSECURE_COOKIE_ANALYZER: require('./insecure-cookie-analyzer'), LDAP_ANALYZER: require('./ldap-injection-analyzer'), diff --git a/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js b/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js index 9f654379cb2..10891ee7aed 100644 --- a/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +++ b/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js @@ -1,4 +1,3 @@ -/* eslint-disable @stylistic/max-len */ 'use strict' const { NameAndValue } = require('./hardcoded-rule-type') diff --git a/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js b/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js index 245c13a7272..881fc84caa7 100644 --- a/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +++ b/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js @@ -1,4 +1,3 @@ -/* eslint-disable @stylistic/max-len */ 'use strict' const { ValueOnly, NameAndValue } = require('./hardcoded-rule-type') diff --git a/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js b/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js index 245c13a7272..881fc84caa7 100644 --- a/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +++ b/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js @@ -1,4 +1,3 @@ -/* eslint-disable @stylistic/max-len */ 'use strict' const { ValueOnly, NameAndValue } = require('./hardcoded-rule-type') diff --git a/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js b/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js deleted file mode 100644 index 4cbed8289dd..00000000000 --- a/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +++ /dev/null @@ -1,120 +0,0 @@ -'use strict' - -const InjectionAnalyzer = require('./injection-analyzer') -const { HEADER_INJECTION } = require('../vulnerabilities') -const { getNodeModulesPaths } = require('../path-line') -const { HEADER_NAME_VALUE_SEPARATOR } = require('../vulnerabilities-formatter/constants') -const { getRanges } = require('../taint-tracking/operations') -const { - HTTP_REQUEST_COOKIE_VALUE, - HTTP_REQUEST_HEADER_VALUE -} = require('../taint-tracking/source-types') - -const EXCLUDED_PATHS = getNodeModulesPaths('express') -const EXCLUDED_HEADER_NAMES = new Set([ - 'location', - 'sec-websocket-location', - 'sec-websocket-accept', - 'upgrade', - 'connection' -]) - -class HeaderInjectionAnalyzer extends InjectionAnalyzer { - constructor () { - super(HEADER_INJECTION) - } - - onConfigure () { - this.addSub('datadog:http:server:response:set-header:finish', ({ name, value }) => { - if (Array.isArray(value)) { - for (const headerValue of value) { - this.analyze({ name, value: headerValue }) - } - } else { - this.analyze({ name, value }) - } - }) - } - - _isVulnerable ({ name, value }, iastContext) { - const lowerCasedHeaderName = name?.trim().toLowerCase() - - if (this.isExcludedHeaderName(lowerCasedHeaderName) || typeof value !== 'string') return - - const ranges = getRanges(iastContext, value) - return ranges?.length > 0 && !this.shouldIgnoreHeader(lowerCasedHeaderName, ranges) - } - - _getEvidence (headerInfo, iastContext) { - const prefix = headerInfo.name + HEADER_NAME_VALUE_SEPARATOR - const prefixLength = prefix.length - - const evidence = super._getEvidence(headerInfo.value, iastContext) - evidence.value = prefix + evidence.value - evidence.ranges = evidence.ranges.map(range => { - return { - ...range, - start: range.start + prefixLength, - end: range.end + prefixLength - } - }) - - return evidence - } - - isExcludedHeaderName (name) { - return EXCLUDED_HEADER_NAMES.has(name) - } - - isAllRangesFromHeader (ranges, headerName) { - return ranges - .every(range => - range.iinfo.type === HTTP_REQUEST_HEADER_VALUE && range.iinfo.parameterName?.toLowerCase() === headerName - ) - } - - isAllRangesFromSource (ranges, source) { - return ranges - .every(range => range.iinfo.type === source) - } - - /** - * Exclude access-control-allow-*: when the header starts with access-control-allow- and the - * source of the tainted range is a request header - */ - isAccessControlAllowExclusion (name, ranges) { - if (name?.startsWith('access-control-allow-')) { - return this.isAllRangesFromSource(ranges, HTTP_REQUEST_HEADER_VALUE) - } - - return false - } - - /** Exclude when the header is reflected from the request */ - isSameHeaderExclusion (name, ranges) { - return ranges.length === 1 && name === ranges[0].iinfo.parameterName?.toLowerCase() - } - - shouldIgnoreHeader (headerName, ranges) { - switch (headerName) { - case 'set-cookie': - /** Exclude set-cookie header if the source of all the tainted ranges are cookies */ - return this.isAllRangesFromSource(ranges, HTTP_REQUEST_COOKIE_VALUE) - case 'pragma': - /** Ignore pragma headers when the source is the cache control header. */ - return this.isAllRangesFromHeader(ranges, 'cache-control') - case 'transfer-encoding': - case 'content-encoding': - /** Ignore transfer and content encoding headers when the source is the accept encoding header. */ - return this.isAllRangesFromHeader(ranges, 'accept-encoding') - } - - return this.isAccessControlAllowExclusion(headerName, ranges) || this.isSameHeaderExclusion(headerName, ranges) - } - - _getExcludedPaths () { - return EXCLUDED_PATHS - } -} - -module.exports = new HeaderInjectionAnalyzer() diff --git a/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js b/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js index 258cac7a907..d8a847a4042 100644 --- a/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js +++ b/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js @@ -32,9 +32,8 @@ class MissingHeaderAnalyzer extends Analyzer { const headerValue = res.getHeader(headerName) if (Array.isArray(headerValue)) { return headerValue - } else { - return headerValue ? [headerValue.toString()] : [] } + return headerValue ? [headerValue.toString()] : [] } _getLocation () {} diff --git a/packages/dd-trace/src/appsec/iast/security-controls/index.js b/packages/dd-trace/src/appsec/iast/security-controls/index.js index 2dcc6fe9e92..a10b1382bcc 100644 --- a/packages/dd-trace/src/appsec/iast/security-controls/index.js +++ b/packages/dd-trace/src/appsec/iast/security-controls/index.js @@ -150,19 +150,18 @@ function addSecureMarks (value, secureMarks, createNewTainted = true) { if (typeof value === 'string') { return TaintTrackingOperations.addSecureMark(iastContext, value, secureMarks, createNewTainted) - } else { - iterateObjectStrings(value, (value, levelKeys, parent, lastKey) => { - try { - const securedTainted = TaintTrackingOperations.addSecureMark(iastContext, value, secureMarks, createNewTainted) - if (createNewTainted) { - parent[lastKey] = securedTainted - } - } catch { - // if it is a readonly property, do nothing - } - }) - return value } + iterateObjectStrings(value, (value, levelKeys, parent, lastKey) => { + try { + const securedTainted = TaintTrackingOperations.addSecureMark(iastContext, value, secureMarks, createNewTainted) + if (createNewTainted) { + parent[lastKey] = securedTainted + } + } catch { + // if it is a readonly property, do nothing + } + }) + return value } function disable () { diff --git a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js index de0195daefe..d7772093c14 100644 --- a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +++ b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js @@ -207,7 +207,7 @@ class TaintTrackingPlugin extends SourceIastPlugin { if (Array.isArray(result)) { for (let i = 0; i < result.length && i < this._rowsToTaint; i++) { - const nextName = name ? `${name}.${i}` : '' + i + const nextName = name ? `${name}.${i}` : String(i) result[i] = this._taintDatabaseResult(result[i], dbOrigin, iastContext, nextName) } } else if (result && typeof result === 'object') { diff --git a/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js b/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js index bc8a069b8e0..ab0b5ec878f 100644 --- a/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +++ b/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js @@ -14,6 +14,7 @@ const log = require('../../../log') const { isMainThread } = require('worker_threads') const { LOG_MESSAGE, REWRITTEN_MESSAGE } = require('./constants') const orchestrionConfig = require('../../../../../datadog-instrumentations/src/orchestrion-config') +const { getEnvironmentVariable } = require('../../../config-helper') let config const hardcodedSecretCh = dc.channel('datadog:secrets:result') @@ -26,7 +27,7 @@ let kSymbolPrepareStackTrace function noop () {} function isFlagPresent (flag) { - return process.env.NODE_OPTIONS?.includes(flag) || + return getEnvironmentVariable('NODE_OPTIONS')?.includes(flag) || process.execArgv?.some(arg => arg.includes(flag)) } diff --git a/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js b/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js index 8b4089af9de..584e2960aa0 100644 --- a/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +++ b/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js @@ -45,7 +45,7 @@ function filterTags (tags) { function processTagValue (tags) { return tags.map(tag => tag.includes(':') ? tag.split(':')[1] : tag) - .join('_').replace(/\./g, '_') + .join('_').replaceAll('.', '_') } module.exports = { diff --git a/packages/dd-trace/src/appsec/iast/telemetry/verbosity.js b/packages/dd-trace/src/appsec/iast/telemetry/verbosity.js index 3e28371974f..72aa91723dc 100644 --- a/packages/dd-trace/src/appsec/iast/telemetry/verbosity.js +++ b/packages/dd-trace/src/appsec/iast/telemetry/verbosity.js @@ -19,9 +19,8 @@ function getVerbosity (verbosity) { if (verbosity) { verbosity = verbosity.toUpperCase() return Verbosity[verbosity] === undefined ? Verbosity.INFORMATION : Verbosity[verbosity] - } else { - return Verbosity.INFORMATION } + return Verbosity.INFORMATION } function getName (verbosityValue) { diff --git a/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/range-utils.js b/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/range-utils.js index 4fe4715d0a9..248208d07e8 100644 --- a/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/range-utils.js +++ b/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/range-utils.js @@ -16,18 +16,17 @@ function remove (range, rangeToRemove) { return [range] } else if (contains(rangeToRemove, range)) { return [] - } else { - const result = [] - if (rangeToRemove.start > range.start) { - const offset = rangeToRemove.start - range.start - result.push({ start: range.start, end: range.start + offset }) - } - if (rangeToRemove.end < range.end) { - const offset = range.end - rangeToRemove.end - result.push({ start: rangeToRemove.end, end: rangeToRemove.end + offset }) - } - return result } + const result = [] + if (rangeToRemove.start > range.start) { + const offset = rangeToRemove.start - range.start + result.push({ start: range.start, end: range.start + offset }) + } + if (rangeToRemove.end < range.end) { + const offset = range.end - rangeToRemove.end + result.push({ start: rangeToRemove.end, end: rangeToRemove.end + offset }) + } + return result } module.exports = { diff --git a/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js b/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js deleted file mode 100644 index 73642c62e6d..00000000000 --- a/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict' - -const { HEADER_NAME_VALUE_SEPARATOR } = require('../../constants') - -module.exports = function extractSensitiveRanges (evidence, namePattern, valuePattern) { - const evidenceValue = evidence.value - const sections = evidenceValue.split(HEADER_NAME_VALUE_SEPARATOR) - const headerName = sections[0] - const headerValue = sections.slice(1).join(HEADER_NAME_VALUE_SEPARATOR) - namePattern.lastIndex = 0 - valuePattern.lastIndex = 0 - if (namePattern.test(headerName) || valuePattern.test(headerValue)) { - return [{ - start: headerName.length + HEADER_NAME_VALUE_SEPARATOR.length, - end: evidenceValue.length - }] - } - - return [] -} diff --git a/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js b/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js index 0b0d6d7d7da..fa4ff70e90d 100644 --- a/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +++ b/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js @@ -8,7 +8,6 @@ const { contains, intersects, remove } = require('./range-utils') const commandSensitiveAnalyzer = require('./sensitive-analyzers/command-sensitive-analyzer') const hardcodedPasswordAnalyzer = require('./sensitive-analyzers/hardcoded-password-analyzer') -const headerSensitiveAnalyzer = require('./sensitive-analyzers/header-sensitive-analyzer') const jsonSensitiveAnalyzer = require('./sensitive-analyzers/json-sensitive-analyzer') const ldapSensitiveAnalyzer = require('./sensitive-analyzers/ldap-sensitive-analyzer') const sqlSensitiveAnalyzer = require('./sensitive-analyzers/sql-sensitive-analyzer') @@ -30,9 +29,6 @@ class SensitiveHandler { this._sensitiveAnalyzers.set(vulnerabilities.HARDCODED_PASSWORD, (evidence) => { return hardcodedPasswordAnalyzer(evidence, this._valuePattern) }) - this._sensitiveAnalyzers.set(vulnerabilities.HEADER_INJECTION, (evidence) => { - return headerSensitiveAnalyzer(evidence, this._namePattern, this._valuePattern) - }) this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, ldapSensitiveAnalyzer) this._sensitiveAnalyzers.set(vulnerabilities.NOSQL_MONGODB_INJECTION, jsonSensitiveAnalyzer) this._sensitiveAnalyzers.set(vulnerabilities.SQL_INJECTION, sqlSensitiveAnalyzer) diff --git a/packages/dd-trace/src/appsec/iast/vulnerabilities.js b/packages/dd-trace/src/appsec/iast/vulnerabilities.js index b504742d63b..639ee43df8b 100644 --- a/packages/dd-trace/src/appsec/iast/vulnerabilities.js +++ b/packages/dd-trace/src/appsec/iast/vulnerabilities.js @@ -3,7 +3,6 @@ module.exports = { CODE_INJECTION: 'CODE_INJECTION', HARDCODED_PASSWORD: 'HARDCODED_PASSWORD', HARDCODED_SECRET: 'HARDCODED_SECRET', - HEADER_INJECTION: 'HEADER_INJECTION', HSTS_HEADER_MISSING: 'HSTS_HEADER_MISSING', INSECURE_COOKIE: 'INSECURE_COOKIE', LDAP_INJECTION: 'LDAP_INJECTION', diff --git a/packages/dd-trace/src/appsec/index.js b/packages/dd-trace/src/appsec/index.js index 342d52ecb2d..6210daabd70 100644 --- a/packages/dd-trace/src/appsec/index.js +++ b/packages/dd-trace/src/appsec/index.js @@ -45,7 +45,7 @@ function enable (_config) { if (isEnabled) return try { - appsecTelemetry.enable(_config.telemetry) + appsecTelemetry.enable(_config) graphql.enable() if (_config.appsec.rasp.enabled) { @@ -139,7 +139,7 @@ function incomingHttpStartTranslator ({ req, res, abortController }) { [HTTP_CLIENT_IP]: clientIp }) - const requestHeaders = Object.assign({}, req.headers) + const requestHeaders = { ...req.headers } delete requestHeaders.cookie const persistent = { @@ -299,12 +299,12 @@ function onResponseWriteHead ({ req, res, abortController, statusCode, responseH const rootSpan = web.root(req) if (!rootSpan) return - responseHeaders = Object.assign({}, responseHeaders) + responseHeaders = { ...responseHeaders } delete responseHeaders['set-cookie'] const results = waf.run({ persistent: { - [addresses.HTTP_INCOMING_RESPONSE_CODE]: '' + statusCode, + [addresses.HTTP_INCOMING_RESPONSE_CODE]: String(statusCode), [addresses.HTTP_INCOMING_RESPONSE_HEADERS]: responseHeaders } }, req) diff --git a/packages/dd-trace/src/appsec/reporter.js b/packages/dd-trace/src/appsec/reporter.js index a6d7368bf22..4d1a951b1b7 100644 --- a/packages/dd-trace/src/appsec/reporter.js +++ b/packages/dd-trace/src/appsec/reporter.js @@ -96,7 +96,7 @@ function formatHeaderName (name) { return name .trim() .slice(0, 200) - .replace(/[^a-zA-Z0-9_\-:/]/g, '_') + .replaceAll(/[^a-zA-Z0-9_\-:/]/g, '_') .toLowerCase() } @@ -116,7 +116,7 @@ function filterHeaders (headers, map) { for (const [headerName, tagName] of map) { const headerValue = headers[headerName] if (headerValue) { - result[tagName] = '' + headerValue + result[tagName] = String(headerValue) } } @@ -132,7 +132,7 @@ function filterExtendedHeaders (headers, excludedHeaderNames, tagPrefix, limit = for (const [headerName, headerValue] of Object.entries(headers)) { if (counter >= limit) break if (!excludedHeaderNames.has(headerName)) { - result[getHeaderTag(tagPrefix, headerName)] = '' + headerValue + result[getHeaderTag(tagPrefix, headerName)] = String(headerValue) counter++ } } @@ -301,8 +301,6 @@ function reportAttack (attackData) { } function truncateRequestBody (target, depth = 0) { - let wasTruncated = false - switch (typeof target) { case 'string': if (target.length > COLLECTED_REQUEST_BODY_MAX_STRING_LENGTH) { @@ -328,7 +326,7 @@ function truncateRequestBody (target, depth = 0) { if (Array.isArray(target)) { const maxArrayLength = Math.min(target.length, COLLECTED_REQUEST_BODY_MAX_ELEMENTS_PER_NODE) - wasTruncated = target.length > COLLECTED_REQUEST_BODY_MAX_ELEMENTS_PER_NODE + let wasTruncated = target.length > COLLECTED_REQUEST_BODY_MAX_ELEMENTS_PER_NODE const truncatedArray = new Array(maxArrayLength) for (let i = 0; i < maxArrayLength; i++) { const { value, truncated } = truncateRequestBody(target[i], depth + 1) @@ -341,7 +339,7 @@ function truncateRequestBody (target, depth = 0) { const keys = Object.keys(target) const maxKeysLength = Math.min(keys.length, COLLECTED_REQUEST_BODY_MAX_ELEMENTS_PER_NODE) - wasTruncated = keys.length > COLLECTED_REQUEST_BODY_MAX_ELEMENTS_PER_NODE + let wasTruncated = keys.length > COLLECTED_REQUEST_BODY_MAX_ELEMENTS_PER_NODE const truncatedObject = {} for (let i = 0; i < maxKeysLength; i++) { diff --git a/packages/dd-trace/src/appsec/sdk/set_user.js b/packages/dd-trace/src/appsec/sdk/set_user.js index 6458319c6d1..6f32facab44 100644 --- a/packages/dd-trace/src/appsec/sdk/set_user.js +++ b/packages/dd-trace/src/appsec/sdk/set_user.js @@ -7,7 +7,7 @@ const addresses = require('../addresses') function setUserTags (user, rootSpan) { for (const k of Object.keys(user)) { - rootSpan.setTag(`usr.${k}`, '' + user[k]) + rootSpan.setTag(`usr.${k}`, String(user[k])) } rootSpan.setTag('_dd.appsec.user.collection_mode', 'sdk') @@ -28,7 +28,7 @@ function setUser (tracer, user) { setUserTags(user, rootSpan) const persistent = { - [addresses.USER_ID]: '' + user.id + [addresses.USER_ID]: String(user.id) } if (user.session_id && typeof user.session_id === 'string') { diff --git a/packages/dd-trace/src/appsec/sdk/track_event.js b/packages/dd-trace/src/appsec/sdk/track_event.js index dcda1dc27b9..05f73536790 100644 --- a/packages/dd-trace/src/appsec/sdk/track_event.js +++ b/packages/dd-trace/src/appsec/sdk/track_event.js @@ -196,7 +196,7 @@ function trackEvent (eventName, fields, sdkMethodName, rootSpan) { } for (const metadataKey of Object.keys(flatFields)) { - tags[`appsec.events.${eventName}.${metadataKey}`] = '' + flatFields[metadataKey] + tags[`appsec.events.${eventName}.${metadataKey}`] = String(flatFields[metadataKey]) } } @@ -211,11 +211,11 @@ function runWaf (eventName, user) { } if (user?.id) { - persistent[addresses.USER_ID] = '' + user.id + persistent[addresses.USER_ID] = String(user.id) } if (user?.login) { - persistent[addresses.USER_LOGIN] = '' + user.login + persistent[addresses.USER_LOGIN] = String(user.login) } waf.run({ persistent }) diff --git a/packages/dd-trace/src/appsec/telemetry/index.js b/packages/dd-trace/src/appsec/telemetry/index.js index 32535b79a27..4926f1fd960 100644 --- a/packages/dd-trace/src/appsec/telemetry/index.js +++ b/packages/dd-trace/src/appsec/telemetry/index.js @@ -15,17 +15,47 @@ const { incrementWafUpdates, incrementWafRequests } = require('./waf') +const telemetryMetrics = require('../../telemetry/metrics') const metricsStoreMap = new WeakMap() +const appsecMetrics = telemetryMetrics.manager.namespace('appsec') + let enabled = false +let interval +const SUPPORTED_ORIGINS = new Set(['env_var', 'code', 'remote_config', 'unknown']) -function enable (telemetryConfig) { +function enable (config) { + const telemetryConfig = config.telemetry enabled = telemetryConfig?.enabled && telemetryConfig.metrics + + if (enabled) { + let origin = 'remote_config' + + if (config.appsec.enabled) { + origin = config.getOrigin('appsec.enabled') + + if (!SUPPORTED_ORIGINS.has(origin)) { + origin = 'unknown' + } + } + + const gauge = appsecMetrics.gauge('enabled', { origin }) + gauge.track() + + interval = setInterval(() => { + gauge.track() + }, telemetryConfig.heartbeatInterval) + interval.unref?.() + } } function disable () { enabled = false + if (interval) { + clearInterval(interval) + interval = undefined + } } function newStore () { diff --git a/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js b/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js index 4513c2aeda1..348f2abd6eb 100644 --- a/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +++ b/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js @@ -49,8 +49,10 @@ class WAFContextWrapper { if (persistent !== null && typeof persistent === 'object') { const persistentInputs = {} + let hasPersistentInputs = false for (const key of Object.keys(persistent)) { if (!this.addressesToSkip.has(key) && this.knownAddresses.has(key)) { + hasPersistentInputs = true persistentInputs[key] = persistent[key] if (preventDuplicateAddresses.has(key)) { newAddressesToSkip.add(key) @@ -58,7 +60,7 @@ class WAFContextWrapper { } } - if (Object.keys(persistentInputs).length) { + if (hasPersistentInputs) { payload.persistent = persistentInputs payloadHasData = true } @@ -67,13 +69,15 @@ class WAFContextWrapper { if (ephemeral !== null && typeof ephemeral === 'object') { const ephemeralInputs = {} + let hasEphemeral = false for (const key of Object.keys(ephemeral)) { if (this.knownAddresses.has(key)) { + hasEphemeral = true ephemeralInputs[key] = ephemeral[key] } } - if (Object.keys(ephemeralInputs).length) { + if (hasEphemeral) { payload.ephemeral = ephemeralInputs payloadHasData = true } diff --git a/packages/dd-trace/src/azure_metadata.js b/packages/dd-trace/src/azure_metadata.js index 9705abb6831..db1bb1836bf 100644 --- a/packages/dd-trace/src/azure_metadata.js +++ b/packages/dd-trace/src/azure_metadata.js @@ -4,6 +4,7 @@ const os = require('os') const { getIsAzureFunction } = require('./serverless') +const { getEnvironmentVariable, getEnvironmentVariables } = require('../../dd-trace/src/config-helper') function extractSubscriptionID (ownerName) { if (ownerName !== undefined) { @@ -45,7 +46,7 @@ function buildMetadata () { WEBSITE_OS, WEBSITE_RESOURCE_GROUP, WEBSITE_SITE_NAME - } = process.env + } = getEnvironmentVariables() const subscriptionID = extractSubscriptionID(WEBSITE_OWNER_NAME) @@ -78,11 +79,15 @@ function getAzureAppMetadata () { // DD_AZURE_APP_SERVICES is an environment variable introduced by the .NET APM team and is set automatically for // anyone using the Datadog APM Extensions (.NET, Java, or Node) for Windows Azure App Services // See: https://github.com/DataDog/datadog-aas-extension/blob/01f94b5c28b7fa7a9ab264ca28bd4e03be603900/node/src/applicationHost.xdt#L20-L21 - return process.env.DD_AZURE_APP_SERVICES === undefined ? undefined : buildMetadata() + if (getEnvironmentVariable('DD_AZURE_APP_SERVICES') !== undefined) { + return buildMetadata() + } } function getAzureFunctionMetadata () { - return getIsAzureFunction() ? buildMetadata() : undefined + if (getIsAzureFunction()) { + return buildMetadata() + } } // Modeled after https://github.com/DataDog/libdatadog/blob/92272e90a7919f07178f3246ef8f82295513cfed/profiling/src/exporter/mod.rs#L187 diff --git a/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js b/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js index 63100e6e335..aae03250bb6 100644 --- a/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +++ b/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js @@ -4,6 +4,7 @@ const { join } = require('path') const { Worker, threadId: parentThreadId } = require('worker_threads') const { randomUUID } = require('crypto') const log = require('../../log') +const { getEnvironmentVariables } = require('../../config-helper') const probeIdToResolveBreakpointSet = new Map() const probeIdToResolveBreakpointRemove = new Map() @@ -72,13 +73,13 @@ class TestVisDynamicInstrumentation { // for PnP support, hence why we deviate from the DI pattern here. // To avoid infinite initialization loops, we're disabling DI and tracing in the worker. env: { - ...process.env, - DD_CIVISIBILITY_ENABLED: 0, - DD_TRACE_ENABLED: 0, - DD_TEST_FAILED_TEST_REPLAY_ENABLED: 0, - DD_CIVISIBILITY_MANUAL_API_ENABLED: 0, - DD_TRACING_ENABLED: 0, - DD_TRACE_TELEMETRY_ENABLED: 0 + ...getEnvironmentVariables(), + DD_CIVISIBILITY_ENABLED: 'false', + DD_TRACE_ENABLED: 'false', + DD_TEST_FAILED_TEST_REPLAY_ENABLED: 'false', + DD_CIVISIBILITY_MANUAL_API_ENABLED: 'false', + DD_TRACING_ENABLED: 'false', + DD_INSTRUMENTATION_TELEMETRY_ENABLED: 'false' }, workerData: { config: this._config.serialize(), diff --git a/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js b/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js index 24e4ae9f01f..e11887169ef 100644 --- a/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +++ b/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js @@ -1,6 +1,7 @@ const request = require('../../exporters/common/request') const id = require('../../id') const log = require('../../log') +const { getEnvironmentVariable } = require('../../config-helper') const { incrementCountMetric, @@ -48,7 +49,7 @@ function getKnownTests ({ options.path = `${evpProxyPrefix}/api/v2/ci/libraries/tests` options.headers['X-Datadog-EVP-Subdomain'] = 'api' } else { - const apiKey = process.env.DATADOG_API_KEY || process.env.DD_API_KEY + const apiKey = getEnvironmentVariable('DD_API_KEY') if (!apiKey) { return done(new Error('Known tests were not fetched because Datadog API key is not defined.')) } diff --git a/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js b/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js index 3ac56f4315c..2552e65c5ae 100644 --- a/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +++ b/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js @@ -2,6 +2,7 @@ const request = require('../../../exporters/common/request') const log = require('../../../log') const { safeJSONStringify } = require('../../../exporters/common/util') +const { getEnvironmentVariable } = require('../../../config-helper') const { CoverageCIVisibilityEncoder } = require('../../../encode/coverage-ci-visibility') const BaseWriter = require('../../../exporters/common/writer') @@ -28,7 +29,7 @@ class Writer extends BaseWriter { path: '/api/v2/citestcov', method: 'POST', headers: { - 'dd-api-key': process.env.DATADOG_API_KEY || process.env.DD_API_KEY, + 'dd-api-key': getEnvironmentVariable('DD_API_KEY'), ...form.getHeaders() }, timeout: 15_000, diff --git a/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js b/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js index 3df7c9f38ef..68415e4e01e 100644 --- a/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +++ b/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js @@ -3,6 +3,7 @@ const request = require('../../../exporters/common/request') const log = require('../../../log') const { safeJSONStringify } = require('../../../exporters/common/util') const { JSONEncoder } = require('../../encode/json-encoder') +const { getEnvironmentVariable } = require('../../../config-helper') const BaseWriter = require('../../../exporters/common/writer') @@ -23,7 +24,7 @@ class DynamicInstrumentationLogsWriter extends BaseWriter { path: '/api/v2/logs', method: 'POST', headers: { - 'dd-api-key': process.env.DATADOG_API_KEY || process.env.DD_API_KEY, + 'dd-api-key': getEnvironmentVariable('DD_API_KEY'), 'Content-Type': 'application/json' }, // TODO: what's a good value for timeout for the logs intake? diff --git a/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js b/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js index e31d2cd84a2..fd975487c8d 100644 --- a/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +++ b/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js @@ -2,6 +2,7 @@ const request = require('../../../exporters/common/request') const { safeJSONStringify } = require('../../../exporters/common/util') const log = require('../../../log') +const { getEnvironmentVariable } = require('../../../config-helper') const { AgentlessCiVisibilityEncoder } = require('../../../encode/agentless-ci-visibility') const BaseWriter = require('../../../exporters/common/writer') @@ -29,7 +30,7 @@ class Writer extends BaseWriter { path: '/api/v2/citestcycle', method: 'POST', headers: { - 'dd-api-key': process.env.DATADOG_API_KEY || process.env.DD_API_KEY, + 'dd-api-key': getEnvironmentVariable('DD_API_KEY'), 'Content-Type': 'application/msgpack' }, timeout: 15_000, diff --git a/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js b/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js index 7d48a444d95..9cca41d0ce7 100644 --- a/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +++ b/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js @@ -3,6 +3,7 @@ const path = require('path') const FormData = require('../../../exporters/common/form-data') const request = require('../../../exporters/common/request') +const { getEnvironmentVariable } = require('../../../config-helper') const log = require('../../../log') const { isFalse } = require('../../../util') @@ -38,7 +39,7 @@ function validateCommits (commits) { throw new Error('Invalid commit type response') } if (isValidSha1(commitSha) || isValidSha256(commitSha)) { - return commitSha.replace(/[^0-9a-f]+/g, '') + return commitSha.replaceAll(/[^0-9a-f]+/g, '') } throw new Error('Invalid commit format') }) @@ -48,7 +49,7 @@ function getCommonRequestOptions (url) { return { method: 'POST', headers: { - 'dd-api-key': process.env.DATADOG_API_KEY || process.env.DD_API_KEY + 'dd-api-key': getEnvironmentVariable('DD_API_KEY') }, timeout: 15_000, url @@ -285,7 +286,7 @@ function sendGitMetadata (url, { isEvpProxy, evpProxyPrefix }, configRepositoryU } // Otherwise we unshallow and get commits to upload again log.debug('It is shallow clone, unshallowing...') - if (!isFalse(process.env.DD_CIVISIBILITY_GIT_UNSHALLOW_ENABLED)) { + if (!isFalse(getEnvironmentVariable('DD_CIVISIBILITY_GIT_UNSHALLOW_ENABLED'))) { unshallowRepository() } diff --git a/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js b/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js index 4aca67ee72f..3575fb10758 100644 --- a/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +++ b/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js @@ -9,18 +9,19 @@ const { JEST_WORKER_LOGS_PAYLOAD_CODE, PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE } = require('../../../plugins/util/test') +const { getEnvironmentVariable } = require('../../../config-helper') function getInterprocessTraceCode () { - if (process.env.JEST_WORKER_ID) { + if (getEnvironmentVariable('JEST_WORKER_ID')) { return JEST_WORKER_TRACE_PAYLOAD_CODE } - if (process.env.CUCUMBER_WORKER_ID) { + if (getEnvironmentVariable('CUCUMBER_WORKER_ID')) { return CUCUMBER_WORKER_TRACE_PAYLOAD_CODE } - if (process.env.MOCHA_WORKER_ID) { + if (getEnvironmentVariable('MOCHA_WORKER_ID')) { return MOCHA_WORKER_TRACE_PAYLOAD_CODE } - if (process.env.DD_PLAYWRIGHT_WORKER) { + if (getEnvironmentVariable('DD_PLAYWRIGHT_WORKER')) { return PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE } return null @@ -28,14 +29,14 @@ function getInterprocessTraceCode () { // TODO: make it available with cucumber function getInterprocessCoverageCode () { - if (process.env.JEST_WORKER_ID) { + if (getEnvironmentVariable('JEST_WORKER_ID')) { return JEST_WORKER_COVERAGE_PAYLOAD_CODE } return null } function getInterprocessLogsCode () { - if (process.env.JEST_WORKER_ID) { + if (getEnvironmentVariable('JEST_WORKER_ID')) { return JEST_WORKER_LOGS_PAYLOAD_CODE } return null diff --git a/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js b/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js index 5184e3121d1..6ac405c513b 100644 --- a/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +++ b/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js @@ -1,5 +1,6 @@ const request = require('../../exporters/common/request') const log = require('../../log') +const { getEnvironmentVariable } = require('../../config-helper') const { incrementCountMetric, distributionMetric, @@ -46,7 +47,7 @@ function getSkippableSuites ({ options.path = `${evpProxyPrefix}/api/v2/ci/tests/skippable` options.headers['X-Datadog-EVP-Subdomain'] = 'api' } else { - const apiKey = process.env.DATADOG_API_KEY || process.env.DD_API_KEY + const apiKey = getEnvironmentVariable('DD_API_KEY') if (!apiKey) { return done(new Error('Skippable suites were not fetched because Datadog API key is not defined.')) } diff --git a/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js b/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js index 3faa5690182..b97ee9731af 100644 --- a/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +++ b/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js @@ -1,5 +1,6 @@ const Plugin = require('../../plugins/plugin') const log = require('../../log') +const { getEnvironmentVariable } = require('../../config-helper') function getWinstonLogSubmissionParameters (config) { const { site, service } = config @@ -9,16 +10,16 @@ function getWinstonLogSubmissionParameters (config) { path: `/api/v2/logs?ddsource=winston&service=${service}`, ssl: true, headers: { - 'DD-API-KEY': process.env.DD_API_KEY + 'DD-API-KEY': getEnvironmentVariable('DD_API_KEY') } } - if (!process.env.DD_AGENTLESS_LOG_SUBMISSION_URL) { + if (!getEnvironmentVariable('DD_AGENTLESS_LOG_SUBMISSION_URL')) { return defaultParameters } try { - const url = new URL(process.env.DD_AGENTLESS_LOG_SUBMISSION_URL) + const url = new URL(getEnvironmentVariable('DD_AGENTLESS_LOG_SUBMISSION_URL')) return { host: url.hostname, port: url.port, diff --git a/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js b/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js index 65c1b02d589..5decb85096e 100644 --- a/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +++ b/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js @@ -1,6 +1,7 @@ const request = require('../../exporters/common/request') const id = require('../../id') const log = require('../../log') +const { getEnvironmentVariable } = require('../../config-helper') const { incrementCountMetric, distributionMetric, @@ -45,7 +46,7 @@ function getLibraryConfiguration ({ options.path = `${evpProxyPrefix}/api/v2/libraries/tests/services/setting` options.headers['X-Datadog-EVP-Subdomain'] = 'api' } else { - const apiKey = process.env.DATADOG_API_KEY || process.env.DD_API_KEY + const apiKey = getEnvironmentVariable('DD_API_KEY') if (!apiKey) { return done(new Error('Request to settings endpoint was not done because Datadog API key is not defined.')) } @@ -123,11 +124,11 @@ function getLibraryConfiguration ({ log.debug(() => `Remote settings: ${JSON.stringify(settings)}`) - if (process.env.DD_CIVISIBILITY_DANGEROUSLY_FORCE_COVERAGE) { + if (getEnvironmentVariable('DD_CIVISIBILITY_DANGEROUSLY_FORCE_COVERAGE')) { settings.isCodeCoverageEnabled = true log.debug(() => 'Dangerously set code coverage to true') } - if (process.env.DD_CIVISIBILITY_DANGEROUSLY_FORCE_TEST_SKIPPING) { + if (getEnvironmentVariable('DD_CIVISIBILITY_DANGEROUSLY_FORCE_TEST_SKIPPING')) { settings.isSuitesSkippingEnabled = true log.debug(() => 'Dangerously set test skipping to true') } diff --git a/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js b/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js index d2fa27a8aef..2e1897e7cb7 100644 --- a/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +++ b/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js @@ -1,5 +1,6 @@ const request = require('../../exporters/common/request') const id = require('../../id') +const { getEnvironmentVariable } = require('../../config-helper') function getTestManagementTests ({ url, @@ -28,7 +29,7 @@ function getTestManagementTests ({ options.path = `${evpProxyPrefix}/api/v2/test/libraries/test-management/tests` options.headers['X-Datadog-EVP-Subdomain'] = 'api' } else { - const apiKey = process.env.DATADOG_API_KEY || process.env.DD_API_KEY + const apiKey = getEnvironmentVariable('DD_API_KEY') if (!apiKey) { return done(new Error('Test management tests were not fetched because Datadog API key is not defined.')) } diff --git a/packages/dd-trace/src/config-helper.js b/packages/dd-trace/src/config-helper.js new file mode 100644 index 00000000000..ea9ca30ff43 --- /dev/null +++ b/packages/dd-trace/src/config-helper.js @@ -0,0 +1,89 @@ +'use strict' + +/* eslint-disable eslint-rules/eslint-process-env */ + +const { deprecate } = require('util') +const { supportedConfigurations, aliases, deprecations } = require('./supported-configurations.json') + +const aliasToCanonical = {} +for (const canonical of Object.keys(aliases)) { + for (const alias of aliases[canonical]) { + if (aliasToCanonical[alias]) { + throw new Error(`The alias ${alias} is already used for ${aliasToCanonical[alias]}.`) + } + aliasToCanonical[alias] = canonical + } +} + +const deprecationMethods = {} +for (const deprecation of Object.keys(deprecations)) { + deprecationMethods[deprecation] = deprecate( + () => {}, + `The environment variable ${deprecation} is deprecated.` + + (aliasToCanonical[deprecation] + ? ` Please use ${aliasToCanonical[deprecation]} instead.` + : ` ${deprecations[deprecation]}`), + `DATADOG_${deprecation}` + ) +} + +module.exports = { + /** + * Returns the environment variables that are supported by the tracer + * (including all non-Datadog/OTEL specific environment variables) + * + * @returns {Partial} The environment variables + */ + getEnvironmentVariables () { + const configs = {} + for (const [key, value] of Object.entries(process.env)) { + if (key.startsWith('DD_') || key.startsWith('OTEL_') || aliasToCanonical[key]) { + if (supportedConfigurations[key]) { + configs[key] = value + } else if (aliasToCanonical[key] && configs[aliasToCanonical[key]] === undefined) { + // The alias should only be used if the actual configuration is not set + // In case that more than a single alias exist, use the one defined first in our own order + for (const alias of aliases[aliasToCanonical[key]]) { + if (process.env[alias] !== undefined) { + configs[aliasToCanonical[key]] = value + break + } + } + // TODO(BridgeAR) Implement logging. It would have to use a timeout to + // lazily log the message after all loading being done otherwise. + // debug( + // `Missing configuration ${env} in supported-configurations file. The environment variable is ignored.` + // ) + } + deprecationMethods[key]?.() + } else { + configs[key] = value + } + } + return configs + }, + + /** + * Returns the environment variable, if it's supported or a non Datadog + * configuration. Otherwise, it throws an error. + * + * @param {string} name Environment variable name + * @returns {string|undefined} + * @throws {Error} if the configuration is not supported + */ + getEnvironmentVariable (name) { + if ((name.startsWith('DD_') || name.startsWith('OTEL_') || aliasToCanonical[name]) && + !supportedConfigurations[name]) { + throw new Error(`Missing ${name} env/configuration in "supported-configurations.json" file.`) + } + const config = process.env[name] + if (config === undefined && aliases[name]) { + for (const alias of aliases[name]) { + if (process.env[alias] !== undefined) { + return process.env[alias] + } + } + } + return config + } +} diff --git a/packages/dd-trace/src/config.js b/packages/dd-trace/src/config.js index d61926ecbc8..a8283ba812c 100644 --- a/packages/dd-trace/src/config.js +++ b/packages/dd-trace/src/config.js @@ -19,6 +19,7 @@ const telemetryMetrics = require('./telemetry/metrics') const { isInServerlessEnvironment, getIsGCPFunction, getIsAzureFunction } = require('./serverless') const { ORIGIN_KEY, GRPC_CLIENT_ERROR_STATUSES, GRPC_SERVER_ERROR_STATUSES } = require('./constants') const { appendRules } = require('./payload-tagging/config') +const { getEnvironmentVariable, getEnvironmentVariables } = require('./config-helper') const tracerMetrics = telemetryMetrics.manager.namespace('tracers') @@ -80,10 +81,10 @@ function getFromOtelSamplerMap (otelTracesSampler, otelTracesSamplerArg) { } function validateOtelPropagators (propagators) { - if (!process.env.PROPAGATION_STYLE_EXTRACT && - !process.env.PROPAGATION_STYLE_INJECT && - !process.env.DD_TRACE_PROPAGATION_STYLE && - process.env.OTEL_PROPAGATORS) { + if (!getEnvironmentVariable('PROPAGATION_STYLE_EXTRACT') && + !getEnvironmentVariable('PROPAGATION_STYLE_INJECT') && + !getEnvironmentVariable('DD_TRACE_PROPAGATION_STYLE') && + getEnvironmentVariable('OTEL_PROPAGATORS')) { for (const style in propagators) { if (!VALID_PROPAGATION_STYLES.has(style)) { log.warn('unexpected value for OTEL_PROPAGATORS environment variable') @@ -94,7 +95,7 @@ function validateOtelPropagators (propagators) { } function validateEnvVarType (envVar) { - const value = process.env[envVar] + const value = getEnvironmentVariable(envVar) switch (envVar) { case 'OTEL_LOG_LEVEL': return VALID_LOG_LEVELS.has(value) @@ -103,7 +104,7 @@ function validateEnvVarType (envVar) { case 'OTEL_SERVICE_NAME': return typeof value === 'string' case 'OTEL_TRACES_SAMPLER': - return getFromOtelSamplerMap(value, process.env.OTEL_TRACES_SAMPLER_ARG) !== undefined + return getFromOtelSamplerMap(value, getEnvironmentVariable('OTEL_TRACES_SAMPLER_ARG')) !== undefined case 'OTEL_TRACES_SAMPLER_ARG': return !Number.isNaN(Number.parseFloat(value)) case 'OTEL_SDK_DISABLED': @@ -119,12 +120,12 @@ function validateEnvVarType (envVar) { function checkIfBothOtelAndDdEnvVarSet () { for (const [otelEnvVar, ddEnvVar] of Object.entries(otelDdEnvMapping)) { - if (ddEnvVar && process.env[ddEnvVar] && process.env[otelEnvVar]) { + if (ddEnvVar && getEnvironmentVariable(ddEnvVar) && getEnvironmentVariable(otelEnvVar)) { log.warn(`both ${ddEnvVar} and ${otelEnvVar} environment variables are set`) getCounter('otel.env.hiding', ddEnvVar, otelEnvVar).inc() } - if (process.env[otelEnvVar] && !validateEnvVarType(otelEnvVar)) { + if (getEnvironmentVariable(otelEnvVar) && !validateEnvVarType(otelEnvVar)) { log.warn(`unexpected value for ${otelEnvVar} environment variable`) getCounter('otel.env.invalid', ddEnvVar, otelEnvVar).inc() } @@ -178,7 +179,7 @@ function validateNamingVersion (versionString) { * @param {string | string[]} input */ function splitJSONPathRules (input) { - if (!input) return null + if (!input) return if (Array.isArray(input)) return input if (input === 'all') return [] return input.split(',') @@ -211,7 +212,8 @@ function propagationStyle (key, option) { // Otherwise, fallback to env var parsing const envKey = `DD_TRACE_PROPAGATION_STYLE_${key.toUpperCase()}` - const envVar = coalesce(process.env[envKey], process.env.DD_TRACE_PROPAGATION_STYLE, process.env.OTEL_PROPAGATORS) + const envVar = coalesce(getEnvironmentVariable(envKey), + getEnvironmentVariable('DD_TRACE_PROPAGATION_STYLE'), getEnvironmentVariable('OTEL_PROPAGATORS')) if (envVar !== undefined) { return envVar.split(',') .filter(v => v !== '') @@ -265,14 +267,11 @@ class Config { checkIfBothOtelAndDdEnvVarSet() - const DD_API_KEY = coalesce( - process.env.DATADOG_API_KEY, - process.env.DD_API_KEY - ) + const DD_API_KEY = getEnvironmentVariable('DD_API_KEY') - if (process.env.DD_TRACE_PROPAGATION_STYLE && ( - process.env.DD_TRACE_PROPAGATION_STYLE_INJECT || - process.env.DD_TRACE_PROPAGATION_STYLE_EXTRACT + if (getEnvironmentVariable('DD_TRACE_PROPAGATION_STYLE') && ( + getEnvironmentVariable('DD_TRACE_PROPAGATION_STYLE_INJECT') || + getEnvironmentVariable('DD_TRACE_PROPAGATION_STYLE_EXTRACT') )) { log.warn( 'Use either the DD_TRACE_PROPAGATION_STYLE environment variable or separate ' + @@ -294,34 +293,34 @@ class Config { } const DD_INSTRUMENTATION_INSTALL_ID = coalesce( - process.env.DD_INSTRUMENTATION_INSTALL_ID, + getEnvironmentVariable('DD_INSTRUMENTATION_INSTALL_ID'), null ) const DD_INSTRUMENTATION_INSTALL_TIME = coalesce( - process.env.DD_INSTRUMENTATION_INSTALL_TIME, + getEnvironmentVariable('DD_INSTRUMENTATION_INSTALL_TIME'), null ) const DD_INSTRUMENTATION_INSTALL_TYPE = coalesce( - process.env.DD_INSTRUMENTATION_INSTALL_TYPE, + getEnvironmentVariable('DD_INSTRUMENTATION_INSTALL_TYPE'), null ) const DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING = splitJSONPathRules( coalesce( - process.env.DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING, + getEnvironmentVariable('DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING'), options.cloudPayloadTagging?.request, '' )) const DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING = splitJSONPathRules( coalesce( - process.env.DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING, + getEnvironmentVariable('DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING'), options.cloudPayloadTagging?.response, '' )) const DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH = coalesce( - process.env.DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH, + getEnvironmentVariable('DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH'), options.cloudPayloadTagging?.maxDepth, 10 ) @@ -370,17 +369,17 @@ class Config { if (this.gitMetadataEnabled) { this.repositoryUrl = removeUserSensitiveInfo( coalesce( - process.env.DD_GIT_REPOSITORY_URL, + getEnvironmentVariable('DD_GIT_REPOSITORY_URL'), this.tags[GIT_REPOSITORY_URL] ) ) this.commitSHA = coalesce( - process.env.DD_GIT_COMMIT_SHA, + getEnvironmentVariable('DD_GIT_COMMIT_SHA'), this.tags[GIT_COMMIT_SHA] ) if (!this.repositoryUrl || !this.commitSHA) { const DD_GIT_PROPERTIES_FILE = coalesce( - process.env.DD_GIT_PROPERTIES_FILE, + getEnvironmentVariable('DD_GIT_PROPERTIES_FILE'), `${process.cwd()}/git.properties` ) let gitPropertiesString @@ -388,7 +387,7 @@ class Config { gitPropertiesString = fs.readFileSync(DD_GIT_PROPERTIES_FILE, 'utf8') } catch (e) { // Only log error if the user has set a git.properties path - if (process.env.DD_GIT_PROPERTIES_FILE) { + if (getEnvironmentVariable('DD_GIT_PROPERTIES_FILE')) { log.error('Error reading DD_GIT_PROPERTIES_FILE: %s', DD_GIT_PROPERTIES_FILE, e) } } @@ -418,7 +417,7 @@ class Config { // TODO: Remove the experimental env vars as a major? const DD_TRACE_B3_ENABLED = coalesce( options.experimental && options.experimental.b3, - process.env.DD_TRACE_EXPERIMENTAL_B3_ENABLED, + getEnvironmentVariable('DD_TRACE_EXPERIMENTAL_B3_ENABLED'), false ) const defaultPropagationStyle = ['datadog', 'tracecontext'] @@ -439,7 +438,7 @@ class Config { FUNCTION_NAME, K_SERVICE, WEBSITE_SITE_NAME - } = process.env + } = getEnvironmentVariables() const service = AWS_LAMBDA_FUNCTION_NAME || FUNCTION_NAME || // Google Cloud Function Name set by deprecated runtimes @@ -643,7 +642,6 @@ class Config { DD_API_SECURITY_SAMPLE_DELAY, DD_APM_TRACING_ENABLED, DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE, - DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING, DD_APPSEC_COLLECT_ALL_HEADERS, DD_APPSEC_ENABLED, DD_APPSEC_GRAPHQL_BLOCKED_TEMPLATE_JSON, @@ -667,16 +665,14 @@ class Config { DD_CODE_ORIGIN_FOR_SPANS_EXPERIMENTAL_EXIT_SPANS_ENABLED, DD_DATA_STREAMS_ENABLED, DD_DBM_PROPAGATION_MODE, - DD_DOGSTATSD_HOSTNAME, DD_DOGSTATSD_HOST, DD_DOGSTATSD_PORT, DD_DYNAMIC_INSTRUMENTATION_ENABLED, DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS, DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS, DD_ENV, - DD_EXPERIMENTAL_API_SECURITY_ENABLED, DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED, - DD_EXPERIMENTAL_PROFILING_ENABLED, + DD_PROFILING_ENABLED, DD_GRPC_CLIENT_ERROR_STATUSES, DD_GRPC_SERVER_ERROR_STATUSES, JEST_WORKER_ID, @@ -703,7 +699,6 @@ class Config { DD_LLMOBS_ML_APP, DD_OPENAI_LOGS_ENABLED, DD_OPENAI_SPAN_CHAR_LIMIT, - DD_PROFILING_ENABLED, DD_PROFILING_EXPORTERS, DD_PROFILING_SOURCE_MAP, DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD, @@ -712,7 +707,6 @@ class Config { DD_RUNTIME_METRICS_ENABLED, DD_SERVICE, DD_SERVICE_MAPPING, - DD_SERVICE_NAME, DD_SITE, DD_SPAN_SAMPLING_RULES, DD_SPAN_SAMPLING_RULES_FILE, @@ -724,7 +718,6 @@ class Config { DD_TELEMETRY_METRICS_ENABLED, DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED, DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED, - DD_TRACE_AGENT_HOSTNAME, DD_TRACE_AGENT_PORT, DD_TRACE_AGENT_PROTOCOL_VERSION, DD_TRACE_AWS_ADD_SPAN_POINTERS, @@ -763,7 +756,6 @@ class Config { DD_TRACE_SPAN_LEAK_DEBUG, DD_TRACE_STARTUP_LOGS, DD_TRACE_TAGS, - DD_TRACE_TELEMETRY_ENABLED, DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH, DD_TRACING_ENABLED, DD_VERSION, @@ -777,7 +769,7 @@ class Config { OTEL_SERVICE_NAME, OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG - } = process.env + } = getEnvironmentVariables() const tags = {} const env = setHiddenProperty(this, '_env', {}) @@ -792,10 +784,7 @@ class Config { DD_APM_TRACING_ENABLED, DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED && isFalse(DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED) )) - this._setBoolean(env, 'appsec.apiSecurity.enabled', coalesce( - DD_API_SECURITY_ENABLED && isTrue(DD_API_SECURITY_ENABLED), - DD_EXPERIMENTAL_API_SECURITY_ENABLED && isTrue(DD_EXPERIMENTAL_API_SECURITY_ENABLED) - )) + this._setBoolean(env, 'appsec.apiSecurity.enabled', DD_API_SECURITY_ENABLED && isTrue(DD_API_SECURITY_ENABLED)) this._setValue(env, 'appsec.apiSecurity.sampleDelay', maybeFloat(DD_API_SECURITY_SAMPLE_DELAY)) this._setValue(env, 'appsec.blockedTemplateGraphql', maybeFile(DD_APPSEC_GRAPHQL_BLOCKED_TEMPLATE_JSON)) this._setValue(env, 'appsec.blockedTemplateHtml', maybeFile(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML)) @@ -803,10 +792,7 @@ class Config { this._setValue(env, 'appsec.blockedTemplateJson', maybeFile(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON)) this._envUnprocessed['appsec.blockedTemplateJson'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON this._setBoolean(env, 'appsec.enabled', DD_APPSEC_ENABLED) - this._setString(env, 'appsec.eventTracking.mode', coalesce( - DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE, - DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING // TODO: remove in next major - )) + this._setString(env, 'appsec.eventTracking.mode', DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE) this._setBoolean(env, 'appsec.extendedHeadersCollection.enabled', DD_APPSEC_COLLECT_ALL_HEADERS) this._setBoolean( env, @@ -847,7 +833,7 @@ class Config { DD_CODE_ORIGIN_FOR_SPANS_EXPERIMENTAL_EXIT_SPANS_ENABLED ) this._setString(env, 'dbmPropagationMode', DD_DBM_PROPAGATION_MODE) - this._setString(env, 'dogstatsd.hostname', DD_DOGSTATSD_HOST || DD_DOGSTATSD_HOSTNAME) + this._setString(env, 'dogstatsd.hostname', DD_DOGSTATSD_HOST) this._setString(env, 'dogstatsd.port', DD_DOGSTATSD_PORT) this._setBoolean(env, 'dsmEnabled', DD_DATA_STREAMS_ENABLED) this._setBoolean(env, 'dynamicInstrumentation.enabled', DD_DYNAMIC_INSTRUMENTATION_ENABLED) @@ -869,7 +855,7 @@ class Config { this._setIntegerRangeSet(env, 'grpc.client.error.statuses', DD_GRPC_CLIENT_ERROR_STATUSES) this._setIntegerRangeSet(env, 'grpc.server.error.statuses', DD_GRPC_SERVER_ERROR_STATUSES) this._setArray(env, 'headerTags', DD_TRACE_HEADER_TAGS) - this._setString(env, 'hostname', coalesce(DD_AGENT_HOST, DD_TRACE_AGENT_HOSTNAME)) + this._setString(env, 'hostname', DD_AGENT_HOST) this._setValue(env, 'iast.dbRowsToTaint', maybeInt(DD_IAST_DB_ROWS_TO_TAINT)) this._setBoolean(env, 'iast.deduplicationEnabled', DD_IAST_DEDUPLICATION_ENABLED) this._setBoolean(env, 'iast.enabled', DD_IAST_ENABLED) @@ -915,7 +901,6 @@ class Config { this._setString(env, 'port', DD_TRACE_AGENT_PORT) const profilingEnabled = normalizeProfilingEnabledValue( coalesce( - DD_EXPERIMENTAL_PROFILING_ENABLED, DD_PROFILING_ENABLED, this._isInServerlessEnvironment() ? 'false' : undefined ) @@ -953,10 +938,10 @@ class Config { this._setSamplingRule(env, 'sampler.rules', safeJsonParse(DD_TRACE_SAMPLING_RULES)) this._envUnprocessed['sampler.rules'] = DD_TRACE_SAMPLING_RULES this._setString(env, 'scope', DD_TRACE_SCOPE) - this._setString(env, 'service', DD_SERVICE || DD_SERVICE_NAME || tags.service || OTEL_SERVICE_NAME) + this._setString(env, 'service', DD_SERVICE || tags.service || OTEL_SERVICE_NAME) if (DD_SERVICE_MAPPING) { this._setValue(env, 'serviceMapping', Object.fromEntries( - process.env.DD_SERVICE_MAPPING.split(',').map(x => x.trim().split(':')) + DD_SERVICE_MAPPING.split(',').map(x => x.trim().split(':')) )) } this._setString(env, 'site', DD_SITE) @@ -971,8 +956,7 @@ class Config { this._setTags(env, 'tags', tags) this._setValue(env, 'tagsHeaderMaxLength', DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH) this._setBoolean(env, 'telemetry.enabled', coalesce( - DD_TRACE_TELEMETRY_ENABLED, // for backward compatibility - DD_INSTRUMENTATION_TELEMETRY_ENABLED, // to comply with instrumentation telemetry specs + DD_INSTRUMENTATION_TELEMETRY_ENABLED, !(this._isInServerlessEnvironment() || JEST_WORKER_ID) )) this._setString(env, 'instrumentation_config_id', DD_INSTRUMENTATION_CONFIG_ID) @@ -1016,7 +1000,7 @@ class Config { const tags = {} setHiddenProperty(this, '_optsUnprocessed', {}) - options = setHiddenProperty(this, '_optionsArg', Object.assign({ ingestion: {} }, options, opts)) + options = setHiddenProperty(this, '_optionsArg', { ingestion: {}, ...options, ...opts }) tagger.add(tags, options.tags) @@ -1181,20 +1165,19 @@ class Config { _isCiVisibilityItrEnabled () { return coalesce( - process.env.DD_CIVISIBILITY_ITR_ENABLED, + getEnvironmentVariable('DD_CIVISIBILITY_ITR_ENABLED'), true ) } _getHostname () { - const DD_CIVISIBILITY_AGENTLESS_URL = process.env.DD_CIVISIBILITY_AGENTLESS_URL + const DD_CIVISIBILITY_AGENTLESS_URL = getEnvironmentVariable('DD_CIVISIBILITY_AGENTLESS_URL') const url = DD_CIVISIBILITY_AGENTLESS_URL ? new URL(DD_CIVISIBILITY_AGENTLESS_URL) : getAgentUrl(this._getTraceAgentUrl(), this._optionsArg) const DD_AGENT_HOST = coalesce( this._optionsArg.hostname, - process.env.DD_AGENT_HOST, - process.env.DD_TRACE_AGENT_HOSTNAME, + getEnvironmentVariable('DD_AGENT_HOST'), '127.0.0.1' ) return DD_AGENT_HOST || (url && url.hostname) @@ -1204,17 +1187,17 @@ class Config { const DD_TRACE_SPAN_ATTRIBUTE_SCHEMA = validateNamingVersion( coalesce( this._optionsArg.spanAttributeSchema, - process.env.DD_TRACE_SPAN_ATTRIBUTE_SCHEMA + getEnvironmentVariable('DD_TRACE_SPAN_ATTRIBUTE_SCHEMA') ) ) const peerServiceSet = ( this._optionsArg.hasOwnProperty('spanComputePeerService') || - process.env.hasOwnProperty('DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED') + getEnvironmentVariables().hasOwnProperty('DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED') ) const peerServiceValue = coalesce( this._optionsArg.spanComputePeerService, - process.env.DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED + getEnvironmentVariable('DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED') ) const spanComputePeerService = ( @@ -1230,14 +1213,14 @@ class Config { _isCiVisibilityGitUploadEnabled () { return coalesce( - process.env.DD_CIVISIBILITY_GIT_UPLOAD_ENABLED, + getEnvironmentVariable('DD_CIVISIBILITY_GIT_UPLOAD_ENABLED'), true ) } _isCiVisibilityManualApiEnabled () { return coalesce( - process.env.DD_CIVISIBILITY_MANUAL_API_ENABLED, + getEnvironmentVariable('DD_CIVISIBILITY_MANUAL_API_ENABLED'), true ) } @@ -1248,7 +1231,7 @@ class Config { return apmTracingEnabled && coalesce( this._optionsArg.stats, - process.env.DD_TRACE_STATS_COMPUTATION_ENABLED, + getEnvironmentVariable('DD_TRACE_STATS_COMPUTATION_ENABLED'), getIsGCPFunction() || getIsAzureFunction() ) } @@ -1256,8 +1239,7 @@ class Config { _getTraceAgentUrl () { return coalesce( this._optionsArg.url, - process.env.DD_TRACE_AGENT_URL, - process.env.DD_TRACE_URL, + getEnvironmentVariable('DD_TRACE_AGENT_URL'), null ) } @@ -1277,7 +1259,7 @@ class Config { DD_TEST_MANAGEMENT_ENABLED, DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES, DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED - } = process.env + } = getEnvironmentVariables() if (DD_CIVISIBILITY_AGENTLESS_URL) { this._setValue(calc, 'url', new URL(DD_CIVISIBILITY_AGENTLESS_URL)) @@ -1454,12 +1436,7 @@ class Config { obj[name] = value } - // TODO: Report origin changes and errors to telemetry. - // TODO: Deeply merge configurations. - // TODO: Move change tracking to telemetry. - // for telemetry reporting, `name`s in `containers` need to be keys from: - // https://github.com/DataDog/dd-go/blob/prod/trace/apps/tracer-telemetry-intake/telemetry-payload/static/config_norm_rules.json - _merge () { + _getContainersAndOriginsOrdered () { const containers = [ this._remote, this._options, @@ -1478,6 +1455,17 @@ class Config { 'calculated', 'default' ] + + return { containers, origins } + } + + // TODO: Report origin changes and errors to telemetry. + // TODO: Deeply merge configurations. + // TODO: Move change tracking to telemetry. + // for telemetry reporting, `name`s in `containers` need to be keys from: + // https://github.com/DataDog/dd-go/blob/prod/trace/apps/tracer-telemetry-intake/telemetry-payload/static/config_norm_rules.json + _merge () { + const { containers, origins } = this._getContainersAndOriginsOrdered() const unprocessedValues = [ this._remoteUnprocessed, this._optsUnprocessed, @@ -1514,6 +1502,18 @@ class Config { updateConfig(changes, this) } + getOrigin (name) { + const { containers, origins } = this._getContainersAndOriginsOrdered() + + for (let i = 0; i < containers.length; i++) { + const container = containers[i] + const value = container[name] + if (value != null || container === this._defaults) { + return origins[i] + } + } + } + // TODO: Refactor the Config class so it never produces any config objects that are incompatible with MessageChannel /** * Serializes the config object so it can be passed over a Worker Thread MessageChannel. @@ -1536,12 +1536,12 @@ function handleOtel (tagString) { ?.replace(/(^|,)deployment\.environment=/, '$1env:') .replace(/(^|,)service\.name=/, '$1service:') .replace(/(^|,)service\.version=/, '$1version:') - .replace(/=/g, ':') + .replaceAll('=', ':') } function parseSpaceSeparatedTags (tagString) { if (tagString && !tagString.includes(',')) { - tagString = tagString.replace(/\s+/g, ',') + tagString = tagString.replaceAll(/\s+/g, ',') } return tagString } @@ -1564,9 +1564,8 @@ function getAgentUrl (url, options) { if ( !options.hostname && !options.port && - !process.env.DD_AGENT_HOST && - !process.env.DD_TRACE_AGENT_HOSTNAME && - !process.env.DD_TRACE_AGENT_PORT && + !getEnvironmentVariable('DD_AGENT_HOST') && + !getEnvironmentVariable('DD_TRACE_AGENT_PORT') && fs.existsSync('/var/run/datadog/apm.socket') ) { return new URL('unix:///var/run/datadog/apm.socket') diff --git a/packages/dd-trace/src/config_stable.js b/packages/dd-trace/src/config_stable.js index 31fbdcf55c9..fa7855ca3e1 100644 --- a/packages/dd-trace/src/config_stable.js +++ b/packages/dd-trace/src/config_stable.js @@ -1,5 +1,6 @@ const os = require('os') const fs = require('fs') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') class StableConfig { constructor () { @@ -40,6 +41,8 @@ class StableConfig { try { const configurator = new libconfig.JsConfigurator() + // Intentionally pass through the raw environment variables for reporting. + // eslint-disable-next-line eslint-rules/eslint-process-env configurator.set_envp(Object.entries(process.env).map(([key, value]) => `${key}=${value}`)) configurator.set_args(process.argv) configurator.get_configuration(localConfig.toString(), fleetConfig.toString()).forEach((entry) => { @@ -86,11 +89,11 @@ class StableConfig { } // Allow overriding the paths for testing - if (process.env.DD_TEST_LOCAL_CONFIG_PATH !== undefined) { - localConfigPath = process.env.DD_TEST_LOCAL_CONFIG_PATH + if (getEnvironmentVariable('DD_TEST_LOCAL_CONFIG_PATH') !== undefined) { + localConfigPath = getEnvironmentVariable('DD_TEST_LOCAL_CONFIG_PATH') } - if (process.env.DD_TEST_FLEET_CONFIG_PATH !== undefined) { - fleetConfigPath = process.env.DD_TEST_FLEET_CONFIG_PATH + if (getEnvironmentVariable('DD_TEST_FLEET_CONFIG_PATH') !== undefined) { + fleetConfigPath = getEnvironmentVariable('DD_TEST_FLEET_CONFIG_PATH') } return { localConfigPath, fleetConfigPath } diff --git a/packages/dd-trace/src/datastreams/fnv.js b/packages/dd-trace/src/datastreams/fnv.js index d7cdcac8f81..4d04fa0c102 100644 --- a/packages/dd-trace/src/datastreams/fnv.js +++ b/packages/dd-trace/src/datastreams/fnv.js @@ -5,7 +5,7 @@ function fnv (data, hvalInit, fnvPrime, fnvSize) { let hval = hvalInit for (const byte of data) { hval = (hval * fnvPrime) % fnvSize - hval = hval ^ BigInt(byte) + hval ^= BigInt(byte) } return hval } diff --git a/packages/dd-trace/src/datastreams/schemas/schema_builder.js b/packages/dd-trace/src/datastreams/schemas/schema_builder.js index 4ea925d56fa..6e4fee424e1 100644 --- a/packages/dd-trace/src/datastreams/schemas/schema_builder.js +++ b/packages/dd-trace/src/datastreams/schemas/schema_builder.js @@ -117,12 +117,12 @@ function convertKey (key) { function jsonStringify (obj, indent = 2) { // made to stringify json exactly similar to python / java in order for hashing to be the same const jsonString = JSON.stringify(obj, (_, value) => value, indent) - return jsonString.replace(/^ +/gm, ' ') // Replace leading spaces with single space - .replace(/\n/g, '') // Remove newlines - .replace(/{ /g, '{') // Remove space after '{' - .replace(/ }/g, '}') // Remove space before '}' - .replace(/\[ /g, '[') // Remove space after '[' - .replace(/ \]/g, ']') // Remove space before ']' + return jsonString.replaceAll(/^ +/gm, ' ') // Replace leading spaces with single space + .replaceAll('\n', '') // Remove newlines + .replaceAll('{ ', '{') // Remove space after '{' + .replaceAll(' }', '}') // Remove space before '}' + .replaceAll('[ ', '[') // Remove space after '[' + .replaceAll(' ]', ']') // Remove space before ']' } module.exports = { diff --git a/packages/dd-trace/src/debugger/devtools_client/breakpoints.js b/packages/dd-trace/src/debugger/devtools_client/breakpoints.js index d43dd03dd29..56aabf77e44 100644 --- a/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +++ b/packages/dd-trace/src/debugger/devtools_client/breakpoints.js @@ -170,7 +170,7 @@ async function updateBreakpointInternal (breakpoint, probe) { const conditionBeforeNewProbe = compileCompoundCondition([...probesAtLocation.values()]) // If a probe is provided, add it to the breakpoint. If not, it's because we're removing a probe, but potentially - // need to update the condtion of the breakpoint. + // need to update the condition of the breakpoint. if (probe) { probesAtLocation.set(probe.id, probe) probeToLocation.set(probe.id, breakpoint.locationKey) @@ -194,7 +194,6 @@ async function updateBreakpointInternal (breakpoint, probe) { } catch (err) { throw new Error(`Error setting breakpoint for probe ${probe.id}`, { cause: err }) } - breakpoint.id = result.breakpointId breakpointToProbes.set(result.breakpointId, probesAtLocation) } } diff --git a/packages/dd-trace/src/debugger/devtools_client/condition.js b/packages/dd-trace/src/debugger/devtools_client/condition.js index e63316c98c1..e602e3f8801 100644 --- a/packages/dd-trace/src/debugger/devtools_client/condition.js +++ b/packages/dd-trace/src/debugger/devtools_client/condition.js @@ -105,9 +105,8 @@ function compile (node) { return '$dd_key' } else if (value === '@value') { return '$dd_value' - } else { - return assertIdentifier(value) } + return assertIdentifier(value) } else if (Array.isArray(value)) { const args = value.map(compile) switch (type) { diff --git a/packages/dd-trace/src/debugger/devtools_client/index.js b/packages/dd-trace/src/debugger/devtools_client/index.js index 23c95fd71c0..8a75f6fc9c8 100644 --- a/packages/dd-trace/src/debugger/devtools_client/index.js +++ b/packages/dd-trace/src/debugger/devtools_client/index.js @@ -109,6 +109,7 @@ session.on('Debugger.paused', async ({ params }) => { if (probe.condition !== undefined) { // TODO: Bundle all conditions and evaluate them in a single call // TODO: Handle errors + // eslint-disable-next-line no-await-in-loop const { result } = await session.post('Debugger.evaluateOnCallFrame', { callFrameId: params.callFrames[0].callFrameId, expression: probe.condition, @@ -134,7 +135,7 @@ session.on('Debugger.paused', async ({ params }) => { const timestamp = Date.now() - let evalResults = null + let evalResults const { result } = await session.post('Debugger.evaluateOnCallFrame', { callFrameId: params.callFrames[0].callFrameId, expression: templateExpressions.length === 0 diff --git a/packages/dd-trace/src/debugger/devtools_client/send.js b/packages/dd-trace/src/debugger/devtools_client/send.js index eeb4259ca1e..466ed1d47db 100644 --- a/packages/dd-trace/src/debugger/devtools_client/send.js +++ b/packages/dd-trace/src/debugger/devtools_client/send.js @@ -9,6 +9,7 @@ const request = require('../../exporters/common/request') const { GIT_COMMIT_SHA, GIT_REPOSITORY_URL } = require('../../plugins/util/tags') const log = require('../../log') const { version } = require('../../../../../package.json') +const { getEnvironmentVariable } = require('../../config-helper') module.exports = send @@ -21,8 +22,8 @@ const hostname = getHostname() const service = config.service const ddtags = [ - ['env', process.env.DD_ENV], - ['version', process.env.DD_VERSION], + ['env', getEnvironmentVariable('DD_ENV')], + ['version', getEnvironmentVariable('DD_VERSION')], ['debugger_version', version], ['host_name', hostname], [GIT_COMMIT_SHA, config.commitSHA], diff --git a/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js b/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js index b292c2c71e3..c901dba04bc 100644 --- a/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +++ b/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js @@ -77,9 +77,8 @@ function getObjectProperties (subtype, objectId, opts, depth) { return getProxy(objectId, opts, depth) } else if (subtype === 'arraybuffer') { return getArrayBuffer(objectId, opts, depth) - } else { - return getObject(objectId, opts, depth + 1, subtype === 'array' || subtype === 'typedarray') } + return getObject(objectId, opts, depth + 1, subtype === 'array' || subtype === 'typedarray') } // TODO: The following extra information from `internalProperties` might be relevant to include for functions: diff --git a/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js b/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js index dbe08e17d45..f1eeee59ec1 100644 --- a/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +++ b/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js @@ -129,11 +129,10 @@ function toFunctionOrClass (value, maxLength) { // This is a function // TODO: Would it make sense to detect if it's an arrow function or not? return toObject(value.className, value.properties, maxLength) - } else { - // This is a class - const className = classMatch[1].trim() - return { type: className ? `class ${className}` : 'class' } } + // This is a class + const className = classMatch[1].trim() + return { type: className ? `class ${className}` : 'class' } } function toString (str, maxLength) { diff --git a/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js b/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js index 6a83f47eccc..1d9da13b403 100644 --- a/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +++ b/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js @@ -103,7 +103,7 @@ const REDACTED_IDENTIFIERS = new Set( function normalizeName (name, isSymbol) { if (isSymbol) name = name.slice(7, -1) // Remove `Symbol(` and `)` - return name.toLowerCase().replace(/[-_@$.]/g, '') + return name.toLowerCase().replaceAll(/[-_@$.]/g, '') } module.exports = { diff --git a/packages/dd-trace/src/debugger/index.js b/packages/dd-trace/src/debugger/index.js index cd973ffa075..45d10e1e7c7 100644 --- a/packages/dd-trace/src/debugger/index.js +++ b/packages/dd-trace/src/debugger/index.js @@ -9,6 +9,7 @@ let worker = null let configChannel = null let ackId = 0 +// eslint-disable-next-line eslint-rules/eslint-process-env const { NODE_OPTIONS, ...env } = process.env module.exports = { diff --git a/packages/dd-trace/src/dogstatsd.js b/packages/dd-trace/src/dogstatsd.js index b0b767246b4..ed3afdac94b 100644 --- a/packages/dd-trace/src/dogstatsd.js +++ b/packages/dd-trace/src/dogstatsd.js @@ -169,7 +169,7 @@ class DogStatsDClient { }) .forEach(key => { // https://docs.datadoghq.com/tagging/#defining-tags - const value = config.tags[key].replace(/[^a-z0-9_:./-]/ig, '_') + const value = config.tags[key].replaceAll(/[^a-z0-9_:./-]/ig, '_') tags.push(`${key}:${value}`) }) @@ -244,7 +244,7 @@ class MetricsAggregationClient { const container = monotonic ? this._counters : this._gauges const node = this._ensureTree(container, name, tags, 0) - node.value = node.value + count + node.value += count } gauge (name, value, tags) { diff --git a/packages/dd-trace/src/encode/0.4.js b/packages/dd-trace/src/encode/0.4.js index 64af3d9dfd7..9a44397a6ab 100644 --- a/packages/dd-trace/src/encode/0.4.js +++ b/packages/dd-trace/src/encode/0.4.js @@ -6,6 +6,7 @@ const log = require('../log') const { isTrue } = require('../util') const coalesce = require('koalas') const { memoize } = require('../log/utils') +const { getEnvironmentVariable } = require('../config-helper') const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB @@ -32,7 +33,7 @@ class AgentEncoder { this._writer = writer this._reset() this._debugEncoding = isTrue(coalesce( - process.env.DD_TRACE_ENCODING_DEBUG, + getEnvironmentVariable('DD_TRACE_ENCODING_DEBUG'), false )) this._config = this._writer?._config @@ -349,15 +350,17 @@ const memoizedLogDebug = memoize((key, message) => { function formatSpanEvents (span) { for (const spanEvent of span.span_events) { if (spanEvent.attributes) { + let hasAttributes = false for (const [key, value] of Object.entries(spanEvent.attributes)) { const newValue = convertSpanEventAttributeValues(key, value) if (newValue === undefined) { delete spanEvent.attributes[key] // delete from attributes if undefined } else { + hasAttributes = true spanEvent.attributes[key] = newValue } } - if (Object.keys(spanEvent.attributes).length === 0) { + if (!hasAttributes) { delete spanEvent.attributes } } diff --git a/packages/dd-trace/src/encode/0.5.js b/packages/dd-trace/src/encode/0.5.js index 1aa4bc4bfcd..aac4c1d8c05 100644 --- a/packages/dd-trace/src/encode/0.5.js +++ b/packages/dd-trace/src/encode/0.5.js @@ -23,12 +23,10 @@ class AgentEncoder extends BaseEncoder { const traceSize = this._traceBytes.length + 5 const buffer = Buffer.allocUnsafe(prefixSize + stringSize + traceSize) - let offset = 0 + buffer[0] = ARRAY_OF_TWO - buffer[offset++] = ARRAY_OF_TWO - - offset = this._writeStrings(buffer, offset) - offset = this._writeTraces(buffer, offset) + const offset = this._writeStrings(buffer, 1) + this._writeTraces(buffer, offset) this._reset() diff --git a/packages/dd-trace/src/encode/agentless-ci-visibility.js b/packages/dd-trace/src/encode/agentless-ci-visibility.js index ae8200b9ed5..acc1b09d25d 100644 --- a/packages/dd-trace/src/encode/agentless-ci-visibility.js +++ b/packages/dd-trace/src/encode/agentless-ci-visibility.js @@ -161,20 +161,20 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder { _encodeEventContent (bytes, content) { let totalKeysLength = TEST_AND_SPAN_KEYS_LENGTH if (content.meta.test_session_id) { - totalKeysLength = totalKeysLength + 1 + totalKeysLength += 1 } if (content.meta.test_module_id) { - totalKeysLength = totalKeysLength + 1 + totalKeysLength += 1 } if (content.meta.test_suite_id) { - totalKeysLength = totalKeysLength + 1 + totalKeysLength += 1 } const itrCorrelationId = content.meta[ITR_CORRELATION_ID] if (itrCorrelationId) { - totalKeysLength = totalKeysLength + 1 + totalKeysLength += 1 } if (content.type) { - totalKeysLength = totalKeysLength + 1 + totalKeysLength += 1 } this._encodeMapPrefix(bytes, totalKeysLength) if (content.type) { diff --git a/packages/dd-trace/src/exporter.js b/packages/dd-trace/src/exporter.js index 6b93110adc8..6a87eadff75 100644 --- a/packages/dd-trace/src/exporter.js +++ b/packages/dd-trace/src/exporter.js @@ -3,6 +3,7 @@ const exporters = require('../../../ext/exporters') const fs = require('fs') const constants = require('./constants') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') module.exports = function getExporter (name) { switch (name) { @@ -20,7 +21,7 @@ module.exports = function getExporter (name) { case exporters.PLAYWRIGHT_WORKER: return require('./ci-visibility/exporters/test-worker') default: { - const inAWSLambda = process.env.AWS_LAMBDA_FUNCTION_NAME !== undefined + const inAWSLambda = getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') !== undefined const usingLambdaExtension = inAWSLambda && fs.existsSync(constants.DATADOG_LAMBDA_EXTENSION_PATH) return require(inAWSLambda && !usingLambdaExtension ? './exporters/log' : './exporters/agent') } diff --git a/packages/dd-trace/src/exporters/common/docker.js b/packages/dd-trace/src/exporters/common/docker.js index 433e3e74bc5..33f4e539cf7 100644 --- a/packages/dd-trace/src/exporters/common/docker.js +++ b/packages/dd-trace/src/exporters/common/docker.js @@ -1,8 +1,9 @@ 'use strict' const fs = require('fs') +const { getEnvironmentVariable } = require('../../config-helper') -const { DD_EXTERNAL_ENV } = process.env +const DD_EXTERNAL_ENV = getEnvironmentVariable('DD_EXTERNAL_ENV') // The second part is the PCF / Garden regexp. We currently assume no suffix($) to avoid matching pod UIDs // See https://github.com/DataDog/datadog-agent/blob/7.40.x/pkg/util/cgroups/reader.go#L50 @@ -24,7 +25,7 @@ try { const inodePath = cgroup.match(lineReg)?.[3] if (inodePath) { - const strippedPath = inodePath.replace(/^\/|\/$/g, '') + const strippedPath = inodePath.replaceAll(/^\/|\/$/g, '') try { inode = fs.statSync(`/sys/fs/cgroup/${strippedPath}`).ino diff --git a/packages/dd-trace/src/exporters/common/request.js b/packages/dd-trace/src/exporters/common/request.js index 1e44d15cc01..9b5cc05d08e 100644 --- a/packages/dd-trace/src/exporters/common/request.js +++ b/packages/dd-trace/src/exporters/common/request.js @@ -56,9 +56,12 @@ function request (data, options, callback) { const timeout = options.timeout || 2000 const isSecure = options.protocol === 'https:' const client = isSecure ? https : http - const dataArray = [].concat(data) + let dataArray = data if (!isReadable) { + if (!Array.isArray(data)) { + dataArray = [data] + } options.headers['Content-Length'] = byteLength(dataArray) } diff --git a/packages/dd-trace/src/exporters/common/util.js b/packages/dd-trace/src/exporters/common/util.js index 38c190cd0a2..cc1c50c0965 100644 --- a/packages/dd-trace/src/exporters/common/util.js +++ b/packages/dd-trace/src/exporters/common/util.js @@ -1,8 +1,10 @@ +const { getEnvironmentVariable } = require('../../config-helper') + function safeJSONStringify (value) { return JSON.stringify( value, (key, value) => key === 'dd-api-key' ? undefined : value, - process.env.DD_TRACE_BEAUTIFUL_LOGS ? 2 : undefined + getEnvironmentVariable('DD_TRACE_BEAUTIFUL_LOGS') ? 2 : undefined ) } diff --git a/packages/dd-trace/src/id.js b/packages/dd-trace/src/id.js index 988291dd4c0..98f370d52b7 100644 --- a/packages/dd-trace/src/id.js +++ b/packages/dd-trace/src/id.js @@ -174,11 +174,11 @@ function readInt32 (buffer, offset) { // Write unsigned integer bytes to a buffer. function writeUInt32BE (buffer, value, offset) { buffer[3 + offset] = value & 255 - value = value >> 8 + value >>= 8 buffer[2 + offset] = value & 255 - value = value >> 8 + value >>= 8 buffer[1 + offset] = value & 255 - value = value >> 8 + value >>= 8 buffer[0 + offset] = value & 255 } diff --git a/packages/dd-trace/src/index.js b/packages/dd-trace/src/index.js index d761efc1885..f83890f241c 100644 --- a/packages/dd-trace/src/index.js +++ b/packages/dd-trace/src/index.js @@ -1,13 +1,14 @@ 'use strict' const { isFalse } = require('./util') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') // Global `jest` is only present in Jest workers. const inJestWorker = typeof jest !== 'undefined' -const ddTraceDisabled = process.env.DD_TRACE_ENABLED - ? isFalse(process.env.DD_TRACE_ENABLED) - : String(process.env.OTEL_TRACES_EXPORTER).toLowerCase() === 'none' +const ddTraceDisabled = getEnvironmentVariable('DD_TRACE_ENABLED') + ? isFalse(getEnvironmentVariable('DD_TRACE_ENABLED')) + : String(getEnvironmentVariable('OTEL_TRACES_EXPORTER')).toLowerCase() === 'none' module.exports = ddTraceDisabled || inJestWorker ? require('./noop/proxy') diff --git a/packages/dd-trace/src/lambda/handler.js b/packages/dd-trace/src/lambda/handler.js index 8d27dfb9421..4a4f9fa653e 100644 --- a/packages/dd-trace/src/lambda/handler.js +++ b/packages/dd-trace/src/lambda/handler.js @@ -4,6 +4,7 @@ const log = require('../log') const { channel } = require('../../../datadog-instrumentations/src/helpers/instrument') const { ERROR_MESSAGE, ERROR_TYPE } = require('../constants') const { ImpendingTimeout } = require('./runtime/errors') +const { getEnvironmentVariable } = require('../config-helper') const globalTracer = global._ddtrace const tracer = globalTracer._tracer @@ -25,7 +26,7 @@ let __lambdaTimeout function checkTimeout (context) { const remainingTimeInMillis = context.getRemainingTimeInMillis() - let apmFlushDeadline = Number.parseInt(process.env.DD_APM_FLUSH_DEADLINE_MILLISECONDS) || 100 + let apmFlushDeadline = Number.parseInt(getEnvironmentVariable('DD_APM_FLUSH_DEADLINE_MILLISECONDS')) || 100 apmFlushDeadline = apmFlushDeadline < 0 ? 100 : apmFlushDeadline __lambdaTimeout = setTimeout(() => { diff --git a/packages/dd-trace/src/lambda/index.js b/packages/dd-trace/src/lambda/index.js index 6d51c16eec2..ce0a5d783c9 100644 --- a/packages/dd-trace/src/lambda/index.js +++ b/packages/dd-trace/src/lambda/index.js @@ -1,12 +1,13 @@ 'use strict' const { registerLambdaHook } = require('./runtime/ritm') +const { getEnvironmentVariable } = require('../config-helper') /** * It is safe to do it this way, since customers will never be expected to disable * this specific instrumentation through the init config object. */ -const _DD_TRACE_DISABLED_INSTRUMENTATIONS = process.env.DD_TRACE_DISABLED_INSTRUMENTATIONS || '' +const _DD_TRACE_DISABLED_INSTRUMENTATIONS = getEnvironmentVariable('DD_TRACE_DISABLED_INSTRUMENTATIONS') || '' const _disabledInstrumentations = new Set( _DD_TRACE_DISABLED_INSTRUMENTATIONS ? _DD_TRACE_DISABLED_INSTRUMENTATIONS.split(',') : [] ) diff --git a/packages/dd-trace/src/lambda/runtime/patch.js b/packages/dd-trace/src/lambda/runtime/patch.js index 96680770596..f9af356dba6 100644 --- a/packages/dd-trace/src/lambda/runtime/patch.js +++ b/packages/dd-trace/src/lambda/runtime/patch.js @@ -6,6 +6,7 @@ const { _extractModuleNameAndHandlerPath, _extractModuleRootAndHandler, _getLamb const { datadog } = require('../handler') const { addHook } = require('../../../../datadog-instrumentations/src/helpers/instrument') const shimmer = require('../../../../datadog-shimmer') +const { getEnvironmentVariable } = require('../../config-helper') /** * Patches a Datadog Lambda module by calling `patchDatadogLambdaHandler` @@ -57,8 +58,8 @@ function patchLambdaHandler (lambdaHandler) { return datadog(lambdaHandler) } -const lambdaTaskRoot = process.env.LAMBDA_TASK_ROOT -const originalLambdaHandler = process.env.DD_LAMBDA_HANDLER +const lambdaTaskRoot = getEnvironmentVariable('LAMBDA_TASK_ROOT') +const originalLambdaHandler = getEnvironmentVariable('DD_LAMBDA_HANDLER') if (originalLambdaHandler === undefined) { // Instrumentation is done manually. diff --git a/packages/dd-trace/src/lambda/runtime/ritm.js b/packages/dd-trace/src/lambda/runtime/ritm.js index 056d3b62e55..1432dca545a 100644 --- a/packages/dd-trace/src/lambda/runtime/ritm.js +++ b/packages/dd-trace/src/lambda/runtime/ritm.js @@ -10,6 +10,7 @@ const path = require('path') const log = require('../../log') +const { getEnvironmentVariable } = require('../../config-helper') const Hook = require('../../../../datadog-instrumentations/src/helpers/hook') const instrumentations = require('../../../../datadog-instrumentations/src/helpers/instrumentations') const { @@ -78,8 +79,8 @@ function _getLambdaFilePaths (lambdaStylePath) { * the file is required. */ const registerLambdaHook = () => { - const lambdaTaskRoot = process.env.LAMBDA_TASK_ROOT - const originalLambdaHandler = process.env.DD_LAMBDA_HANDLER + const lambdaTaskRoot = getEnvironmentVariable('LAMBDA_TASK_ROOT') + const originalLambdaHandler = getEnvironmentVariable('DD_LAMBDA_HANDLER') if (originalLambdaHandler !== undefined && lambdaTaskRoot !== undefined) { const [moduleRoot, moduleAndHandler] = _extractModuleRootAndHandler(originalLambdaHandler) diff --git a/packages/dd-trace/src/llmobs/constants/tags.js b/packages/dd-trace/src/llmobs/constants/tags.js index 889594f87ab..34e04f4e62a 100644 --- a/packages/dd-trace/src/llmobs/constants/tags.js +++ b/packages/dd-trace/src/llmobs/constants/tags.js @@ -10,6 +10,7 @@ module.exports = { METRICS: '_ml_obs.metrics', ML_APP: '_ml_obs.meta.ml_app', PROPAGATED_PARENT_ID_KEY: '_dd.p.llmobs_parent_id', + PROPAGATED_ML_APP_KEY: '_dd.p.llmobs_ml_app', PARENT_ID_KEY: '_ml_obs.llmobs_parent_id', TAGS: '_ml_obs.tags', NAME: '_ml_obs.name', diff --git a/packages/dd-trace/src/llmobs/index.js b/packages/dd-trace/src/llmobs/index.js index a21c5eed9b2..2778ef51b26 100644 --- a/packages/dd-trace/src/llmobs/index.js +++ b/packages/dd-trace/src/llmobs/index.js @@ -1,7 +1,11 @@ 'use strict' const log = require('../log') -const { PROPAGATED_PARENT_ID_KEY } = require('./constants/tags') +const { + ML_APP, + PROPAGATED_ML_APP_KEY, + PROPAGATED_PARENT_ID_KEY +} = require('./constants/tags') const { storage } = require('./storage') const telemetry = require('./telemetry') @@ -14,6 +18,7 @@ const flushCh = channel('llmobs:writers:flush') const injectCh = channel('dd-trace:span:inject') const LLMObsEvalMetricsWriter = require('./writers/evaluations') +const LLMObsTagger = require('./tagger') const LLMObsSpanWriter = require('./writers/spans') const { setAgentStrategy } = require('./writers/util') @@ -35,7 +40,12 @@ let spanWriter /** @type {LLMObsEvalMetricsWriter | null} */ let evalWriter +/** @type {import('../config')} */ +let globalTracerConfig + function enable (config) { + globalTracerConfig = config + const startTime = performance.now() // create writers and eval writer append and flush channels // span writer append is handled by the span processor @@ -83,14 +93,20 @@ function disable () { } // since LLMObs traces can extend between services and be the same trace, -// we need to propogate the parent id. +// we need to propagate the parent id and mlApp. function handleLLMObsParentIdInjection ({ carrier }) { const parent = storage.getStore()?.span - if (!parent) return + const mlObsSpanTags = LLMObsTagger.tagMap.get(parent) - const parentId = parent?.context().toSpanId() + const parentContext = parent?.context() + const parentId = parentContext?.toSpanId() + const mlApp = + mlObsSpanTags?.[ML_APP] || + parentContext?._trace?.tags?.[PROPAGATED_ML_APP_KEY] || + globalTracerConfig.llmobs.mlApp - carrier['x-datadog-tags'] += `,${PROPAGATED_PARENT_ID_KEY}=${parentId}` + if (parentId) carrier['x-datadog-tags'] += `,${PROPAGATED_PARENT_ID_KEY}=${parentId}` + if (mlApp) carrier['x-datadog-tags'] += `,${PROPAGATED_ML_APP_KEY}=${mlApp}` } function handleFlush () { diff --git a/packages/dd-trace/src/llmobs/noop.js b/packages/dd-trace/src/llmobs/noop.js index d25790cd34c..c3ca24cc6a1 100644 --- a/packages/dd-trace/src/llmobs/noop.js +++ b/packages/dd-trace/src/llmobs/noop.js @@ -44,27 +44,25 @@ class NoopLLMObs { if (ctx.kind !== 'method') return target return llmobs.wrap({ name: ctx.name, _decorator: true, ...options }, target) - } else { - const propertyKey = ctxOrPropertyKey - if (descriptor) { - if (typeof descriptor.value !== 'function') return descriptor - - const original = descriptor.value - descriptor.value = llmobs.wrap({ name: propertyKey, _decorator: true, ...options }, original) - - return descriptor - } else { - if (typeof target[propertyKey] !== 'function') return target[propertyKey] - - const original = target[propertyKey] - Object.defineProperty(target, propertyKey, { - ...Object.getOwnPropertyDescriptor(target, propertyKey), - value: llmobs.wrap({ name: propertyKey, _decorator: true, ...options }, original) - }) - - return target - } } + const propertyKey = ctxOrPropertyKey + if (descriptor) { + if (typeof descriptor.value !== 'function') return descriptor + + const original = descriptor.value + descriptor.value = llmobs.wrap({ name: propertyKey, _decorator: true, ...options }, original) + + return descriptor + } + if (typeof target[propertyKey] !== 'function') return target[propertyKey] + + const original = target[propertyKey] + Object.defineProperty(target, propertyKey, { + ...Object.getOwnPropertyDescriptor(target, propertyKey), + value: llmobs.wrap({ name: propertyKey, _decorator: true, ...options }, original) + }) + + return target } } diff --git a/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js b/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js index d2a0aafdd44..c786ebb8829 100644 --- a/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +++ b/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js @@ -23,26 +23,24 @@ class LangChainLLMObsHandler { return formatted } else if (Array.isArray(messages)) { return messages.map(message => this.formatIO(message)) - } else { // either a BaseMesage type or a string - return this.getContentFromMessage(messages) - } + } // either a BaseMesage type or a string + return this.getContentFromMessage(messages) } getContentFromMessage (message) { if (typeof message === 'string') { return message - } else { - try { - const messageContent = {} - messageContent.content = message.content || '' + } + try { + const messageContent = {} + messageContent.content = message.content || '' - const role = this.getRole(message) - if (role) messageContent.role = role + const role = this.getRole(message) + if (role) messageContent.role = role - return messageContent - } catch { - return JSON.stringify(message) - } + return messageContent + } catch { + return JSON.stringify(message) } } diff --git a/packages/dd-trace/src/llmobs/plugins/openai.js b/packages/dd-trace/src/llmobs/plugins/openai.js index 32db6a9ff07..0f8a6a40e8f 100644 --- a/packages/dd-trace/src/llmobs/plugins/openai.js +++ b/packages/dd-trace/src/llmobs/plugins/openai.js @@ -68,9 +68,8 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin { return { modelProvider: 'azure_openai', client: 'AzureOpenAI' } } else if (baseUrl.includes('deepseek')) { return { modelProvider: 'deepseek', client: 'DeepSeek' } - } else { - return { modelProvider: 'openai', client: 'OpenAI' } } + return { modelProvider: 'openai', client: 'OpenAI' } } _extractMetrics (response) { diff --git a/packages/dd-trace/src/llmobs/sdk.js b/packages/dd-trace/src/llmobs/sdk.js index ecbc35dcea2..182872f51db 100644 --- a/packages/dd-trace/src/llmobs/sdk.js +++ b/packages/dd-trace/src/llmobs/sdk.js @@ -14,6 +14,7 @@ const Span = require('../opentracing/span') const tracerVersion = require('../../../../package.json').version const logger = require('../log') +const { getEnvironmentVariable } = require('../config-helper') const telemetry = require('./telemetry') const LLMObsTagger = require('./tagger') @@ -47,7 +48,7 @@ class LLMObs extends NoopLLMObs { const { mlApp, agentlessEnabled } = options - const { DD_LLMOBS_ENABLED } = process.env + const DD_LLMOBS_ENABLED = getEnvironmentVariable('DD_LLMOBS_ENABLED') const llmobsConfig = { mlApp, diff --git a/packages/dd-trace/src/llmobs/span_processor.js b/packages/dd-trace/src/llmobs/span_processor.js index c719463e5dd..77d6751f574 100644 --- a/packages/dd-trace/src/llmobs/span_processor.js +++ b/packages/dd-trace/src/llmobs/span_processor.js @@ -158,7 +158,7 @@ class LLMObsSpanProcessor { const add = (obj, carrier) => { for (const key in obj) { const value = obj[key] - if (!Object.prototype.hasOwnProperty.call(obj, key)) continue + if (!Object.hasOwn(obj, key)) continue if (typeof value === 'bigint' || isCircular(value)) { // mark as unserializable instead of dropping logger.warn(`Unserializable property found in metadata: ${key}`) diff --git a/packages/dd-trace/src/llmobs/tagger.js b/packages/dd-trace/src/llmobs/tagger.js index aa39e099548..77e922c6216 100644 --- a/packages/dd-trace/src/llmobs/tagger.js +++ b/packages/dd-trace/src/llmobs/tagger.js @@ -24,7 +24,8 @@ const { OUTPUT_TOKENS_METRIC_KEY, TOTAL_TOKENS_METRIC_KEY, INTEGRATION, - DECORATOR + DECORATOR, + PROPAGATED_ML_APP_KEY } = require('./constants/tags') // global registry of LLMObs spans @@ -73,12 +74,24 @@ class LLMObsTagger { if (integration) this._setTag(span, INTEGRATION, integration) if (_decorator) this._setTag(span, DECORATOR, _decorator) - if (!mlApp) mlApp = registry.get(parent)?.[ML_APP] || this._config.llmobs.mlApp - this._setTag(span, ML_APP, mlApp) + const spanMlApp = + mlApp || + registry.get(parent)?.[ML_APP] || + span.context()._trace.tags[PROPAGATED_ML_APP_KEY] || + this._config.llmobs.mlApp + + if (!spanMlApp) { + throw new Error( + '[LLMObs] Cannot start an LLMObs span without an mlApp configured.' + + 'Ensure this configuration is set before running your application.' + ) + } + + this._setTag(span, ML_APP, spanMlApp) const parentId = - parent?.context().toSpanId() || - span.context()._trace.tags[PROPAGATED_PARENT_ID_KEY] || + parent?.context().toSpanId() ?? + span.context()._trace.tags[PROPAGATED_PARENT_ID_KEY] ?? ROOT_PARENT_ID this._setTag(span, PARENT_ID_KEY, parentId) } @@ -357,7 +370,7 @@ class LLMObsTagger { } const tagsCarrier = registry.get(span) - Object.assign(tagsCarrier, { [key]: value }) + tagsCarrier[key] = value } } diff --git a/packages/dd-trace/src/llmobs/writers/base.js b/packages/dd-trace/src/llmobs/writers/base.js index 8f2b8a81977..df3657a9b34 100644 --- a/packages/dd-trace/src/llmobs/writers/base.js +++ b/packages/dd-trace/src/llmobs/writers/base.js @@ -157,7 +157,7 @@ class BaseLLMObsWriter { return encodeUnicode(value) // serialize unicode characters } return value - }).replace(/\\\\u/g, String.raw`\u`) // remove double escaping + }).replaceAll(String.raw`\\u`, String.raw`\u`) // remove double escaping } } diff --git a/packages/dd-trace/src/log/index.js b/packages/dd-trace/src/log/index.js index 99241100448..85b625a10ab 100644 --- a/packages/dd-trace/src/log/index.js +++ b/packages/dd-trace/src/log/index.js @@ -7,6 +7,7 @@ const { traceChannel, debugChannel, infoChannel, warnChannel, errorChannel } = r const logWriter = require('./writer') const { Log } = require('./log') const { memoize } = require('./utils') +const { getEnvironmentVariable } = require('../config-helper') const config = { enabled: false, @@ -98,8 +99,8 @@ const log = { isEnabled (fleetStableConfigValue, localStableConfigValue) { return isTrue(coalesce( fleetStableConfigValue, - process.env?.DD_TRACE_DEBUG, - process.env?.OTEL_LOG_LEVEL === 'debug' || undefined, + getEnvironmentVariable('DD_TRACE_DEBUG'), + getEnvironmentVariable('OTEL_LOG_LEVEL') === 'debug' || undefined, localStableConfigValue, config.enabled )) @@ -113,8 +114,8 @@ const log = { return coalesce( optionsValue, fleetStableConfigValue, - process.env?.DD_TRACE_LOG_LEVEL, - process.env?.OTEL_LOG_LEVEL, + getEnvironmentVariable('DD_TRACE_LOG_LEVEL'), + getEnvironmentVariable('OTEL_LOG_LEVEL'), localStableConfigValue, config.logLevel ) diff --git a/packages/dd-trace/src/log/writer.js b/packages/dd-trace/src/log/writer.js index b599f8b97ee..a629ddf35ef 100644 --- a/packages/dd-trace/src/log/writer.js +++ b/packages/dd-trace/src/log/writer.js @@ -57,9 +57,8 @@ function getErrorLog (err) { if (typeof err?.delegate === 'function') { const result = err.delegate() return Array.isArray(result) ? Log.parse(...result) : Log.parse(result) - } else { - return err } + return err } function setStackTraceLimitFunction (fn) { diff --git a/packages/dd-trace/src/msgpack/encoder.js b/packages/dd-trace/src/msgpack/encoder.js index 61d94c2af8c..c967b1843c6 100644 --- a/packages/dd-trace/src/msgpack/encoder.js +++ b/packages/dd-trace/src/msgpack/encoder.js @@ -163,7 +163,7 @@ class MsgpackEncoder { encodeLong (bytes, value) { const offset = bytes.length - const hi = (value / Math.pow(2, 32)) >> 0 + const hi = (value / 2 ** 32) >> 0 const lo = value >>> 0 bytes.reserve(9) @@ -216,7 +216,7 @@ class MsgpackEncoder { bytes.buffer[offset + 3] = value >> 8 bytes.buffer[offset + 4] = value } else { - const hi = Math.floor(value / Math.pow(2, 32)) + const hi = Math.floor(value / 2 ** 32) const lo = value >>> 0 bytes.reserve(9) @@ -255,7 +255,7 @@ class MsgpackEncoder { bytes.buffer[offset + 3] = value >> 8 bytes.buffer[offset + 4] = value } else { - const hi = (value / Math.pow(2, 32)) >> 0 + const hi = (value / 2 ** 32) >> 0 const lo = value >>> 0 bytes.reserve(9) diff --git a/packages/dd-trace/src/noop/span.js b/packages/dd-trace/src/noop/span.js index 4ffb0f2a156..288da533b66 100644 --- a/packages/dd-trace/src/noop/span.js +++ b/packages/dd-trace/src/noop/span.js @@ -36,7 +36,7 @@ class NoopSpan { traceId: parent._traceId, spanId, parentId: parent._spanId, - baggageItems: Object.assign({}, parent._baggageItems) + baggageItems: { ...parent._baggageItems } }) : new NoopSpanContext({ noop: this, diff --git a/packages/dd-trace/src/opentelemetry/tracer.js b/packages/dd-trace/src/opentelemetry/tracer.js index f23f13cba7f..5e6b538cc93 100644 --- a/packages/dd-trace/src/opentelemetry/tracer.js +++ b/packages/dd-trace/src/opentelemetry/tracer.js @@ -32,7 +32,7 @@ class Tracer { spanId: id(), parentId: parentSpanContext._spanId, sampling: parentSpanContext._sampling, - baggageItems: Object.assign({}, parentSpanContext._baggageItems), + baggageItems: { ...parentSpanContext._baggageItems }, trace: parentSpanContext._trace, tracestate: parentSpanContext._tracestate }) diff --git a/packages/dd-trace/src/opentracing/propagation/log.js b/packages/dd-trace/src/opentracing/propagation/log.js index 21da6cb38da..c606cb00f71 100644 --- a/packages/dd-trace/src/opentracing/propagation/log.js +++ b/packages/dd-trace/src/opentracing/propagation/log.js @@ -43,12 +43,11 @@ class LogPropagator { spanContext._trace.tags['_dd.p.tid'] = hi return spanContext - } else { - return new DatadogSpanContext({ - traceId: id(carrier.dd.trace_id, 10), - spanId: id(carrier.dd.span_id, 10) - }) } + return new DatadogSpanContext({ + traceId: id(carrier.dd.trace_id, 10), + spanId: id(carrier.dd.span_id, 10) + }) } } diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index fabb4616ebc..55935034c2d 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -239,8 +239,8 @@ class TextMapPropagator { if (typeof origin === 'string') { const originValue = origin - .replace(tracestateOriginFilter, '_') - .replace(/[\x3D]/g, '~') + .replaceAll(tracestateOriginFilter, '_') + .replaceAll(/[\x3D]/g, '~') state.set('o', originValue) } @@ -249,12 +249,12 @@ class TextMapPropagator { if (!tags[key] || !key.startsWith('_dd.p.')) continue const tagKey = 't.' + key.slice(6) - .replace(tracestateTagKeyFilter, '_') + .replaceAll(tracestateTagKeyFilter, '_') const tagValue = tags[key] .toString() - .replace(tracestateTagValueFilter, '_') - .replace(/[\x3D]/g, '~') + .replaceAll(tracestateTagValueFilter, '_') + .replaceAll(/[\x3D]/g, '~') state.set(tagKey, tagValue) } @@ -508,7 +508,7 @@ class TextMapPropagator { default: { if (!key.startsWith('t.')) continue const subKey = key.slice(2) // e.g. t.tid -> tid - const transformedValue = value.replace(/[\x7E]/gm, '=') + const transformedValue = value.replaceAll(/[\x7E]/gm, '=') // If subkey is tid then do nothing because trace header tid should always be preserved if (subKey === 'tid') { @@ -581,22 +581,21 @@ class TextMapPropagator { return { [b3SampledKey]: parts[0] } - } else { - const b3 = { - [b3TraceKey]: parts[0], - [b3SpanKey]: parts[1] - } + } + const b3 = { + [b3TraceKey]: parts[0], + [b3SpanKey]: parts[1] + } - if (parts[2]) { - b3[b3SampledKey] = parts[2] === '0' ? '0' : '1' + if (parts[2]) { + b3[b3SampledKey] = parts[2] === '0' ? '0' : '1' - if (parts[2] === 'd') { - b3[b3FlagsKey] = '1' - } + if (parts[2] === 'd') { + b3[b3FlagsKey] = '1' } - - return b3 } + + return b3 } _extractOrigin (carrier, spanContext) { diff --git a/packages/dd-trace/src/opentracing/span.js b/packages/dd-trace/src/opentracing/span.js index bedbd5dc001..27c67e474e1 100644 --- a/packages/dd-trace/src/opentracing/span.js +++ b/packages/dd-trace/src/opentracing/span.js @@ -13,18 +13,17 @@ const { storage } = require('../../../datadog-core') const telemetryMetrics = require('../telemetry/metrics') const { channel } = require('dc-polyfill') const util = require('util') +const { getEnvironmentVariable } = require('../config-helper') const tracerMetrics = telemetryMetrics.manager.namespace('tracers') -const { - DD_TRACE_EXPERIMENTAL_STATE_TRACKING, - DD_TRACE_EXPERIMENTAL_SPAN_COUNTS -} = process.env +const DD_TRACE_EXPERIMENTAL_STATE_TRACKING = getEnvironmentVariable('DD_TRACE_EXPERIMENTAL_STATE_TRACKING') +const DD_TRACE_EXPERIMENTAL_SPAN_COUNTS = getEnvironmentVariable('DD_TRACE_EXPERIMENTAL_SPAN_COUNTS') const unfinishedRegistry = createRegistry('unfinished') const finishedRegistry = createRegistry('finished') -const OTEL_ENABLED = !!process.env.DD_TRACE_OTEL_ENABLED +const OTEL_ENABLED = !!getEnvironmentVariable('DD_TRACE_OTEL_ENABLED') const ALLOWED = new Set(['string', 'number', 'boolean']) const integrationCounters = { @@ -56,6 +55,8 @@ class DatadogSpan { constructor (tracer, processor, prioritySampler, fields, debug) { const operationName = fields.operationName const parent = fields.parent || null + // TODO(BridgeAR): Investigate why this is causing a performance regression + // eslint-disable-next-line prefer-object-spread const tags = Object.assign({}, fields.tags) const hostname = fields.hostname @@ -333,7 +334,7 @@ class DatadogSpan { spanId: id(), parentId: parent._spanId, sampling: parent._sampling, - baggageItems: Object.assign({}, parent._baggageItems), + baggageItems: { ...parent._baggageItems }, trace: parent._trace, tracestate: parent._tracestate }) diff --git a/packages/dd-trace/src/payload-tagging/config/index.js b/packages/dd-trace/src/payload-tagging/config/index.js index 16ab4dfd814..a5c8dc6cf1f 100644 --- a/packages/dd-trace/src/payload-tagging/config/index.js +++ b/packages/dd-trace/src/payload-tagging/config/index.js @@ -2,29 +2,25 @@ const aws = require('./aws.json') const sdks = { aws } function getSDKRules (sdk, requestInput, responseInput) { - return Object.fromEntries( - Object.entries(sdk).map(([service, serviceRules]) => { - return [ - service, - { - request: serviceRules.request.concat(requestInput || []), - response: serviceRules.response.concat(responseInput || []), - expand: serviceRules.expand || [] - } - ] - }) - ) + const sdkServiceRules = {} + for (const [service, serviceRules] of Object.entries(sdk)) { + sdkServiceRules[service] = { + // Make a copy. Otherwise calling the function multiple times would append + // the rules to the same object. + request: [...serviceRules.request, ...requestInput], + response: [...serviceRules.response, ...responseInput], + expand: serviceRules.expand + } + } + return sdkServiceRules } -function appendRules (requestInput, responseInput) { - return Object.fromEntries( - Object.entries(sdks).map(([name, sdk]) => { - return [ - name, - getSDKRules(sdk, requestInput, responseInput) - ] - }) - ) +function appendRules (requestInput = [], responseInput = []) { + const sdkRules = {} + for (const [name, sdk] of Object.entries(sdks)) { + sdkRules[name] = getSDKRules(sdk, requestInput, responseInput) + } + return sdkRules } module.exports = { appendRules } diff --git a/packages/dd-trace/src/plugin_manager.js b/packages/dd-trace/src/plugin_manager.js index 4850e298e1b..8a935a04a0f 100644 --- a/packages/dd-trace/src/plugin_manager.js +++ b/packages/dd-trace/src/plugin_manager.js @@ -4,17 +4,18 @@ const { channel } = require('dc-polyfill') const { isFalse } = require('./util') const plugins = require('./plugins') const log = require('./log') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') const loadChannel = channel('dd-trace:instrumentation:load') // instrument everything that needs Plugin System V2 instrumentation require('../../datadog-instrumentations') -if (process.env.AWS_LAMBDA_FUNCTION_NAME !== undefined) { +if (getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') !== undefined) { // instrument lambda environment require('./lambda') } -const { DD_TRACE_DISABLED_PLUGINS } = process.env +const DD_TRACE_DISABLED_PLUGINS = getEnvironmentVariable('DD_TRACE_DISABLED_PLUGINS') const disabledPlugins = new Set( DD_TRACE_DISABLED_PLUGINS && DD_TRACE_DISABLED_PLUGINS.split(',').map(plugin => plugin.trim()) @@ -32,7 +33,7 @@ function maybeEnable (Plugin) { if (!Plugin || typeof Plugin !== 'function') return if (!pluginClasses[Plugin.id]) { const envName = `DD_TRACE_${Plugin.id.toUpperCase()}_ENABLED` - const enabled = process.env[envName.replace(/[^a-z0-9_]/ig, '_')] + const enabled = getEnvironmentVariable(envName.replaceAll(/[^a-z0-9_]/ig, '_')) // TODO: remove the need to load the plugin class in order to disable the plugin if (isFalse(enabled) || disabledPlugins.has(Plugin.id)) { diff --git a/packages/dd-trace/src/plugins/ci_plugin.js b/packages/dd-trace/src/plugins/ci_plugin.js index 9d6becf7086..4730969d546 100644 --- a/packages/dd-trace/src/plugins/ci_plugin.js +++ b/packages/dd-trace/src/plugins/ci_plugin.js @@ -1,3 +1,4 @@ +const { storage } = require('../../../datadog-core') const { getTestEnvironmentMetadata, getTestSessionName, @@ -73,7 +74,10 @@ module.exports = class CiPlugin extends Plugin { this.fileLineToProbeId = new Map() this.rootDir = process.cwd() // fallback in case :session:start events are not emitted - this.addSub(`ci:${this.constructor.id}:library-configuration`, ({ onDone, isParallel }) => { + this.addSub(`ci:${this.constructor.id}:library-configuration`, (ctx) => { + const { onDone, isParallel } = ctx + ctx.currentStore = storage('legacy').getStore() + if (!this.tracer._exporter || !this.tracer._exporter.getLibraryConfiguration) { return onDone({ err: new Error('Test optimization was not initialized correctly') }) } @@ -95,6 +99,10 @@ module.exports = class CiPlugin extends Plugin { }) }) + this.addBind(`ci:${this.constructor.id}:test-suite:skippable`, (ctx) => { + return ctx.currentStore + }) + this.addSub(`ci:${this.constructor.id}:test-suite:skippable`, ({ onDone }) => { if (!this.tracer._exporter?.getSkippableSuites) { return onDone({ err: new Error('Test optimization was not initialized correctly') }) @@ -158,8 +166,12 @@ module.exports = class CiPlugin extends Plugin { // only for vitest // These are added for the worker threads to use if (this.constructor.id === 'vitest') { + // TODO: Figure out alternative ways to pass this information to the worker threads + // eslint-disable-next-line eslint-rules/eslint-process-env process.env.DD_CIVISIBILITY_TEST_SESSION_ID = this.testSessionSpan.context().toTraceId() + // eslint-disable-next-line eslint-rules/eslint-process-env process.env.DD_CIVISIBILITY_TEST_MODULE_ID = this.testModuleSpan.context().toSpanId() + // eslint-disable-next-line eslint-rules/eslint-process-env process.env.DD_CIVISIBILITY_TEST_COMMAND = this.command } @@ -188,6 +200,10 @@ module.exports = class CiPlugin extends Plugin { this.telemetry.count(TELEMETRY_ITR_SKIPPED, { testLevel: 'suite' }, skippedSuites.length) }) + this.addBind(`ci:${this.constructor.id}:known-tests`, (ctx) => { + return ctx.currentStore + }) + this.addSub(`ci:${this.constructor.id}:known-tests`, ({ onDone }) => { if (!this.tracer._exporter?.getKnownTests) { return onDone({ err: new Error('Test optimization was not initialized correctly') }) @@ -202,6 +218,10 @@ module.exports = class CiPlugin extends Plugin { }) }) + this.addBind(`ci:${this.constructor.id}:test-management-tests`, (ctx) => { + return ctx.currentStore + }) + this.addSub(`ci:${this.constructor.id}:test-management-tests`, ({ onDone }) => { if (!this.tracer._exporter?.getTestManagementTests) { return onDone({ err: new Error('Test optimization was not initialized correctly') }) @@ -215,6 +235,10 @@ module.exports = class CiPlugin extends Plugin { }) }) + this.addBind(`ci:${this.constructor.id}:modified-tests`, (ctx) => { + return ctx.currentStore + }) + this.addSub(`ci:${this.constructor.id}:modified-tests`, ({ onDone }) => { const { [GIT_PULL_REQUEST_BASE_BRANCH]: pullRequestBaseBranch, diff --git a/packages/dd-trace/src/plugins/plugin.js b/packages/dd-trace/src/plugins/plugin.js index a19fceab123..b3bb6da20e9 100644 --- a/packages/dd-trace/src/plugins/plugin.js +++ b/packages/dd-trace/src/plugins/plugin.js @@ -34,7 +34,7 @@ class StoreBinding { this._transform = data => { const store = storage('legacy').getStore() - return !store || !store.noop || data?.currentStore + return !store || !store.noop || (data && Object.hasOwn(data, 'currentStore')) ? transform(data) : store } diff --git a/packages/dd-trace/src/plugins/util/ci.js b/packages/dd-trace/src/plugins/util/ci.js index 8c3a62ffd09..bbdb4c87104 100644 --- a/packages/dd-trace/src/plugins/util/ci.js +++ b/packages/dd-trace/src/plugins/util/ci.js @@ -27,6 +27,7 @@ const { CI_NODE_NAME } = require('./tags') const { filterSensitiveInfoFromRepository } = require('./url') +const { getEnvironmentVariable } = require('../../config-helper') // Receives a string with the form 'John Doe ' // and returns { name: 'John Doe', email: 'john.doe@gmail.com' } @@ -67,7 +68,7 @@ function normalizeRef (ref) { if (!ref) { return ref } - return ref.replace(/origin\/|refs\/heads\/|tags\//gm, '') + return ref.replaceAll(/origin\/|refs\/heads\/|tags\//gm, '') } function resolveTilde (filePath) { @@ -76,16 +77,16 @@ function resolveTilde (filePath) { } // '~/folder/path' or '~' if (filePath[0] === '~' && (filePath[1] === '/' || filePath.length === 1)) { - return filePath.replace('~', process.env.HOME) + return filePath.replace('~', getEnvironmentVariable('HOME')) } return filePath } function getGitHubEventPayload () { - if (!process.env.GITHUB_EVENT_PATH) { + if (!getEnvironmentVariable('GITHUB_EVENT_PATH')) { return } - return JSON.parse(readFileSync(process.env.GITHUB_EVENT_PATH, 'utf8')) + return JSON.parse(readFileSync(getEnvironmentVariable('GITHUB_EVENT_PATH'), 'utf8')) } module.exports = { @@ -139,11 +140,10 @@ module.exports = { tags[refKey] = ref - let finalPipelineName = '' if (JOB_NAME) { // Job names can contain parameters, e.g. jobName/KEY1=VALUE1,KEY2=VALUE2/branchName const jobNameAndParams = JOB_NAME.split('/') - finalPipelineName = jobNameAndParams.length > 1 && jobNameAndParams[1].includes('=') + const finalPipelineName = jobNameAndParams.length > 1 && jobNameAndParams[1].includes('=') ? jobNameAndParams[0] : JOB_NAME.replace(`/${ref}`, '') tags[CI_PIPELINE_NAME] = finalPipelineName @@ -432,7 +432,7 @@ module.exports = { [GIT_TAG]: BITBUCKET_TAG, [GIT_REPOSITORY_URL]: BITBUCKET_GIT_SSH_ORIGIN || BITBUCKET_GIT_HTTP_ORIGIN, [CI_WORKSPACE_PATH]: BITBUCKET_CLONE_DIR, - [CI_PIPELINE_ID]: BITBUCKET_PIPELINE_UUID && BITBUCKET_PIPELINE_UUID.replace(/{|}/gm, ''), + [CI_PIPELINE_ID]: BITBUCKET_PIPELINE_UUID && BITBUCKET_PIPELINE_UUID.replaceAll(/{|}/gm, ''), [GIT_PULL_REQUEST_BASE_BRANCH]: BITBUCKET_PR_DESTINATION_BRANCH } } diff --git a/packages/dd-trace/src/plugins/util/git.js b/packages/dd-trace/src/plugins/util/git.js index d2d5a76cd7f..a2e11138199 100644 --- a/packages/dd-trace/src/plugins/util/git.js +++ b/packages/dd-trace/src/plugins/util/git.js @@ -51,7 +51,7 @@ function sanitizedExec ( try { let result = cp.execFileSync(cmd, flags, { stdio: 'pipe' }).toString() if (shouldTrim) { - result = result.replace(/(\r\n|\n|\r)/gm, '') + result = result.replaceAll(/(\r\n|\n|\r)/gm, '') } if (durationMetric) { distributionMetric(durationMetric.name, durationMetric.tags, Date.now() - startTime) diff --git a/packages/dd-trace/src/plugins/util/llm.js b/packages/dd-trace/src/plugins/util/llm.js index 3d2d32d4809..22a7ad211dc 100644 --- a/packages/dd-trace/src/plugins/util/llm.js +++ b/packages/dd-trace/src/plugins/util/llm.js @@ -11,8 +11,8 @@ function normalize (text, limit = 128) { } text = text - .replace(RE_NEWLINE, String.raw`\n`) - .replace(RE_TAB, String.raw`\t`) + .replaceAll(RE_NEWLINE, String.raw`\n`) + .replaceAll(RE_TAB, String.raw`\t`) // In case the replace above matched, more characters were added that must now be considered. if (text.length > limit) { diff --git a/packages/dd-trace/src/plugins/util/stacktrace.js b/packages/dd-trace/src/plugins/util/stacktrace.js index 81e627542bc..04cf78cab5f 100644 --- a/packages/dd-trace/src/plugins/util/stacktrace.js +++ b/packages/dd-trace/src/plugins/util/stacktrace.js @@ -34,7 +34,14 @@ function getCallSites (constructorOpt) { * * @param {Function} constructorOpt - Function to pass along to Error.captureStackTrace * @param {number} [limit=Infinity] - The maximum number of frames to return - * @returns {{ file: string, line: number, method: (string|undefined), type: (string|undefined) }[]} - A + * @returns {StackFrame[]} - A list of stack frames from user-land code + * + * @typedef {Object} StackFrame + * @property {string} file - The file path of the frame + * @property {number} line - The line number in the file + * @property {number} column - The column number in the file + * @property {string} [method] - The function name, if available + * @property {string} [type] - The type name, if available */ function getUserLandFrames (constructorOpt, limit = Infinity) { const callsites = getCallSites(constructorOpt) diff --git a/packages/dd-trace/src/plugins/util/test.js b/packages/dd-trace/src/plugins/util/test.js index caf1e809236..1a11f4bc7ac 100644 --- a/packages/dd-trace/src/plugins/util/test.js +++ b/packages/dd-trace/src/plugins/util/test.js @@ -2,6 +2,7 @@ const path = require('path') const fs = require('fs') const { URL } = require('url') const log = require('../../log') +const { getEnvironmentVariable } = require('../../config-helper') const istanbul = require('istanbul-lib-coverage') const ignore = require('ignore') @@ -284,7 +285,7 @@ module.exports = { // Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19 function getPkgManager () { try { - return process.env.npm_config_user_agent.split(' ')[0].replace('/', '-') + return getEnvironmentVariable('npm_config_user_agent').split(' ')[0].replace('/', '-') } catch { return '' } @@ -789,11 +790,11 @@ function addAttemptToFixStringToTestName (testName, numAttempt) { } function removeEfdStringFromTestName (testName) { - return testName.replace(EFD_TEST_NAME_REGEX, '') + return testName.replaceAll(EFD_TEST_NAME_REGEX, '') } function removeAttemptToFixStringFromTestName (testName) { - return testName.replace(ATTEMPT_TEST_NAME_REGEX, '') + return testName.replaceAll(ATTEMPT_TEST_NAME_REGEX, '') } function getIsFaultyEarlyFlakeDetection (projectSuites, testsBySuiteName, faultyThresholdPercentage) { diff --git a/packages/dd-trace/src/plugins/util/user-provided-git.js b/packages/dd-trace/src/plugins/util/user-provided-git.js index 65863ca7670..c2195cc92e4 100644 --- a/packages/dd-trace/src/plugins/util/user-provided-git.js +++ b/packages/dd-trace/src/plugins/util/user-provided-git.js @@ -17,6 +17,7 @@ const { const { normalizeRef } = require('./ci') const { filterSensitiveInfoFromRepository } = require('./url') +const { getEnvironmentVariables } = require('../../config-helper') function removeEmptyValues (tags) { return Object.keys(tags).reduce((filteredTags, tag) => { @@ -59,7 +60,7 @@ function getUserProviderGitMetadata () { DD_GIT_PULL_REQUEST_BASE_BRANCH, DD_GIT_PULL_REQUEST_BASE_BRANCH_SHA, DD_GIT_COMMIT_HEAD_SHA - } = process.env + } = getEnvironmentVariables() const branch = normalizeRef(DD_GIT_BRANCH) let tag = normalizeRef(DD_GIT_TAG) diff --git a/packages/dd-trace/src/plugins/util/web.js b/packages/dd-trace/src/plugins/util/web.js index 817558f010e..0e45bc36753 100644 --- a/packages/dd-trace/src/plugins/util/web.js +++ b/packages/dd-trace/src/plugins/util/web.js @@ -421,7 +421,7 @@ function addAllowHeaders (req, res, headers) { ] for (const header of contextHeaders) { - if (~requestHeaders.indexOf(header)) { + if (requestHeaders.includes(header)) { allowHeaders.push(header) } } @@ -530,10 +530,9 @@ function extractURL (req) { if (req.stream) { return `${headers[HTTP2_HEADER_SCHEME]}://${headers[HTTP2_HEADER_AUTHORITY]}${headers[HTTP2_HEADER_PATH]}` - } else { - const protocol = getProtocol(req) - return `${protocol}://${req.headers.host}${req.originalUrl || req.url}` } + const protocol = getProtocol(req) + return `${protocol}://${req.headers.host}${req.originalUrl || req.url}` } function getProtocol (req) { diff --git a/packages/dd-trace/src/priority_sampler.js b/packages/dd-trace/src/priority_sampler.js index 8883759936d..0a0c0bbb991 100644 --- a/packages/dd-trace/src/priority_sampler.js +++ b/packages/dd-trace/src/priority_sampler.js @@ -5,7 +5,6 @@ const RateLimiter = require('./rate_limiter') const Sampler = require('./sampler') const { setSamplingRules } = require('./startup-log') const SamplingRule = require('./sampling_rule') -const { hasOwn } = require('./util') const { SAMPLING_MECHANISM_DEFAULT, @@ -40,16 +39,28 @@ const DEFAULT_KEY = 'service:,env:' const defaultSampler = new Sampler(AUTO_KEEP) /** - * from config.js - * @typedef { sampleRate: number, provenance: string, rateLimit: number, rules: SamplingRule[] } SamplingConfig + * PrioritySampler is responsible for determining whether a span should be sampled + * based on various rules, rate limits, and priorities. It supports manual and + * automatic sampling mechanisms and integrates with Datadog's tracing system. * - * empirically defined - * @typedef {2|-1|1|0} SamplingPriority + * @class PrioritySampler + * @typedef {import('./opentracing/span')} DatadogSpan + * @typedef {import('./opentracing/span_context')} DatadogSpanContext + * @typedef {import('./standalone/product')} PRODUCTS + * @typedef {2|-1|1|0} SamplingPriority Empirically defined sampling priorities. */ class PrioritySampler { /** - * @param env {string} - * @param config {SamplingConfig} + * Creates an instance of PrioritySampler. + * + * @typedef {Object} SamplingConfig + * @property {number} [sampleRate] - The default sample rate for traces. + * @property {string} [provenance] - The provenance of the sampling rule (e.g., "customer", "dynamic"). + * @property {number} [rateLimit=100] - The maximum number of traces to sample per second. + * @property {Array} [rules=[]] - An array of sampling rules to apply. + * + * @param {string} env - The environment name (e.g., "production", "staging"). + * @param {SamplingConfig} config - The configuration object for sampling. */ constructor (env, config) { this.configure(env, config) @@ -62,9 +73,9 @@ class PrioritySampler { * @param opts {SamplingConfig} */ configure (env, opts = {}) { - const { sampleRate, provenance, rateLimit = 100, rules = [] } = opts + const { sampleRate, provenance, rateLimit = 100, rules } = opts this._env = env - this._rules = this.#normalizeRules(rules, sampleRate, rateLimit, provenance) + this._rules = this.#normalizeRules(rules || [], sampleRate, rateLimit, provenance) this._limiter = new RateLimiter(rateLimit) log.trace(env, opts) @@ -154,7 +165,7 @@ class PrioritySampler { * * @param span {DatadogSpan} * @param samplingPriority {SamplingPriority} - * @param product {import('./standalone/product').PRODUCTS} + * @param product {import('./standalone/product')} */ setPriority (span, samplingPriority, product) { if (!span || !this.validate(samplingPriority)) return @@ -208,18 +219,17 @@ class PrioritySampler { * @returns {SamplingPriority} */ _getPriorityFromTags (tags, _context) { - if (hasOwn(tags, MANUAL_KEEP) && tags[MANUAL_KEEP] !== false) { + if (Object.hasOwn(tags, MANUAL_KEEP) && tags[MANUAL_KEEP] !== false) { return USER_KEEP - } else if (hasOwn(tags, MANUAL_DROP) && tags[MANUAL_DROP] !== false) { + } else if (Object.hasOwn(tags, MANUAL_DROP) && tags[MANUAL_DROP] !== false) { return USER_REJECT - } else { - const priority = Number.parseInt(tags[SAMPLING_PRIORITY], 10) + } + const priority = Number.parseInt(tags[SAMPLING_PRIORITY], 10) - if (priority === 1 || priority === 2) { - return USER_KEEP - } else if (priority === 0 || priority === -1) { - return USER_REJECT - } + if (priority === 1 || priority === 2) { + return USER_KEEP + } else if (priority === 0 || priority === -1) { + return USER_REJECT } } @@ -258,7 +268,6 @@ class PrioritySampler { * * @param context {DatadogSpanContext} * @returns {SamplingPriority} - * @private */ #getPriorityByAgent (context) { const key = `service:${context._tags[SERVICE_NAME]},env:${this._env}` @@ -274,7 +283,6 @@ class PrioritySampler { /** * * @param span {DatadogSpan} - * @private * @returns {void} */ #addDecisionMaker (span) { @@ -293,30 +301,33 @@ class PrioritySampler { } /** - * - * @param rules {SamplingRule[]} - * @param sampleRate {number} - * @param rateLimit {number} - * @param provenance {string} + * @param {Record[] | Record} rules - The sampling rules to normalize. + * @param {number} sampleRate + * @param {number} rateLimit + * @param {string} provenance * @returns {SamplingRule[]} - * @private */ #normalizeRules (rules, sampleRate, rateLimit, provenance) { - rules = [].concat(rules || []) + rules = Array.isArray(rules) ? rules.flat() : [rules] rules.push({ sampleRate, maxPerSecond: rateLimit, provenance }) - return rules - .map(rule => ({ ...rule, sampleRate: Number.parseFloat(rule.sampleRate) })) - .filter(rule => !Number.isNaN(rule.sampleRate)) - .map(SamplingRule.from) + const result = [] + for (const rule of rules) { + const sampleRate = Number.parseFloat(rule.sampleRate) + // TODO(BridgeAR): Debug logging invalid rules fails our tests. + // Should we definitely not know about these? + if (!Number.isNaN(sampleRate)) { + result.push(SamplingRule.from({ ...rule, sampleRate })) + } + } + return result } /** * * @param span {DatadogSpan} - * @returns {SamplingRule} - * @private + * @returns {SamplingRule|undefined} */ #findRule (span) { for (const rule of this._rules) { @@ -330,7 +341,7 @@ class PrioritySampler { /** * * @param span {DatadogSpan} - * @param product {import('./standalone/product').PRODUCTS} + * @param product {import('./standalone/product')} */ static keepTrace (span, product) { span?._prioritySampler?.setPriority(span, USER_KEEP, product) diff --git a/packages/dd-trace/src/profiling/config.js b/packages/dd-trace/src/profiling/config.js index 313e1c28595..9cddf3769c7 100644 --- a/packages/dd-trace/src/profiling/config.js +++ b/packages/dd-trace/src/profiling/config.js @@ -15,10 +15,12 @@ const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags') const { tagger } = require('./tagger') const { isFalse, isTrue } = require('../util') const { getAzureTagsFromMetadata, getAzureAppMetadata } = require('../azure_metadata') +const { getEnvironmentVariables } = require('../config-helper') class Config { constructor (options = {}) { const { + AWS_LAMBDA_FUNCTION_NAME: functionname, DD_AGENT_HOST, DD_ENV, DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED, // used for testing @@ -27,14 +29,10 @@ class Config { DD_PROFILING_DEBUG_SOURCE_MAPS, DD_PROFILING_DEBUG_UPLOAD_COMPRESSION, DD_PROFILING_ENDPOINT_COLLECTION_ENABLED, - DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, - DD_PROFILING_EXPERIMENTAL_CPU_ENABLED, - DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED, DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES, DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE, DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT, DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED, - DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED, DD_PROFILING_HEAP_ENABLED, DD_PROFILING_HEAP_SAMPLING_INTERVAL, DD_PROFILING_PPROF_PREFIX, @@ -50,13 +48,12 @@ class Config { DD_TRACE_AGENT_PORT, DD_TRACE_AGENT_URL, DD_VERSION - } = process.env + } = getEnvironmentVariables() const env = coalesce(options.env, DD_ENV) const service = options.service || DD_SERVICE || 'node' const host = os.hostname() const version = coalesce(options.version, DD_VERSION) - const functionname = process.env.AWS_LAMBDA_FUNCTION_NAME // Must be longer than one minute so pad with five seconds const flushInterval = coalesce(options.interval, Number(DD_PROFILING_UPLOAD_PERIOD) * 1000, 65 * 1000) const uploadTimeout = coalesce(options.uploadTimeout, @@ -86,16 +83,6 @@ class Config { } this.logger = ensureLogger(options.logger) - const logger = this.logger - function logExperimentalVarDeprecation (shortVarName) { - const deprecatedEnvVarName = `DD_PROFILING_EXPERIMENTAL_${shortVarName}` - const v = process.env[deprecatedEnvVarName] - // not null, undefined, or NaN -- same logic as koalas.hasValue - // eslint-disable-next-line no-self-compare - if (v != null && v === v) { - logger.warn(`${deprecatedEnvVarName} is deprecated. Use DD_PROFILING_${shortVarName} instead.`) - } - } // Profiler sampling contexts are not available on Windows, so features // depending on those (code hotspots and endpoint collection) need to default // to false on Windows. @@ -119,9 +106,7 @@ class Config { this.sourceMap = sourceMap this.debugSourceMaps = isTrue(coalesce(options.debugSourceMaps, DD_PROFILING_DEBUG_SOURCE_MAPS, false)) this.endpointCollectionEnabled = isTrue(coalesce(options.endpointCollection, - DD_PROFILING_ENDPOINT_COLLECTION_ENABLED, - DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED, samplingContextsAvailable)) - logExperimentalVarDeprecation('ENDPOINT_COLLECTION_ENABLED') + DD_PROFILING_ENDPOINT_COLLECTION_ENABLED, samplingContextsAvailable)) checkOptionWithSamplingContextAllowed(this.endpointCollectionEnabled, 'Endpoint collection') this.pprofPrefix = pprofPrefix @@ -172,23 +157,18 @@ class Config { }) this.timelineEnabled = isTrue(coalesce(options.timelineEnabled, - DD_PROFILING_TIMELINE_ENABLED, - DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED, samplingContextsAvailable)) - logExperimentalVarDeprecation('TIMELINE_ENABLED') + DD_PROFILING_TIMELINE_ENABLED, samplingContextsAvailable)) checkOptionWithSamplingContextAllowed(this.timelineEnabled, 'Timeline view') this.timelineSamplingEnabled = isTrue(coalesce(options.timelineSamplingEnabled, DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED, true)) this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled, - DD_PROFILING_CODEHOTSPOTS_ENABLED, - DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, samplingContextsAvailable)) - logExperimentalVarDeprecation('CODEHOTSPOTS_ENABLED') + DD_PROFILING_CODEHOTSPOTS_ENABLED, samplingContextsAvailable)) checkOptionWithSamplingContextAllowed(this.codeHotspotsEnabled, 'Code hotspots') this.cpuProfilingEnabled = isTrue(coalesce(options.cpuProfilingEnabled, DD_PROFILING_CPU_ENABLED, - DD_PROFILING_EXPERIMENTAL_CPU_ENABLED, samplingContextsAvailable)) - logExperimentalVarDeprecation('CPU_ENABLED') + samplingContextsAvailable)) checkOptionWithSamplingContextAllowed(this.cpuProfilingEnabled, 'CPU profiling') this.heapSamplingInterval = coalesce(options.heapSamplingInterval, @@ -196,25 +176,25 @@ class Config { const uploadCompression0 = coalesce(options.uploadCompression, DD_PROFILING_DEBUG_UPLOAD_COMPRESSION, 'on') let [uploadCompression, level0] = uploadCompression0.split('-') if (!['on', 'off', 'gzip', 'zstd'].includes(uploadCompression)) { - logger.warn(`Invalid profile upload compression method "${uploadCompression0}". Will use "on".`) + this.logger.warn(`Invalid profile upload compression method "${uploadCompression0}". Will use "on".`) uploadCompression = 'on' } let level = level0 ? Number.parseInt(level0, 10) : undefined if (level !== undefined) { if (['on', 'off'].includes(uploadCompression)) { - logger.warn(`Compression levels are not supported for "${uploadCompression}".`) + this.logger.warn(`Compression levels are not supported for "${uploadCompression}".`) level = undefined } else if (Number.isNaN(level)) { - logger.warn( + this.logger.warn( `Invalid compression level "${level0}". Will use default level.`) level = undefined } else if (level < 1) { - logger.warn(`Invalid compression level ${level}. Will use 1.`) + this.logger.warn(`Invalid compression level ${level}. Will use 1.`) level = 1 } else { const maxLevel = { gzip: 9, zstd: 22 }[uploadCompression] if (level > maxLevel) { - logger.warn(`Invalid compression level ${level}. Will use ${maxLevel}.`) + this.logger.warn(`Invalid compression level ${level}. Will use ${maxLevel}.`) level = maxLevel } } diff --git a/packages/dd-trace/src/profiling/exporter_cli.js b/packages/dd-trace/src/profiling/exporter_cli.js index fde5b4a5963..3a844672010 100644 --- a/packages/dd-trace/src/profiling/exporter_cli.js +++ b/packages/dd-trace/src/profiling/exporter_cli.js @@ -8,6 +8,7 @@ const { ConsoleLogger } = require('./loggers/console') const { tagger } = require('./tagger') const fs = require('fs') const { fileURLToPath } = require('url') +const { getEnvironmentVariable } = require('../config-helper') const logger = new ConsoleLogger() const timeoutMs = 15 * 1000 @@ -15,25 +16,24 @@ const timeoutMs = 15 * 1000 function exporterFromURL (url) { if (url.protocol === 'file:') { return new FileExporter({ pprofPrefix: fileURLToPath(url) }) - } else { - const injectionEnabled = (process.env.DD_INJECTION_ENABLED || '').split(',') - const libraryInjected = injectionEnabled.length > 0 - const profilingEnabled = (process.env.DD_PROFILING_ENABLED || '').toLowerCase() - const activation = ['true', '1'].includes(profilingEnabled) - ? 'manual' - : profilingEnabled === 'auto' - ? 'auto' - : injectionEnabled.includes('profiling') - ? 'injection' - : 'unknown' - return new AgentExporter({ - url, - logger, - uploadTimeout: timeoutMs, - libraryInjected, - activation - }) } + const injectionEnabled = (getEnvironmentVariable('DD_INJECTION_ENABLED') ?? '').split(',') + const libraryInjected = injectionEnabled.length > 0 + const profilingEnabled = (getEnvironmentVariable('DD_PROFILING_ENABLED') ?? '').toLowerCase() + const activation = ['true', '1'].includes(profilingEnabled) + ? 'manual' + : profilingEnabled === 'auto' + ? 'auto' + : injectionEnabled.includes('profiling') + ? 'injection' + : 'unknown' + return new AgentExporter({ + url, + logger, + uploadTimeout: timeoutMs, + libraryInjected, + activation + }) } async function exportProfile (urls, tags, profileType, profile) { @@ -46,7 +46,7 @@ async function exportProfile (urls, tags, profileType, profile) { const encodedProfile = await encode(heap.convertProfile(profile, undefined, mapper)) const start = new Date() - for (const url of urls) { + await Promise.all(urls.map(async (url) => { const exporter = exporterFromURL(url) await exporter.export({ @@ -57,7 +57,7 @@ async function exportProfile (urls, tags, profileType, profile) { end: start, tags }) - } + })) } /** Expected command line arguments are: diff --git a/packages/dd-trace/src/profiling/exporters/agent.js b/packages/dd-trace/src/profiling/exporters/agent.js index 4b7f16060ac..afe2ad465f1 100644 --- a/packages/dd-trace/src/profiling/exporters/agent.js +++ b/packages/dd-trace/src/profiling/exporters/agent.js @@ -150,7 +150,7 @@ class AgentExporter extends EventSerializer { 'DD-EVP-ORIGIN-VERSION': version, ...form.getHeaders() }, - timeout: this._backoffTime * Math.pow(2, attempt) + timeout: this._backoffTime * 2 ** attempt } docker.inject(options.headers) diff --git a/packages/dd-trace/src/profiling/exporters/event_serializer.js b/packages/dd-trace/src/profiling/exporters/event_serializer.js index 70afbf0c50b..4a3e591d97e 100644 --- a/packages/dd-trace/src/profiling/exporters/event_serializer.js +++ b/packages/dd-trace/src/profiling/exporters/event_serializer.js @@ -1,9 +1,10 @@ const os = require('os') const perf = require('perf_hooks').performance const version = require('../../../../../package.json').version +const { getEnvironmentVariable } = require('../../config-helper') const libuvThreadPoolSize = (() => { - const ss = process.env.UV_THREADPOOL_SIZE + const ss = getEnvironmentVariable('UV_THREADPOOL_SIZE') if (ss === undefined) { // Backend will apply the default size based on Node version. return diff --git a/packages/dd-trace/src/profiling/index.js b/packages/dd-trace/src/profiling/index.js index 1b65945c8b8..7a35871b705 100644 --- a/packages/dd-trace/src/profiling/index.js +++ b/packages/dd-trace/src/profiling/index.js @@ -6,8 +6,9 @@ const SpaceProfiler = require('./profilers/space') const { AgentExporter } = require('./exporters/agent') const { FileExporter } = require('./exporters/file') const { ConsoleLogger } = require('./loggers/console') +const { getEnvironmentVariable } = require('../config-helper') -const profiler = process.env.AWS_LAMBDA_FUNCTION_NAME ? new ServerlessProfiler() : new Profiler() +const profiler = getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') ? new ServerlessProfiler() : new Profiler() module.exports = { profiler, diff --git a/packages/dd-trace/src/profiling/profiler.js b/packages/dd-trace/src/profiling/profiler.js index 1e15b9efff3..e9f1ced1313 100644 --- a/packages/dd-trace/src/profiling/profiler.js +++ b/packages/dd-trace/src/profiling/profiler.js @@ -226,7 +226,7 @@ class Profiler extends EventEmitter { const encodedProfiles = {} try { - if (Object.keys(this._config.profilers).length === 0) { + if (this._config.profilers.length === 0) { throw new Error('No profile types configured.') } @@ -246,8 +246,10 @@ class Profiler extends EventEmitter { this._capture(this._timeoutInterval, endDate) } + let hasEncoded = false + // encode and export asynchronously - for (const { profiler, profile } of profiles) { + await Promise.all(profiles.map(async ({ profiler, profile }) => { try { const encoded = await profiler.encode(profile) const compressed = encoded instanceof Buffer && this._compressionFn !== undefined @@ -260,14 +262,15 @@ class Profiler extends EventEmitter { }) return `Collected ${profiler.type} profile: ` + profileJson }) + hasEncoded = true } catch (err) { // If encoding one of the profile types fails, we should still try to // encode and submit the other profile types. this._logError(err) } - } + })) - if (Object.keys(encodedProfiles).length > 0) { + if (hasEncoded) { await this._submit(encodedProfiles, startDate, endDate, snapshotKind) profileSubmittedChannel.publish() this._logger.debug('Submitted profiles') diff --git a/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js b/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js index 564046e383b..d92c13391a4 100644 --- a/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js +++ b/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js @@ -3,7 +3,9 @@ const dc = require('dc-polyfill') const coalesce = require('koalas') const profileSubmittedChannel = dc.channel('datadog:profiling:mock-profile-submitted') -const { DD_PROFILING_UPLOAD_PERIOD } = process.env +const { getEnvironmentVariable } = require('../config-helper') + +const DD_PROFILING_UPLOAD_PERIOD = getEnvironmentVariable('DD_PROFILING_UPLOAD_PERIOD') let timerId diff --git a/packages/dd-trace/src/profiling/tagger.js b/packages/dd-trace/src/profiling/tagger.js index 68f40131b20..25143242a2a 100644 --- a/packages/dd-trace/src/profiling/tagger.js +++ b/packages/dd-trace/src/profiling/tagger.js @@ -5,20 +5,30 @@ const tagger = { if (!tags) return {} switch (typeof tags) { - case 'object': - return Array.isArray(tags) - ? tags.reduce((prev, next) => { - const parts = next.split(':') - const key = parts.shift().trim() - const value = parts.join(':').trim() + case 'object': { + if (Array.isArray(tags)) { + const tagObject = {} + for (const tag of tags) { + const colon = tag.indexOf(':') + if (colon === -1) continue + const key = tag.slice(0, colon).trim() + const value = tag.slice(colon + 1).trim() + if (key.length !== 0 && value.length !== 0) { + tagObject[key] = value + } + } + return tagObject + } - if (!key || !value) return prev + const tagsArray = [] + for (const [key, value] of Object.entries(tags)) { + if (value != null) { + tagsArray.push(`${key}:${value}`) + } + } - return Object.assign(prev, { [key]: value }) - }, {}) - : tagger.parse(Object.keys(tags) - .filter(key => tags[key] !== undefined && tags[key] !== null) - .map(key => `${key}:${tags[key]}`)) + return tagger.parse(tagsArray) + } case 'string': return tagger.parse(tags.split(',')) default: diff --git a/packages/dd-trace/src/proxy.js b/packages/dd-trace/src/proxy.js index ee0bca5782c..57c56b13f10 100644 --- a/packages/dd-trace/src/proxy.js +++ b/packages/dd-trace/src/proxy.js @@ -10,6 +10,7 @@ const telemetry = require('./telemetry') const nomenclature = require('./service-naming') const PluginManager = require('./plugin_manager') const NoopDogStatsDClient = require('./noop/dogstatsd') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') const { setBaggageItem, getBaggageItem, @@ -198,7 +199,7 @@ class Tracer extends NoopProxy { this._testApiManualPlugin.configure({ ...config, enabled: true }, false) } if (config.ciVisAgentlessLogSubmissionEnabled) { - if (process.env.DD_API_KEY) { + if (getEnvironmentVariable('DD_API_KEY')) { const LogSubmissionPlugin = require('./ci-visibility/log-submission/log-submission-plugin') const automaticLogPlugin = new LogSubmissionPlugin(this) automaticLogPlugin.configure({ ...config, enabled: true }) diff --git a/packages/dd-trace/src/ritm.js b/packages/dd-trace/src/ritm.js index de246666ce2..07c03c84468 100644 --- a/packages/dd-trace/src/ritm.js +++ b/packages/dd-trace/src/ritm.js @@ -4,6 +4,7 @@ const path = require('path') const Module = require('module') const parse = require('module-details-from-path') const dc = require('dc-polyfill') +const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') const origRequire = Module.prototype.require @@ -82,9 +83,8 @@ function Hook (modules, options, onrequire) { if (patched) { // If it's already patched, just return it as-is. return origRequire.apply(this, arguments) - } else { - patching[filename] = true } + patching[filename] = true const payload = { filename, @@ -110,8 +110,8 @@ function Hook (modules, options, onrequire) { if (!hooks) return exports // abort if module name isn't on whitelist name = filename } else { - const inAWSLambda = process.env.AWS_LAMBDA_FUNCTION_NAME !== undefined - const hasLambdaHandler = process.env.DD_LAMBDA_HANDLER !== undefined + const inAWSLambda = getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') !== undefined + const hasLambdaHandler = getEnvironmentVariable('DD_LAMBDA_HANDLER') !== undefined const segments = filename.split(path.sep) const filenameFromNodeModule = segments.includes('node_modules') // decide how to assign the stat diff --git a/packages/dd-trace/src/runtime_metrics/runtime_metrics.js b/packages/dd-trace/src/runtime_metrics/runtime_metrics.js index 7f1f7b3e65b..88c9bd50405 100644 --- a/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +++ b/packages/dd-trace/src/runtime_metrics/runtime_metrics.js @@ -8,9 +8,10 @@ const { DogStatsDClient, MetricsAggregationClient } = require('../dogstatsd') const log = require('../log') const Histogram = require('../histogram') const { performance, PerformanceObserver } = require('perf_hooks') +const { getEnvironmentVariable } = require('../config-helper') const { NODE_MAJOR, NODE_MINOR } = require('../../../../version') -const { DD_RUNTIME_METRICS_FLUSH_INTERVAL = '10000' } = process.env +const DD_RUNTIME_METRICS_FLUSH_INTERVAL = getEnvironmentVariable('DD_RUNTIME_METRICS_FLUSH_INTERVAL') ?? '10000' const INTERVAL = Number.parseInt(DD_RUNTIME_METRICS_FLUSH_INTERVAL, 10) // Node >=16 has PerformanceObserver with `gc` type, but <16.7 had a critical bug. @@ -202,7 +203,7 @@ function captureGCMetrics () { const pause = {} for (const stat of profile.statistics) { - const type = stat.gcType.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase() + const type = stat.gcType.replaceAll(/([a-z])([A-Z])/g, '$1_$2').toLowerCase() pause[type] = pause[type] || new Histogram() pause[type].record(stat.cost) diff --git a/packages/dd-trace/src/sampler.js b/packages/dd-trace/src/sampler.js index e975d0260e4..b023c55b6de 100644 --- a/packages/dd-trace/src/sampler.js +++ b/packages/dd-trace/src/sampler.js @@ -16,12 +16,16 @@ const SAMPLING_KNUTH_FACTOR = 1_111_111_111_111_111_111n * This class uses a deterministic sampling algorithm that is consistent across all languages. */ class Sampler { + #threshold = 0n + /** * @param {number} rate */ constructor (rate) { + // TODO: Should this be moved up to the calling parts? + rate = Math.min(Math.max(rate, 0), 1) this._rate = rate - this._threshold = BigInt(Math.floor(rate * MAX_TRACE_ID)) + this.#threshold = BigInt(Math.floor(rate * MAX_TRACE_ID)) } /** @@ -31,6 +35,10 @@ class Sampler { return this._rate } + get threshold () { + return this.#threshold + } + /** * Determines whether a trace/span should be sampled based on the configured sampling rate. * @@ -48,7 +56,7 @@ class Sampler { span = typeof span.context === 'function' ? span.context() : span - return (span._traceId.toBigInt() * SAMPLING_KNUTH_FACTOR) % UINT64_MODULO <= this._threshold + return (span._traceId.toBigInt() * SAMPLING_KNUTH_FACTOR) % UINT64_MODULO <= this.#threshold } } diff --git a/packages/dd-trace/src/serverless.js b/packages/dd-trace/src/serverless.js index 79fcacc43c1..9c4039228a0 100644 --- a/packages/dd-trace/src/serverless.js +++ b/packages/dd-trace/src/serverless.js @@ -1,21 +1,28 @@ 'use strict' +const { getEnvironmentVariable } = require('./config-helper') + function getIsGCPFunction () { - const isDeprecatedGCPFunction = process.env.FUNCTION_NAME !== undefined && process.env.GCP_PROJECT !== undefined - const isNewerGCPFunction = process.env.K_SERVICE !== undefined && process.env.FUNCTION_TARGET !== undefined + const isDeprecatedGCPFunction = + getEnvironmentVariable('FUNCTION_NAME') !== undefined && + getEnvironmentVariable('GCP_PROJECT') !== undefined + const isNewerGCPFunction = + getEnvironmentVariable('K_SERVICE') !== undefined && + getEnvironmentVariable('FUNCTION_TARGET') !== undefined return isDeprecatedGCPFunction || isNewerGCPFunction } function getIsAzureFunction () { const isAzureFunction = - process.env.FUNCTIONS_EXTENSION_VERSION !== undefined && process.env.FUNCTIONS_WORKER_RUNTIME !== undefined + getEnvironmentVariable('FUNCTIONS_EXTENSION_VERSION') !== undefined && + getEnvironmentVariable('FUNCTIONS_WORKER_RUNTIME') !== undefined return isAzureFunction } function isInServerlessEnvironment () { - const inAWSLambda = process.env.AWS_LAMBDA_FUNCTION_NAME !== undefined + const inAWSLambda = getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') !== undefined const isGCPFunction = getIsGCPFunction() const isAzureFunction = getIsAzureFunction() diff --git a/packages/dd-trace/src/span_processor.js b/packages/dd-trace/src/span_processor.js index 979961dcf08..9bcb6410361 100644 --- a/packages/dd-trace/src/span_processor.js +++ b/packages/dd-trace/src/span_processor.js @@ -4,6 +4,7 @@ const log = require('./log') const format = require('./format') const SpanSampler = require('./span_sampler') const GitMetadataTagger = require('./git_metadata_tagger') +const { getEnvironmentVariable } = require('./config-helper') const startedSpans = new WeakSet() const finishedSpans = new WeakSet() @@ -79,7 +80,7 @@ class SpanProcessor { } _erase (trace, active) { - if (process.env.DD_TRACE_EXPERIMENTAL_STATE_TRACKING === 'true') { + if (getEnvironmentVariable('DD_TRACE_EXPERIMENTAL_STATE_TRACKING') === 'true') { const started = new Set() const startedIds = new Set() const finished = new Set() diff --git a/packages/dd-trace/src/standalone/tracesource.js b/packages/dd-trace/src/standalone/tracesource.js index d251a12dffd..67704436df7 100644 --- a/packages/dd-trace/src/standalone/tracesource.js +++ b/packages/dd-trace/src/standalone/tracesource.js @@ -1,7 +1,6 @@ 'use strict' const { TRACE_SOURCE_PROPAGATION_KEY } = require('../constants') -const { hasOwn } = require('../util') function addTraceSourceTag (tags, product) { if (tags && product) { @@ -13,7 +12,7 @@ function addTraceSourceTag (tags, product) { } function hasTraceSourcePropagationTag (tags) { - return hasOwn(tags, TRACE_SOURCE_PROPAGATION_KEY) + return Object.hasOwn(tags, TRACE_SOURCE_PROPAGATION_KEY) } module.exports = { diff --git a/packages/dd-trace/src/standalone/tracesource_priority_sampler.js b/packages/dd-trace/src/standalone/tracesource_priority_sampler.js index 0fb9d8fe58a..33963dbf4e0 100644 --- a/packages/dd-trace/src/standalone/tracesource_priority_sampler.js +++ b/packages/dd-trace/src/standalone/tracesource_priority_sampler.js @@ -1,6 +1,5 @@ 'use strict' -const { hasOwn } = require('../util') const PrioritySampler = require('../priority_sampler') const { MANUAL_KEEP } = require('../../../../ext/tags') const { USER_KEEP, AUTO_KEEP, AUTO_REJECT } = require('../../../../ext/priority') @@ -16,7 +15,7 @@ class TraceSourcePrioritySampler extends PrioritySampler { } _getPriorityFromTags (tags, context) { - if (hasOwn(tags, MANUAL_KEEP) && + if (Object.hasOwn(tags, MANUAL_KEEP) && tags[MANUAL_KEEP] !== false && hasTraceSourcePropagationTag(context._trace.tags) ) { diff --git a/packages/dd-trace/src/startup-log.js b/packages/dd-trace/src/startup-log.js index 5268fc0db24..40a06e3bd03 100644 --- a/packages/dd-trace/src/startup-log.js +++ b/packages/dd-trace/src/startup-log.js @@ -13,13 +13,9 @@ let samplingRules = [] let alreadyRan = false function getIntegrationsAndAnalytics () { - const integrations = new Set() - const extras = {} - for (const pluginName in pluginManager._pluginsByName) { - integrations.add(pluginName) + return { + integrations_loaded: Object.keys(pluginManager._pluginsByName) } - extras.integrations_loaded = [...integrations] - return extras } function startupLog ({ agentError } = {}) { @@ -61,7 +57,9 @@ function tracerInfo () { return String(this) }, toString () { - return JSON.stringify(this) + return JSON.stringify(this, (_key_, value) => { + return typeof value === 'bigint' || typeof value === 'symbol' ? String(value) : value + }) } } @@ -92,16 +90,6 @@ function tracerInfo () { out.appsec_enabled = !!config.appsec.enabled - // // This next bunch is for features supported by other tracers, but not this - // // one. They may be implemented in the future. - - // out.enabled_cli - // out.sampling_rules_error - // out.integration_XXX_analytics_enabled - // out.integration_XXX_sample_rate - // out.service_mapping - // out.service_mapping_error - return out } diff --git a/packages/dd-trace/src/supported-configurations.json b/packages/dd-trace/src/supported-configurations.json new file mode 100644 index 00000000000..b9947a85834 --- /dev/null +++ b/packages/dd-trace/src/supported-configurations.json @@ -0,0 +1,439 @@ +{ + "supportedConfigurations": { + "DD_AAS_DOTNET_EXTENSION_VERSION": ["A"], + "DD_AGENT_HOST": ["A"], + "DD_AGENTLESS_LOG_SUBMISSION_ENABLED": ["A"], + "DD_AGENTLESS_LOG_SUBMISSION_URL": ["A"], + "DD_API_KEY": ["A"], + "DD_API_SECURITY_ENABLED": ["A"], + "DD_API_SECURITY_SAMPLE_DELAY": ["A"], + "DD_APM_FLUSH_DEADLINE_MILLISECONDS": ["A"], + "DD_APM_TRACING_ENABLED": ["A"], + "DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE": ["A"], + "DD_APPSEC_COLLECT_ALL_HEADERS": ["A"], + "DD_APPSEC_ENABLED": ["A"], + "DD_APPSEC_GRAPHQL_BLOCKED_TEMPLATE_JSON": ["A"], + "DD_APPSEC_HEADER_COLLECTION_REDACTION_ENABLED": ["A"], + "DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML": ["A"], + "DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON": ["A"], + "DD_APPSEC_MAX_COLLECTED_HEADERS": ["A"], + "DD_APPSEC_MAX_STACK_TRACE_DEPTH": ["A"], + "DD_APPSEC_MAX_STACK_TRACES": ["A"], + "DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP": ["A"], + "DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP": ["A"], + "DD_APPSEC_RASP_COLLECT_REQUEST_BODY": ["A"], + "DD_APPSEC_RASP_ENABLED": ["A"], + "DD_APPSEC_RULES": ["A"], + "DD_APPSEC_SCA_ENABLED": ["A"], + "DD_APPSEC_STACK_TRACE_ENABLED": ["A"], + "DD_APPSEC_TRACE_RATE_LIMIT": ["A"], + "DD_APPSEC_WAF_TIMEOUT": ["A"], + "DD_AZURE_APP_SERVICES": ["A"], + "DD_CIVISIBILITY_AGENTLESS_ENABLED": ["A"], + "DD_CIVISIBILITY_AGENTLESS_URL": ["A"], + "DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER": ["A"], + "DD_CIVISIBILITY_DANGEROUSLY_FORCE_COVERAGE": ["A"], + "DD_CIVISIBILITY_DANGEROUSLY_FORCE_TEST_SKIPPING": ["A"], + "DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED": ["A"], + "DD_CIVISIBILITY_ENABLED": ["A"], + "DD_CIVISIBILITY_FLAKY_RETRY_COUNT": ["A"], + "DD_CIVISIBILITY_FLAKY_RETRY_ENABLED": ["A"], + "DD_CIVISIBILITY_GIT_UNSHALLOW_ENABLED": ["A"], + "DD_CIVISIBILITY_GIT_UPLOAD_ENABLED": ["A"], + "DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED": ["A"], + "DD_CIVISIBILITY_ITR_ENABLED": ["A"], + "DD_CIVISIBILITY_MANUAL_API_ENABLED": ["A"], + "DD_CIVISIBILITY_TEST_COMMAND": ["A"], + "DD_CIVISIBILITY_TEST_MODULE_ID": ["A"], + "DD_CIVISIBILITY_TEST_SESSION_ID": ["A"], + "DD_CODE_ORIGIN_FOR_SPANS_ENABLED": ["A"], + "DD_CODE_ORIGIN_FOR_SPANS_EXPERIMENTAL_EXIT_SPANS_ENABLED": ["A"], + "DD_CRASHTRACKING_ENABLED": ["A"], + "DD_DATA_STREAMS_ENABLED": ["A"], + "DD_DBM_PROPAGATION_MODE": ["A"], + "DD_DOGSTATSD_HOST": ["A"], + "DD_DOGSTATSD_PORT": ["A"], + "DD_DYNAMIC_INSTRUMENTATION_ENABLED": ["A"], + "DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS": ["A"], + "DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS": ["A"], + "DD_ENV": ["A"], + "DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED": ["A"], + "DD_EXTERNAL_ENV": ["A"], + "DD_GIT_BRANCH": ["A"], + "DD_GIT_COMMIT_AUTHOR_DATE": ["A"], + "DD_GIT_COMMIT_AUTHOR_EMAIL": ["A"], + "DD_GIT_COMMIT_AUTHOR_NAME": ["A"], + "DD_GIT_COMMIT_COMMITTER_DATE": ["A"], + "DD_GIT_COMMIT_COMMITTER_EMAIL": ["A"], + "DD_GIT_COMMIT_COMMITTER_NAME": ["A"], + "DD_GIT_COMMIT_MESSAGE": ["A"], + "DD_GIT_COMMIT_SHA": ["A"], + "DD_GIT_PROPERTIES_FILE": ["A"], + "DD_GIT_REPOSITORY_URL": ["A"], + "DD_GIT_TAG": ["A"], + "DD_GRPC_CLIENT_ERROR_STATUSES": ["A"], + "DD_GRPC_SERVER_ERROR_STATUSES": ["A"], + "DD_IAST_DB_ROWS_TO_TAINT": ["A"], + "DD_IAST_DEDUPLICATION_ENABLED": ["A"], + "DD_IAST_ENABLED": ["A"], + "DD_IAST_MAX_CONCURRENT_REQUESTS": ["A"], + "DD_IAST_MAX_CONTEXT_OPERATIONS": ["A"], + "DD_IAST_REDACTION_ENABLED": ["A"], + "DD_IAST_REDACTION_NAME_PATTERN": ["A"], + "DD_IAST_REDACTION_VALUE_PATTERN": ["A"], + "DD_IAST_REQUEST_SAMPLING": ["A"], + "DD_IAST_SECURITY_CONTROLS_CONFIGURATION": ["A"], + "DD_IAST_STACK_TRACE_ENABLED": ["A"], + "DD_IAST_TELEMETRY_VERBOSITY": ["A"], + "DD_INJECT_FORCE": ["A"], + "DD_INJECTION_ENABLED": ["A"], + "DD_INSTRUMENTATION_CONFIG_ID": ["A"], + "DD_INSTRUMENTATION_INSTALL_ID": ["A"], + "DD_INSTRUMENTATION_INSTALL_TIME": ["A"], + "DD_INSTRUMENTATION_INSTALL_TYPE": ["A"], + "DD_INSTRUMENTATION_TELEMETRY_ENABLED": ["A"], + "DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD": ["A"], + "DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED": ["A"], + "DD_LAMBDA_HANDLER": ["A"], + "DD_LANGCHAIN_SPAN_CHAR_LIMIT": ["A"], + "DD_LANGCHAIN_SPAN_PROMPT_COMPLETION_SAMPLE_RATE": ["A"], + "DD_LLMOBS_AGENTLESS_ENABLED": ["A"], + "DD_LLMOBS_ENABLED": ["A"], + "DD_LLMOBS_ML_APP": ["A"], + "DD_LOG_LEVEL": ["A"], + "DD_LOGS_INJECTION": ["A"], + "DD_MINI_AGENT_PATH": ["A"], + "DD_OPENAI_LOGS_ENABLED": ["A"], + "DD_OPENAI_SPAN_CHAR_LIMIT": ["A"], + "DD_PLAYWRIGHT_WORKER": ["A"], + "DD_PROFILING_CODEHOTSPOTS_ENABLED": ["A"], + "DD_PROFILING_CPU_ENABLED": ["A"], + "DD_PROFILING_DEBUG_UPLOAD_COMPRESSION": ["A"], + "DD_PROFILING_ENABLED": ["A"], + "DD_PROFILING_ENDPOINT_COLLECTION_ENABLED": ["A"], + "DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES": ["A"], + "DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE": ["A"], + "DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT": ["A"], + "DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED": ["A"], + "DD_PROFILING_EXPORTERS": ["A"], + "DD_PROFILING_HEAP_ENABLED": ["A"], + "DD_PROFILING_PROFILERS": ["A"], + "DD_PROFILING_SOURCE_MAP": ["A"], + "DD_PROFILING_UPLOAD_PERIOD": ["A"], + "DD_PROFILING_V8_PROFILER_BUG_WORKAROUND": ["A"], + "DD_PROFILING_WALLTIME_ENABLED": ["A"], + "DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS": ["A"], + "DD_REMOTE_CONFIGURATION_ENABLED": ["A"], + "DD_RUNTIME_METRICS_ENABLED": ["A"], + "DD_RUNTIME_METRICS_FLUSH_INTERVAL": ["A"], + "DD_SERVICE_MAPPING": ["A"], + "DD_SERVICE": ["A"], + "DD_SITE": ["A"], + "DD_SPAN_SAMPLING_RULES_FILE": ["A"], + "DD_SPAN_SAMPLING_RULES": ["A"], + "DD_TAGS": ["A"], + "DD_TELEMETRY_DEBUG": ["A"], + "DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED": ["A"], + "DD_TELEMETRY_FORWARDER_PATH": ["A"], + "DD_TELEMETRY_HEARTBEAT_INTERVAL": ["A"], + "DD_TELEMETRY_LOG_COLLECTION_ENABLED": ["A"], + "DD_TELEMETRY_METRICS_ENABLED": ["A"], + "DD_TEST_FAILED_TEST_REPLAY_ENABLED": ["A"], + "DD_TEST_FLEET_CONFIG_PATH": ["A"], + "DD_TEST_LOCAL_CONFIG_PATH": ["A"], + "DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES": ["A"], + "DD_TEST_MANAGEMENT_ENABLED": ["A"], + "DD_TEST_SESSION_NAME": ["A"], + "DD_TRACE__CONFLUENTINC_KAFKA_JAVASCRIPT_ENABLED": ["A"], + "DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED": ["A"], + "DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED": ["A"], + "DD_TRACE_AEROSPIKE_ENABLED": ["A"], + "DD_TRACE_AGENT_PORT": ["A"], + "DD_TRACE_AGENT_PROTOCOL_VERSION": ["A"], + "DD_TRACE_AGENT_URL": ["A"], + "DD_TRACE_AMQP10_ENABLED": ["A"], + "DD_TRACE_AMQPLIB_ENABLED": ["A"], + "DD_TRACE_APOLLO_ENABLED": ["A"], + "DD_TRACE_APOLLO_GATEWAY_ENABLED": ["A"], + "DD_TRACE_APOLLO_SERVER_CORE_ENABLED": ["A"], + "DD_TRACE_APOLLO_SERVER_ENABLED": ["A"], + "DD_TRACE_APOLLO_SERVER_EXPRESS_ENABLED": ["A"], + "DD_TRACE_APOLLO_SERVER_FASTIFY_ENABLED": ["A"], + "DD_TRACE_APOLLO_SUBGRAPH_ENABLED": ["A"], + "DD_TRACE_AVSC_ENABLED": ["A"], + "DD_TRACE_AWS_ADD_SPAN_POINTERS": ["A"], + "DD_TRACE_AWS_SDK_AWS_BATCH_PROPAGATION_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_AWS_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_BATCH_PROPAGATION_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_BEDROCKRUNTIME_BATCH_PROPAGATION_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_BEDROCKRUNTIME_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_CLOUDWATCHLOGS_BATCH_PROPAGATION_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_CLOUDWATCHLOGS_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_DYNAMODB_BATCH_PROPAGATION_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_DYNAMODB_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_EVENTBRIDGE_BATCH_PROPAGATION_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_EVENTBRIDGE_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_KINESIS_BATCH_PROPAGATION_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_KINESIS_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_LAMBDA_BATCH_PROPAGATION_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_LAMBDA_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_NODE_HTTP_HANDLER_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_REDSHIFT_BATCH_PROPAGATION_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_REDSHIFT_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_S3_BATCH_PROPAGATION_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_S3_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_SFN_BATCH_PROPAGATION_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_SFN_CLIENT_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_SFN_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_SMITHY_CLIENT_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_SNS_BATCH_PROPAGATION_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_SNS_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_SQS_BATCH_PROPAGATION_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_SQS_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_STATES_BATCH_PROPAGATION_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_STATES_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_STEPFUNCTIONS_BATCH_PROPAGATION_ENABLED": ["A"], + "DD_TRACE_AWS_SDK_STEPFUNCTIONS_ENABLED": ["A"], + "DD_TRACE_AXIOS_ENABLED": ["A"], + "DD_TRACE_AZURE_FUNCTIONS_ENABLED": ["A"], + "DD_TRACE_BAGGAGE_MAX_BYTES": ["A"], + "DD_TRACE_BAGGAGE_MAX_ITEMS": ["A"], + "DD_TRACE_BAGGAGE_TAG_KEYS": ["A"], + "DD_TRACE_BEAUTIFUL_LOGS": ["A"], + "DD_TRACE_BLUEBIRD_ENABLED": ["A"], + "DD_TRACE_BODY_PARSER_ENABLED": ["A"], + "DD_TRACE_BSON_ENABLED": ["A"], + "DD_TRACE_BUNYAN_ENABLED": ["A"], + "DD_TRACE_CASSANDRA_DRIVER_ENABLED": ["A"], + "DD_TRACE_CHILD_PROCESS_ENABLED": ["A"], + "DD_TRACE_CLIENT_IP_ENABLED": ["A"], + "DD_TRACE_CLIENT_IP_HEADER": ["A"], + "DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH": ["A"], + "DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING": ["A"], + "DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING": ["A"], + "DD_TRACE_COLLECTIONS_ENABLED": ["A"], + "DD_TRACE_COMMONPLUGIN_ENABLED": ["A"], + "DD_TRACE_CONFLUENTINC_KAFKA_JAVASCRIPT_ENABLED": ["A"], + "DD_TRACE_CONNECT_ENABLED": ["A"], + "DD_TRACE_COOKIE_ENABLED": ["A"], + "DD_TRACE_COOKIE_PARSER_ENABLED": ["A"], + "DD_TRACE_COUCHBASE_ENABLED": ["A"], + "DD_TRACE_CRYPTO_ENABLED": ["A"], + "DD_TRACE_CUCUMBER_CUCUMBER_ENABLED": ["A"], + "DD_TRACE_CUCUMBER_ENABLED": ["A"], + "DD_TRACE_CYPRESS_ENABLED": ["A"], + "DD_TRACE_DD_TRACE_API_ENABLED": ["A"], + "DD_TRACE_DEBUG": ["A"], + "DD_TRACE_DISABLED_INSTRUMENTATIONS": ["A"], + "DD_TRACE_DISABLED_PLUGINS": ["A"], + "DD_TRACE_DNS_ENABLED": ["A"], + "DD_TRACE_DYNAMODB_TABLE_PRIMARY_KEYS": ["A"], + "DD_TRACE_ELASTIC_ELASTICSEARCH_ENABLED": ["A"], + "DD_TRACE_ELASTIC_TRANSPORT_ENABLED": ["A"], + "DD_TRACE_ELASTICSEARCH_ENABLED": ["A"], + "DD_TRACE_ENABLED": ["A"], + "DD_TRACE_ENCODING_DEBUG": ["A"], + "DD_TRACE_EXPERIMENTAL_B3_ENABLED": ["A"], + "DD_TRACE_EXPERIMENTAL_EXPORTER": ["A"], + "DD_TRACE_EXPERIMENTAL_GET_RUM_DATA_ENABLED": ["A"], + "DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED": ["A"], + "DD_TRACE_EXPERIMENTAL_SPAN_COUNTS": ["A"], + "DD_TRACE_EXPERIMENTAL_STATE_TRACKING": ["A"], + "DD_TRACE_EXPRESS_ENABLED": ["A"], + "DD_TRACE_EXPRESS_MONGO_SANITIZE_ENABLED": ["A"], + "DD_TRACE_EXPRESS_SESSION_ENABLED": ["A"], + "DD_TRACE_FASTIFY_ENABLED": ["A"], + "DD_TRACE_FETCH_ENABLED": ["A"], + "DD_TRACE_FIND_MY_WAY_ENABLED": ["A"], + "DD_TRACE_FS_ENABLED": ["A"], + "DD_TRACE_GENERIC_POOL_ENABLED": ["A"], + "DD_TRACE_GIT_METADATA_ENABLED": ["A"], + "DD_TRACE_GLOBAL_TAGS": ["A"], + "DD_TRACE_GOOGLE_CLOUD_PUBSUB_ENABLED": ["A"], + "DD_TRACE_GOOGLE_CLOUD_VERTEXAI_ENABLED": ["A"], + "DD_TRACE_GOOGLE_GAX_ENABLED": ["A"], + "DD_TRACE_GRAPHQL_ENABLED": ["A"], + "DD_TRACE_GRAPHQL_ERROR_EXTENSIONS": ["A"], + "DD_TRACE_GRAPHQL_TAG_ENABLED": ["A"], + "DD_TRACE_GRAPHQL_TOOLS_ENABLED": ["A"], + "DD_TRACE_GRAPHQL_TOOLS_EXECUTOR_ENABLED": ["A"], + "DD_TRACE_GRAPHQL_YOGA_ENABLED": ["A"], + "DD_TRACE_GRPC_ENABLED": ["A"], + "DD_TRACE_GRPC_GRPC_JS_ENABLED": ["A"], + "DD_TRACE_GRPC_PROTO_LOADER_ENABLED": ["A"], + "DD_TRACE_HANDLEBARS_ENABLED": ["A"], + "DD_TRACE_HAPI_BOOM_ENABLED": ["A"], + "DD_TRACE_HAPI_ENABLED": ["A"], + "DD_TRACE_HAPI_HAPI_ENABLED": ["A"], + "DD_TRACE_HEADER_TAGS": ["A"], + "DD_TRACE_HTTP_ENABLED": ["A"], + "DD_TRACE_HTTP2_ENABLED": ["A"], + "DD_TRACE_HTTPS_ENABLED": ["A"], + "DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED": ["A"], + "DD_TRACE_IOREDIS_ENABLED": ["A"], + "DD_TRACE_IOVALKEY_ENABLED": ["A"], + "DD_TRACE_JEST_CIRCUS_ENABLED": ["A"], + "DD_TRACE_JEST_CONFIG_ENABLED": ["A"], + "DD_TRACE_JEST_CORE_ENABLED": ["A"], + "DD_TRACE_JEST_ENABLED": ["A"], + "DD_TRACE_JEST_ENVIRONMENT_JSDOM_ENABLED": ["A"], + "DD_TRACE_JEST_ENVIRONMENT_NODE_ENABLED": ["A"], + "DD_TRACE_JEST_GLOBALS_ENABLED": ["A"], + "DD_TRACE_JEST_REPORTERS_ENABLED": ["A"], + "DD_TRACE_JEST_RUNTIME_ENABLED": ["A"], + "DD_TRACE_JEST_TEST_SEQUENCER_ENABLED": ["A"], + "DD_TRACE_JEST_TRANSFORM_ENABLED": ["A"], + "DD_TRACE_JEST_WORKER_ENABLED": ["A"], + "DD_TRACE_KAFKAJS_ENABLED": ["A"], + "DD_TRACE_KNEX_ENABLED": ["A"], + "DD_TRACE_KOA_ENABLED": ["A"], + "DD_TRACE_KOA_ROUTE_ENABLED": ["A"], + "DD_TRACE_KOA_ROUTER_ENABLED": ["A"], + "DD_TRACE_KOA_WEBSOCKET_ENABLED": ["A"], + "DD_TRACE_LANGCHAIN_ANTHROPIC_ENABLED": ["A"], + "DD_TRACE_LANGCHAIN_COHERE_ENABLED": ["A"], + "DD_TRACE_LANGCHAIN_CORE_ENABLED": ["A"], + "DD_TRACE_LANGCHAIN_ENABLED": ["A"], + "DD_TRACE_LANGCHAIN_GOOGLE_GENAI_ENABLED": ["A"], + "DD_TRACE_LANGCHAIN_OPENAI_ENABLED": ["A"], + "DD_TRACE_LDAPJS_ENABLED": ["A"], + "DD_TRACE_LDAPJS_PROMISE_ENABLED": ["A"], + "DD_TRACE_LEGACY_BAGGAGE_ENABLED": ["A"], + "DD_TRACE_LIMITD_CLIENT_ENABLED": ["A"], + "DD_TRACE_LODASH_ENABLED": ["A"], + "DD_TRACE_LOG_LEVEL": ["A"], + "DD_TRACE_LOOPBACK_ENABLED": ["A"], + "DD_TRACE_MARIADB_ENABLED": ["A"], + "DD_TRACE_MEMCACHED_COMMAND_ENABLED": ["A"], + "DD_TRACE_MEMCACHED_ENABLED": ["A"], + "DD_TRACE_MICROGATEWAY_CORE_ENABLED": ["A"], + "DD_TRACE_MIDDIE_ENABLED": ["A"], + "DD_TRACE_MIDDLEWARE_TRACING_ENABLED": ["A"], + "DD_TRACE_MOCHA_EACH_ENABLED": ["A"], + "DD_TRACE_MOCHA_ENABLED": ["A"], + "DD_TRACE_MOLECULER_ENABLED": ["A"], + "DD_TRACE_MONGODB_CORE_ENABLED": ["A"], + "DD_TRACE_MONGODB_ENABLED": ["A"], + "DD_TRACE_MONGODB_HEARTBEAT_ENABLED": ["A"], + "DD_TRACE_MONGOOSE_ENABLED": ["A"], + "DD_TRACE_MQUERY_ENABLED": ["A"], + "DD_TRACE_MULTER_ENABLED": ["A"], + "DD_TRACE_MYSQL_ENABLED": ["A"], + "DD_TRACE_MYSQL2_ENABLED": ["A"], + "DD_TRACE_NATIVE_SPAN_EVENTS": ["A"], + "DD_TRACE_NET_ENABLED": ["A"], + "DD_TRACE_NEXT_ENABLED": ["A"], + "DD_TRACE_NODE_CHILD_PROCESS_ENABLED": ["A"], + "DD_TRACE_NODE_REDIS_CLIENT_ENABLED": ["A"], + "DD_TRACE_NODE_SERIALIZE_ENABLED": ["A"], + "DD_TRACE_NYC_ENABLED": ["A"], + "DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP": ["A"], + "DD_TRACE_OPENAI_ENABLED": ["A"], + "DD_TRACE_OPENSEARCH_ENABLED": ["A"], + "DD_TRACE_OPENSEARCH_PROJECT_OPENSEARCH_ENABLED": ["A"], + "DD_TRACE_OPENTELEMETRY_SDK_TRACE_NODE_ENABLED": ["A"], + "DD_TRACE_ORACLEDB_ENABLED": ["A"], + "DD_TRACE_OTEL_ENABLED": ["A"], + "DD_TRACE_PARTIAL_FLUSH_MIN_SPANS": ["A"], + "DD_TRACE_PASSPORT_ENABLED": ["A"], + "DD_TRACE_PASSPORT_HTTP_ENABLED": ["A"], + "DD_TRACE_PASSPORT_LOCAL_ENABLED": ["A"], + "DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED": ["A"], + "DD_TRACE_PEER_SERVICE_MAPPING": ["A"], + "DD_TRACE_PG_CURSOR_ENABLED": ["A"], + "DD_TRACE_PG_ENABLED": ["A"], + "DD_TRACE_PG_NATIVE_ENABLED": ["A"], + "DD_TRACE_PG_QUERY_STREAM_ENABLED": ["A"], + "DD_TRACE_PINO_ENABLED": ["A"], + "DD_TRACE_PINO_PRETTY_ENABLED": ["A"], + "DD_TRACE_PLAYWRIGHT_CORE_ENABLED": ["A"], + "DD_TRACE_PLAYWRIGHT_ENABLED": ["A"], + "DD_TRACE_PLAYWRIGHT_TEST_ENABLED": ["A"], + "DD_TRACE_PROCESS_ENABLED": ["A"], + "DD_TRACE_PROMISE_ENABLED": ["A"], + "DD_TRACE_PROMISE_JS_ENABLED": ["A"], + "DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT": ["A"], + "DD_TRACE_PROPAGATION_EXTRACT_FIRST": ["A"], + "DD_TRACE_PROPAGATION_STYLE_EXTRACT": ["A"], + "DD_TRACE_PROPAGATION_STYLE_INJECT": ["A"], + "DD_TRACE_PROPAGATION_STYLE": ["A"], + "DD_TRACE_PROTOBUFJS_ENABLED": ["A"], + "DD_TRACE_PUG_ENABLED": ["A"], + "DD_TRACE_Q_ENABLED": ["A"], + "DD_TRACE_RATE_LIMIT": ["A"], + "DD_TRACE_REACT_DOM_ENABLED": ["A"], + "DD_TRACE_REACT_ENABLED": ["A"], + "DD_TRACE_REDIS_CLIENT_ENABLED": ["A"], + "DD_TRACE_REDIS_ENABLED": ["A"], + "DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED": ["A"], + "DD_TRACE_REPORT_HOSTNAME": ["A"], + "DD_TRACE_REQUEST_ENABLED": ["A"], + "DD_TRACE_RESTIFY_ENABLED": ["A"], + "DD_TRACE_RHEA_ENABLED": ["A"], + "DD_TRACE_ROUTER_ENABLED": ["A"], + "DD_TRACE_SAMPLE_RATE": ["A"], + "DD_TRACE_SAMPLING_RULES": ["A"], + "DD_TRACE_SCOPE": ["A"], + "DD_TRACE_SELENIUM_ENABLED": ["A"], + "DD_TRACE_SELENIUM_WEBDRIVER_ENABLED": ["A"], + "DD_TRACE_SEQUELIZE_ENABLED": ["A"], + "DD_TRACE_SHAREDB_ENABLED": ["A"], + "DD_TRACE_SMITHY_SMITHY_CLIENT_ENABLED": ["A"], + "DD_TRACE_SPAN_ATTRIBUTE_SCHEMA": ["A"], + "DD_TRACE_SPAN_LEAK_DEBUG": ["A"], + "DD_TRACE_SQLITE3_ENABLED": ["A"], + "DD_TRACE_STARTUP_LOGS": ["A"], + "DD_TRACE_STATS_COMPUTATION_ENABLED": ["A"], + "DD_TRACE_SUFFIXPLUGIN_ENABLED": ["A"], + "DD_TRACE_TAGS": ["A"], + "DD_TRACE_TEDIOUS_ENABLED": ["A"], + "DD_TRACE_UNDICI_ENABLED": ["A"], + "DD_TRACE_URL_ENABLED": ["A"], + "DD_TRACE_VITEST_ENABLED": ["A"], + "DD_TRACE_VITEST_RUNNER_ENABLED": ["A"], + "DD_TRACE_VM_ENABLED": ["A"], + "DD_TRACE_WHEN_ENABLED": ["A"], + "DD_TRACE_WINSTON_ENABLED": ["A"], + "DD_TRACE_WORKERPOOL_ENABLED": ["A"], + "DD_TRACE_WS_ENABLED": ["A"], + "DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH": ["A"], + "DD_TRACING_ENABLED": ["A"], + "DD_VERSION": ["A"], + "DD_VERTEXAI_SPAN_CHAR_LIMIT": ["A"], + "DD_VERTEXAI_SPAN_PROMPT_COMPLETION_SAMPLE_RATE": ["A"], + "OTEL_LOG_LEVEL": ["A"], + "OTEL_LOGS_EXPORTER": ["A"], + "OTEL_METRICS_EXPORTER": ["A"], + "OTEL_PROPAGATORS": ["A"], + "OTEL_RESOURCE_ATTRIBUTES": ["A"], + "OTEL_SDK_DISABLED": ["A"], + "OTEL_SERVICE_NAME": ["A"], + "OTEL_TRACES_EXPORTER": ["A"], + "OTEL_TRACES_SAMPLER_ARG": ["A"], + "OTEL_TRACES_SAMPLER": ["A"] + }, + "aliases": { + "DD_AGENT_HOST": ["DD_TRACE_AGENT_HOSTNAME"], + "DD_API_KEY": ["DATADOG_API_KEY"], + "DD_API_SECURITY_ENABLED": ["DD_EXPERIMENTAL_API_SECURITY_ENABLED"], + "DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE": ["DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING"], + "DD_DOGSTATSD_HOST": ["DD_DOGSTATSD_HOSTNAME"], + "DD_INSTRUMENTATION_TELEMETRY_ENABLED": ["DD_TRACE_TELEMETRY_ENABLED"], + "DD_PROFILING_CODEHOTSPOTS_ENABLED": ["DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED"], + "DD_PROFILING_CPU_ENABLED": ["DD_PROFILING_EXPERIMENTAL_CPU_ENABLED"], + "DD_PROFILING_ENABLED": ["DD_EXPERIMENTAL_PROFILING_ENABLED"], + "DD_PROFILING_ENDPOINT_COLLECTION_ENABLED": ["DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED"], + "DD_PROFILING_TIMELINE_ENABLED": ["DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED"], + "DD_REMOTE_CONFIGURATION_ENABLED": ["DD_REMOTE_CONFIG_ENABLED" ], + "DD_SERVICE": ["DD_SERVICE_NAME"], + "DD_TRACE_AGENT_URL": ["DD_TRACE_URL"] + }, + "deprecations": { + "DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED": "DD_PROFILING_ENDPOINT_COLLECTION_ENABLED", + "DD_PROFILING_EXPERIMENTAL_CPU_ENABLED": "DD_PROFILING_CPU_ENABLED", + "DD_EXPERIMENTAL_PROFILING_ENABLED": "DD_PROFILING_ENABLED", + "DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED": "DD_PROFILING_CODEHOTSPOTS_ENABLED", + "DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED": "DD_PROFILING_TIMELINE_ENABLED" + } +} diff --git a/packages/dd-trace/src/telemetry/dependencies.js b/packages/dd-trace/src/telemetry/dependencies.js index dee894d726c..27c2f707921 100644 --- a/packages/dd-trace/src/telemetry/dependencies.js +++ b/packages/dd-trace/src/telemetry/dependencies.js @@ -8,6 +8,7 @@ const dc = require('dc-polyfill') const { fileURLToPath } = require('url') const { isTrue } = require('../../src/util') +/** @type {Set} */ const savedDependenciesToSend = new Set() const detectedDependencyKeys = new Set() const detectedDependencyVersions = new Set() @@ -15,63 +16,66 @@ const detectedDependencyVersions = new Set() const FILE_URI_START = 'file://' const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart') -let immediate, config, application, host, initialLoad +let config, application, host, initialLoad let isFirstModule = true let getRetryData let updateRetryData -function createBatchPayload (payload) { - const batchPayload = payload.map(item => { - return { - request_type: item.reqType, - payload: item.payload - } - }) - - return batchPayload -} function waitAndSend (config, application, host) { - if (!immediate) { - immediate = setImmediate(() => { - immediate = null - if (savedDependenciesToSend.size > 0) { - const dependencies = [...savedDependenciesToSend.values()] - // if a dependency is from the initial load, *always* send the event - // Otherwise, only send if dependencyCollection is enabled - .filter(dep => { - const initialLoadModule = isTrue(dep.split(' ')[2]) - const sendModule = initialLoadModule || (config.telemetry?.dependencyCollection) - - if (!sendModule) savedDependenciesToSend.delete(dep) // we'll never send it - return sendModule - }) - .splice(0, 2000) // v2 documentation specifies up to 2000 dependencies can be sent at once - .map(pair => { - savedDependenciesToSend.delete(pair) - const [name, version] = pair.split(' ') - return { name, version } - }) - let currPayload - const retryData = getRetryData() - if (retryData) { - currPayload = { reqType: 'app-dependencies-loaded', payload: { dependencies } } - } else { - if (!dependencies.length) return // no retry data and no dependencies, nothing to send - currPayload = { dependencies } + setImmediate(() => { + if (savedDependenciesToSend.size === 0) { + return + } + const dependencies = [] + let send = 0 + for (const dependency of savedDependenciesToSend) { + const [name, version, initialLoadModule] = dependency.split(' ') + // If a dependency is from the initial load, *always* send the event + // Otherwise, only send if dependencyCollection is enabled + const sendModule = isTrue(initialLoadModule) || config.telemetry?.dependencyCollection + + savedDependenciesToSend.delete(dependency) + + if (sendModule) { + dependencies.push({ name, version }) + send++ + if (send === 2000) { + // v2 documentation specifies up to 2000 dependencies can be sent at once + break } + } + } - const payload = retryData ? createBatchPayload([currPayload, retryData]) : currPayload - const reqType = retryData ? 'message-batch' : 'app-dependencies-loaded' + /** + * @type { { dependencies: typeof dependencies } | { + * request_type: string, + * payload: typeof dependencies + * }[]} + */ + let payload = { dependencies } + let reqType = 'app-dependencies-loaded' + const retryData = getRetryData() + + if (retryData) { + payload = [{ + request_type: 'app-dependencies-loaded', + payload + }, { + request_type: retryData.reqType, + payload: retryData.payload + }] + reqType = 'message-batch' + } else if (!dependencies.length) { + // No retry data and no dependencies, nothing to send + return + } - sendData(config, application, host, reqType, payload, updateRetryData) + sendData(config, application, host, reqType, payload, updateRetryData) - if (savedDependenciesToSend.size > 0) { - waitAndSend(config, application, host) - } - } - }) - immediate.unref() - } + if (savedDependenciesToSend.size > 0) { + waitAndSend(config, application, host) + } + }).unref() } function loadAllTheLoadedModules () { @@ -91,7 +95,7 @@ function onModuleLoad (data) { if (data) { let filename = data.filename - if (filename && filename.startsWith(FILE_URI_START)) { + if (filename?.startsWith(FILE_URI_START)) { try { filename = fileURLToPath(filename) } catch { @@ -99,10 +103,10 @@ function onModuleLoad (data) { } } const parseResult = filename && parse(filename) - const request = data.request || (parseResult && parseResult.name) - const dependencyKey = parseResult && parseResult.basedir ? parseResult.basedir : request + const request = data.request || parseResult?.name + const dependencyKey = parseResult?.basedir ?? request - if (filename && request && isDependency(filename, request) && !detectedDependencyKeys.has(dependencyKey)) { + if (filename && request && isDependency(request) && !detectedDependencyKeys.has(dependencyKey)) { detectedDependencyKeys.add(dependencyKey) if (parseResult) { @@ -135,20 +139,21 @@ function start (_config = {}, _application, _host, getRetryDataFunction, updateR updateRetryData = updateRetryDatafunction moduleLoadStartChannel.subscribe(onModuleLoad) - // try and capture intially loaded modules in the first tick + // Try and capture initially loaded modules in the first tick // since, ideally, the tracer (and this module) should be loaded first, // this should capture any first-tick dependencies queueMicrotask(() => { initialLoad = false }) } -function isDependency (filename, request) { - const isDependencyWithSlash = isDependencyWithSeparator(filename, request, '/') +function isDependency (request) { + const isDependencyWithSlash = isDependencyWithSeparator(request, '/') if (isDependencyWithSlash && process.platform === 'win32') { - return isDependencyWithSeparator(filename, request, path.sep) + return isDependencyWithSeparator(request, path.sep) } return isDependencyWithSlash } -function isDependencyWithSeparator (filename, request, sep) { + +function isDependencyWithSeparator (request, sep) { return request.indexOf(`..${sep}`) !== 0 && request.indexOf(`.${sep}`) !== 0 && request.indexOf(sep) !== 0 && diff --git a/packages/dd-trace/src/telemetry/send-data.js b/packages/dd-trace/src/telemetry/send-data.js index 3f3ce51553f..9a1d9e828f9 100644 --- a/packages/dd-trace/src/telemetry/send-data.js +++ b/packages/dd-trace/src/telemetry/send-data.js @@ -1,6 +1,7 @@ const request = require('../exporters/common/request') const log = require('../log') const { isTrue } = require('../util') +const { getEnvironmentVariable } = require('../config-helper') let agentTelemetry = true @@ -36,10 +37,9 @@ function getPayload (payload) { // 'logs' request type payload is meant to send library logs to Datadog’s backend. if (Array.isArray(payload)) { return payload - } else { - const { logger, tags, serviceMapping, ...trimmedPayload } = payload - return trimmedPayload } + const { logger, tags, serviceMapping, ...trimmedPayload } = payload + return trimmedPayload } function sendData (config, application, host, reqType, payload = {}, cb = () => {}) { @@ -51,7 +51,8 @@ function sendData (config, application, host, reqType, payload = {}, cb = () => let url = config.url - const isCiVisibilityAgentlessMode = isCiVisibility && isTrue(process.env.DD_CIVISIBILITY_AGENTLESS_ENABLED) + const isCiVisibilityAgentlessMode = isCiVisibility && + isTrue(getEnvironmentVariable('DD_CIVISIBILITY_AGENTLESS_ENABLED')) if (isCiVisibilityAgentlessMode) { try { @@ -85,14 +86,14 @@ function sendData (config, application, host, reqType, payload = {}, cb = () => }) request(data, options, (error) => { - if (error && process.env.DD_API_KEY && config.site) { + if (error && getEnvironmentVariable('DD_API_KEY') && config.site) { if (agentTelemetry) { log.warn('Agent telemetry failed, started agentless telemetry') agentTelemetry = false } // figure out which data center to send to const backendUrl = getAgentlessTelemetryEndpoint(config.site) - const backendHeader = { ...options.headers, 'DD-API-KEY': process.env.DD_API_KEY } + const backendHeader = { ...options.headers, 'DD-API-KEY': getEnvironmentVariable('DD_API_KEY') } const backendOptions = { ...options, url: backendUrl, diff --git a/packages/dd-trace/src/telemetry/telemetry.js b/packages/dd-trace/src/telemetry/telemetry.js index 62cda8a9423..116071b3414 100644 --- a/packages/dd-trace/src/telemetry/telemetry.js +++ b/packages/dd-trace/src/telemetry/telemetry.js @@ -22,7 +22,7 @@ let heartbeatTimeout let heartbeatInterval let extendedInterval let integrations -let configWithOrigin = [] +const configWithOrigin = new Map() let retryData = null const extendedHeartbeatPayload = {} @@ -113,7 +113,7 @@ function getInstallSignature (config) { function appStarted (config) { const app = { products: getProducts(config), - configuration: configWithOrigin + configuration: [...configWithOrigin.values()] } const installSignature = getInstallSignature(config) if (installSignature) { @@ -282,7 +282,7 @@ function stop () { } function updateIntegrations () { - if (!config || !config.telemetry.enabled) { + if (!config?.telemetry.enabled) { return } const integrations = getIntegrations() @@ -322,6 +322,8 @@ const nameMapping = { traceId128BitLoggingEnabled: 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED' } +const namesNeedFormatting = new Set(['DD_TAGS', 'peerServiceMapping', 'serviceMapping']) + function updateConfig (changes, config) { if (!config.telemetry.enabled) return if (changes.length === 0) return @@ -331,15 +333,11 @@ function updateConfig (changes, config) { const application = createAppObject(config) const host = createHostObject() - const namesNeedFormatting = new Set(['DD_TAGS', 'peerServiceMapping', 'serviceMapping']) - - const configuration = [] - const names = [] // list of config names whose values have been changed + const changed = configWithOrigin.size > 0 for (const change of changes) { const name = nameMapping[change.name] || change.name - names.push(name) const { origin, value } = change const entry = { name, value, origin } @@ -354,21 +352,15 @@ function updateConfig (changes, config) { } else if (Array.isArray(entry.value)) { entry.value = value.join(',') } - configuration.push(entry) - } - - function isNotModified (entry) { - return !names.includes(entry.name) + configWithOrigin.set(name, entry) } - if (configWithOrigin.length) { + if (changed) { // update configWithOrigin to contain up-to-date full list of config values for app-extended-heartbeat - configWithOrigin = configWithOrigin.filter(isNotModified) - configWithOrigin = [...configWithOrigin, ...configuration] - const { reqType, payload } = createPayload('app-client-configuration-change', { configuration }) + const { reqType, payload } = createPayload('app-client-configuration-change', { + configuration: [...configWithOrigin.values()] + }) sendData(config, application, host, reqType, payload, updateRetryData) - } else { - configWithOrigin = configuration } } @@ -376,12 +368,7 @@ function profilingEnabledToBoolean (profilingEnabled) { if (typeof profilingEnabled === 'boolean') { return profilingEnabled } - if (['auto', 'true'].includes(profilingEnabled)) { - return true - } - if (profilingEnabled === 'false') { - return false - } + return profilingEnabled === 'true' || profilingEnabled === 'auto' } module.exports = { diff --git a/packages/dd-trace/src/tracer.js b/packages/dd-trace/src/tracer.js index 04c8666199f..86a373a0ff5 100644 --- a/packages/dd-trace/src/tracer.js +++ b/packages/dd-trace/src/tracer.js @@ -46,9 +46,7 @@ class DatadogTracer extends Tracer { } trace (name, options, fn) { - options = Object.assign({ - childOf: this.scope().active() - }, options) + options = { childOf: this.scope().active(), ...options } const span = this.startSpan(name, options) @@ -76,9 +74,8 @@ class DatadogTracer extends Tracer { throw err } ) - } else { - span.finish() } + span.finish() return result } catch (e) { @@ -110,9 +107,8 @@ class DatadogTracer extends Tracer { return fn.apply(this, arguments) }) - } else { - return tracer.trace(name, optionsObj, () => fn.apply(this, arguments)) } + return tracer.trace(name, optionsObj, () => fn.apply(this, arguments)) } } diff --git a/packages/dd-trace/src/util.js b/packages/dd-trace/src/util.js index 676d278f19e..a39ce9d9b7e 100644 --- a/packages/dd-trace/src/util.js +++ b/packages/dd-trace/src/util.js @@ -69,10 +69,6 @@ function calculateDDBasePath (dirname) { return dirSteps.slice(0, packagesIndex + 1).join(path.sep) + path.sep } -function hasOwn (object, prop) { - return Object.prototype.hasOwnProperty.call(object, prop) -} - function normalizeProfilingEnabledValue (configValue) { return isTrue(configValue) ? 'true' @@ -87,6 +83,5 @@ module.exports = { isError, globMatch, ddBasePath: calculateDDBasePath(__dirname), - hasOwn, normalizeProfilingEnabledValue } diff --git a/packages/dd-trace/test/appsec/iast/analyzers/header-injection.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/header-injection.express.plugin.spec.js deleted file mode 100644 index 7af02e47637..00000000000 --- a/packages/dd-trace/test/appsec/iast/analyzers/header-injection.express.plugin.spec.js +++ /dev/null @@ -1,419 +0,0 @@ -'use strict' - -const axios = require('axios') -const fs = require('fs') -const os = require('os') -const path = require('path') -const { prepareTestServerForIastInExpress } = require('../utils') - -describe('Header injection vulnerability', () => { - let setHeaderFunction - const setHeaderFunctionFilename = 'set-header-function.js' - const setHeaderFunctionPath = path.join(os.tmpdir(), setHeaderFunctionFilename) - - before(() => { - fs.copyFileSync(path.join(__dirname, 'resources', 'set-header-function.js'), setHeaderFunctionPath) - setHeaderFunction = require(setHeaderFunctionPath).setHeader - }) - - after(() => { - fs.unlinkSync(setHeaderFunctionPath) - }) - - withVersions('express', 'express', version => { - prepareTestServerForIastInExpress('in express', version, - (testThatRequestHasVulnerability, testThatRequestHasNoVulnerability) => { - testThatRequestHasVulnerability({ - fn: (req, res) => { - setHeaderFunction('custom', req.body.test, res) - }, - vulnerability: 'HEADER_INJECTION', - occurrencesAndLocation: { - occurrences: 1, - location: { - path: setHeaderFunctionFilename, - line: 4 - } - }, - cb: (headerInjectionVulnerabilities) => { - const evidenceString = headerInjectionVulnerabilities[0].evidence.valueParts - .map(part => part.value).join('') - expect(evidenceString).to.be.equal('custom: value') - }, - makeRequest: (done, config) => { - return axios.post(`http://localhost:${config.port}/`, { - test: 'value' - }).catch(done) - } - }) - - testThatRequestHasVulnerability({ - testDescription: 'should have HEADER_INJECTION vulnerability ' + - 'when the header value is an array with tainted string', - fn: (req, res) => { - setHeaderFunction('custom', ['not_tainted', req.body.test], res) - }, - vulnerability: 'HEADER_INJECTION', - occurrencesAndLocation: { - occurrences: 1, - location: { - path: setHeaderFunctionFilename, - line: 4 - } - }, - cb: (headerInjectionVulnerabilities) => { - const evidenceString = headerInjectionVulnerabilities[0].evidence.valueParts - .map(part => part.value).join('') - - expect(evidenceString).to.be.equal('custom: value') - }, - makeRequest: (done, config) => { - return axios.post(`http://localhost:${config.port}/`, { - test: 'value' - }).catch(done) - } - }) - - testThatRequestHasNoVulnerability({ - testDescription: 'should not have HEADER_INJECTION vulnerability ' + - 'when the header value an array without tainteds', - fn: (req, res) => { - setHeaderFunction('custom', ['not tainted string 1', 'not tainted string 2'], res) - }, - vulnerability: 'HEADER_INJECTION' - }) - - testThatRequestHasNoVulnerability({ - testDescription: 'should not have HEADER_INJECTION vulnerability ' + - 'when is the header same header', - fn: (req, res) => { - setHeaderFunction('testheader', req.get('testheader'), res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.get(`http://localhost:${config.port}/`, { - headers: { - testheader: 'headerValue' - } - }).catch(done) - } - }) - - testThatRequestHasNoVulnerability({ - testDescription: 'should not have HEADER_INJECTION vulnerability when the header value is not tainted', - fn: (req, res) => { - setHeaderFunction('custom', 'not tainted string', res) - }, - vulnerability: 'HEADER_INJECTION' - }) - } - ) - - describe('Header Injection exclusions', () => { - let i = 0 - let setHeaderFunctionsPath - - before(() => { - setHeaderFunctionsPath = path.join(os.tmpdir(), `set-header-function-${i++}.js`) - fs.copyFileSync( - path.join(__dirname, 'resources', 'set-header-function.js'), - setHeaderFunctionsPath - ) - }) - - after(() => { - fs.unlinkSync(setHeaderFunctionsPath) - }) - - prepareTestServerForIastInExpress('in express', version, - (testThatRequestHasVulnerability, testThatRequestHasNoVulnerability) => { - testThatRequestHasNoVulnerability({ - testDescription: 'should not have HEADER_INJECTION vulnerability when the header is "location"', - fn: (req, res) => { - setHeaderFunction('location', req.body.test, res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.post(`http://localhost:${config.port}/`, { - test: 'https://www.datadoghq.com' - }).catch(done) - } - }) - - testThatRequestHasNoVulnerability({ - testDescription: 'should not have HEADER_INJECTION vulnerability ' + - 'when the header is "Sec-WebSocket-Location"', - fn: (req, res) => { - setHeaderFunction('Sec-WebSocket-Location', req.body.test, res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.post(`http://localhost:${config.port}/`, { - test: 'https://www.datadoghq.com' - }).catch(done) - } - }) - - testThatRequestHasNoVulnerability({ - testDescription: 'should not have HEADER_INJECTION vulnerability when the header is "Sec-WebSocket-Accept"', - fn: (req, res) => { - setHeaderFunction('Sec-WebSocket-Accept', req.body.test, res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.post(`http://localhost:${config.port}/`, { - test: 'https://www.datadoghq.com' - }).catch(done) - } - }) - - testThatRequestHasNoVulnerability({ - testDescription: 'should not have HEADER_INJECTION vulnerability when the header is "Upgrade"', - fn: (req, res) => { - setHeaderFunction('Upgrade', req.body.test, res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.post(`http://localhost:${config.port}/`, { - test: 'https://www.datadoghq.com' - }).catch(done) - } - }) - - testThatRequestHasNoVulnerability({ - testDescription: 'should not have HEADER_INJECTION vulnerability when the header is "Connection"', - fn: (req, res) => { - setHeaderFunction('Upgrade', req.body.test, res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.post(`http://localhost:${config.port}/`, { - test: 'https://www.datadoghq.com' - }).catch(done) - } - }) - - testThatRequestHasNoVulnerability({ - testDescription: 'should not have HEADER_INJECTION vulnerability ' + - 'when the header is "access-control-allow-origin" and the origin is a header', - fn: (req, res) => { - setHeaderFunction('access-control-allow-origin', req.headers.testheader, res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.get(`http://localhost:${config.port}/`, { - headers: { - testheader: 'headerValue' - } - }).catch(done) - } - }) - - testThatRequestHasVulnerability({ - testDescription: 'should have HEADER_INJECTION vulnerability ' + - 'when the header is "access-control-allow-origin" and the origin is not a header', - fn: (req, res) => { - setHeaderFunction('access-control-allow-origin', req.body.test, res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.post(`http://localhost:${config.port}/`, { - test: 'https://www.datadoghq.com' - }, { - headers: { - testheader: 'headerValue' - } - }).catch(done) - } - }) - - testThatRequestHasNoVulnerability({ - testDescription: 'should not have HEADER_INJECTION vulnerability ' + - 'when the header is "set-cookie" and the origin is a cookie', - fn: (req, res) => { - setHeaderFunction('set-cookie', req.cookies.cookie1, res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.get(`http://localhost:${config.port}/`, { - headers: { - Cookie: 'cookie1=value' - } - }).catch(done) - } - }) - - testThatRequestHasVulnerability({ - testDescription: 'should have HEADER_INJECTION vulnerability when ' + - 'the header is "access-control-allow-origin" and the origin is not a header', - fn: (req, res) => { - setHeaderFunction('access-control-allow-origin', req.body.test, res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.post(`http://localhost:${config.port}/`, { - test: 'key=value' - }, { - headers: { - testheader: 'headerValue' - } - }).catch(done) - } - }) - - testThatRequestHasNoVulnerability({ - fn: (req, res) => { - setHeaderFunction('Access-Control-Allow-Origin', req.headers.origin, res) - setHeaderFunction('Access-Control-Allow-Headers', req.headers['access-control-request-headers'], res) - setHeaderFunction('Access-Control-Allow-Methods', req.headers['access-control-request-methods'], res) - }, - testDescription: 'Should not have vulnerability with CORS headers', - vulnerability: 'HEADER_INJECTION', - occurrencesAndLocation: { - occurrences: 1, - location: { - path: setHeaderFunctionFilename, - line: 4 - } - }, - cb: (headerInjectionVulnerabilities) => { - const evidenceString = headerInjectionVulnerabilities[0].evidence.valueParts - .map(part => part.value).join('') - expect(evidenceString).to.be.equal('custom: value') - }, - makeRequest: (done, config) => { - return axios.options(`http://localhost:${config.port}/`, { - headers: { - origin: 'http://custom-origin', - 'Access-Control-Request-Headers': 'TestHeader', - 'Access-Control-Request-Methods': 'GET' - } - }).catch(done) - } - }) - - testThatRequestHasVulnerability({ - testDescription: 'should have HEADER_INJECTION vulnerability when ' + - 'the header is "pragma" and the origin is not a header', - fn: (req, res) => { - setHeaderFunction('pragma', req.body.test, res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.post(`http://localhost:${config.port}/`, { - test: 'key=value' - }, { - headers: { - testheader: 'headerValue' - } - }).catch(done) - } - }) - - testThatRequestHasVulnerability({ - testDescription: 'should have HEADER_INJECTION vulnerability when ' + - 'the header is "pragma" and the origin is not the cache-control header', - fn: (req, res) => { - setHeaderFunction('pragma', req.headers.testheader, res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.post(`http://localhost:${config.port}/`, { - test: 'key=value' - }, { - headers: { - testheader: 'headerValue' - } - }).catch(done) - } - }) - - testThatRequestHasNoVulnerability({ - testDescription: 'should not have HEADER_INJECTION vulnerability ' + - 'when the header is "pragma" and the origin is a cache-control header', - fn: (req, res) => { - setHeaderFunction('pragma', req.headers['cache-control'], res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.get(`http://localhost:${config.port}/`, { - headers: { - 'Cache-Control': 'cachecontrolvalue' - } - }).catch(done) - } - }) - - ;['transfer-encoding', 'content-encoding'].forEach((headerName) => { - testThatRequestHasVulnerability({ - testDescription: 'should have HEADER_INJECTION vulnerability when ' + - `the header is "${headerName}" and the origin is not a header`, - fn: (req, res) => { - setHeaderFunction(headerName, req.body.test, res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.post(`http://localhost:${config.port}/`, { - test: 'key=value' - }, { - headers: { - testheader: 'headerValue' - } - }).catch(done) - } - }) - - testThatRequestHasVulnerability({ - testDescription: 'should have HEADER_INJECTION vulnerability when ' + - `the header is "${headerName}" and the origin is not the accept-encoding header`, - fn: (req, res) => { - setHeaderFunction(headerName, req.headers.testheader, res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.post(`http://localhost:${config.port}/`, { - test: 'key=value' - }, { - headers: { - testheader: 'headerValue' - } - }).catch(done) - } - }) - - testThatRequestHasNoVulnerability({ - testDescription: 'should not have HEADER_INJECTION vulnerability ' + - `when the header is "${headerName}" and the origin is a accept-encoding header`, - fn: (req, res) => { - setHeaderFunction(headerName, req.headers['accept-encoding'], res) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.get(`http://localhost:${config.port}/`, { - headers: { - 'Accept-encoding': 'gzip, deflate' - } - }).catch(done) - } - }) - - testThatRequestHasNoVulnerability({ - testDescription: 'should not have HEADER_INJECTION vulnerability ' + - `when the header is "${headerName}" and the origin is a substring of accept-encoding header`, - fn: (req, res) => { - require(setHeaderFunctionsPath).reflectPartialAcceptEncodingHeader(req, res, headerName) - }, - vulnerability: 'HEADER_INJECTION', - makeRequest: (done, config) => { - return axios.get(`http://localhost:${config.port}/`, { - headers: { - 'Accept-encoding': 'gzip, deflate' - } - }).catch(done) - } - }) - }) - }) - }) - }) -}) diff --git a/packages/dd-trace/test/appsec/iast/analyzers/resources/set-header-function.js b/packages/dd-trace/test/appsec/iast/analyzers/resources/set-header-function.js deleted file mode 100644 index 1883e13bb16..00000000000 --- a/packages/dd-trace/test/appsec/iast/analyzers/resources/set-header-function.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict' - -function setHeader (name, value, res) { - res.setHeader(name, value) -} - -function reflectPartialAcceptEncodingHeader (req, res, headerName) { - const substringAcceptEncodingValue = - req.headers['accept-encoding'].substring(0, req.headers['accept-encoding'].indexOf(',')) - res.setHeader( - headerName, - substringAcceptEncodingValue - ) -} - -module.exports = { - reflectPartialAcceptEncodingHeader, - setHeader -} diff --git a/packages/dd-trace/test/appsec/iast/overhead-controller.spec.js b/packages/dd-trace/test/appsec/iast/overhead-controller.spec.js index 51b7091ea9e..a0d41a53a0a 100644 --- a/packages/dd-trace/test/appsec/iast/overhead-controller.spec.js +++ b/packages/dd-trace/test/appsec/iast/overhead-controller.spec.js @@ -411,7 +411,6 @@ describe('Overhead controller', () => { function tests (serverConfig) { const handlers = [] - beforeEach(() => { testRequestEventEmitter .removeAllListeners(TEST_REQUEST_STARTED) @@ -583,7 +582,6 @@ describe('Overhead controller', () => { } handlers.push(handler) agent.subscribe(handler) - testRequestEventEmitter.on(TEST_REQUEST_STARTED, (url) => { if (url === FIRST_REQUEST) { axios.get(`http://localhost:${serverConfig.port}${SECOND_REQUEST}`).then().catch(done) @@ -591,21 +589,20 @@ describe('Overhead controller', () => { axios.get(`http://localhost:${serverConfig.port}${THIRD_REQUEST}`).then().catch(done) } else if (url === THIRD_REQUEST) { requestResolvers[FIRST_REQUEST]() + } else if (url === FOURTH_REQUEST) { + axios.get(`http://localhost:${serverConfig.port}${FIFTH_REQUEST}`).then().catch(done) } else if (url === FIFTH_REQUEST) { requestResolvers[SECOND_REQUEST]() } }) + testRequestEventEmitter.on(TEST_REQUEST_FINISHED, (url) => { if (url === FIRST_REQUEST) { axios.get(`http://localhost:${serverConfig.port}${FOURTH_REQUEST}`).then().catch(done) - axios.get(`http://localhost:${serverConfig.port}${FIFTH_REQUEST}`).then().catch(done) } else if (url === SECOND_REQUEST) { - // Previously this was a setImmediate, but that made this flaky. Waiting 100ms de-flakes it. - setTimeout(() => { - requestResolvers[THIRD_REQUEST]() - requestResolvers[FOURTH_REQUEST]() - requestResolvers[FIFTH_REQUEST]() - }, 100) + requestResolvers[THIRD_REQUEST]() + requestResolvers[FOURTH_REQUEST]() + requestResolvers[FIFTH_REQUEST]() } }) diff --git a/packages/dd-trace/test/appsec/iast/utils.js b/packages/dd-trace/test/appsec/iast/utils.js index 96e9dfbca23..e4a8b43997e 100644 --- a/packages/dd-trace/test/appsec/iast/utils.js +++ b/packages/dd-trace/test/appsec/iast/utils.js @@ -21,6 +21,13 @@ function testInRequest (app, tests) { let appListener const config = {} + before(() => { + return agent.load('http', undefined, { flushInterval: 1 }) + .then(() => { + http = require('http') + }) + }) + beforeEach(() => { listener = (req, res) => { const appResult = app && app(req, res) @@ -36,13 +43,6 @@ function testInRequest (app, tests) { } }) - beforeEach(() => { - return agent.load('http', undefined, { flushInterval: 1 }) - .then(() => { - http = require('http') - }) - }) - beforeEach(done => { const server = new http.Server(listener) appListener = server @@ -54,6 +54,9 @@ function testInRequest (app, tests) { afterEach(() => { appListener && appListener.close() + }) + + after(() => { return agent.close({ ritmReset: false }) }) diff --git a/packages/dd-trace/test/appsec/iast/vulnerability-formatter/index.spec.js b/packages/dd-trace/test/appsec/iast/vulnerability-formatter/index.spec.js index 66df9da46c5..1217b567494 100644 --- a/packages/dd-trace/test/appsec/iast/vulnerability-formatter/index.spec.js +++ b/packages/dd-trace/test/appsec/iast/vulnerability-formatter/index.spec.js @@ -5,7 +5,7 @@ const sensitiveHandler = require('../../../../src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler') const { suite } = require('./resources/evidence-redaction-suite.json') -const excludedVulnerabilityTypes = ['XSS', 'EMAIL_HTML_INJECTION'] +const excludedVulnerabilityTypes = ['XSS', 'EMAIL_HTML_INJECTION', 'HEADER_INJECTION'] const excludedTests = [ 'Query with single quoted string literal and null source', // does not apply 'Redacted source that needs to be truncated', // not implemented yet diff --git a/packages/dd-trace/test/appsec/index.spec.js b/packages/dd-trace/test/appsec/index.spec.js index a96aeeb6ca8..41a98b862e2 100644 --- a/packages/dd-trace/test/appsec/index.spec.js +++ b/packages/dd-trace/test/appsec/index.spec.js @@ -257,7 +257,7 @@ describe('AppSec Index', function () { } AppSec.enable(config) - expect(appsecTelemetry.enable).to.be.calledOnceWithExactly(config.telemetry) + expect(appsecTelemetry.enable).to.be.calledOnceWithExactly(config) }) it('should call rasp enable', () => { @@ -1220,8 +1220,9 @@ describe('AppSec Index', function () { const metrics = appsecNamespace.metrics.toJSON() - expect(metrics.series.length).to.equal(1) - expect(metrics.series[0].metric).to.equal('waf.init') + expect(metrics.series.length).to.equal(2) + expect(metrics.series[0].metric).to.equal('enabled') + expect(metrics.series[1].metric).to.equal('waf.init') }) it('should not increment waf.init metric if metrics are not enabled', () => { diff --git a/packages/dd-trace/test/appsec/telemetry/index.spec.js b/packages/dd-trace/test/appsec/telemetry/index.spec.js new file mode 100644 index 00000000000..a634422ab72 --- /dev/null +++ b/packages/dd-trace/test/appsec/telemetry/index.spec.js @@ -0,0 +1,136 @@ +'use strict' + +const { assert } = require('chai') +const Config = require('../../../src/config') +const appsecTelemetry = require('../../../src/appsec/telemetry') +const telemetryMetrics = require('../../../src/telemetry/metrics') + +describe('appsec enabled metric', () => { + let appsecNamespace + let originalTelemetryEnabledEnvVar, originalSetInterval, originalAppsecEnabled + + beforeEach(() => { + originalTelemetryEnabledEnvVar = process.env.DD_INSTRUMENTATION_TELEMETRY_ENABLED + originalAppsecEnabled = process.env.DD_APPSEC_ENABLED + originalSetInterval = global.setInterval + appsecNamespace = telemetryMetrics.manager.namespace('appsec') + + appsecNamespace.reset() + appsecNamespace.metrics.clear() + appsecNamespace.distributions.clear() + }) + + afterEach(() => { + process.env.DD_INSTRUMENTATION_TELEMETRY_ENABLED = originalTelemetryEnabledEnvVar + process.env.DD_APPSEC_ENABLED = originalAppsecEnabled + global.setInterval = originalSetInterval + + appsecTelemetry.disable() + appsecNamespace.reset() + }) + + describe('when telemetry is disabled', () => { + beforeEach(() => { + process.env.DD_INSTRUMENTATION_TELEMETRY_ENABLED = 'false' + }) + + it('should not gauge nor interval', () => { + const config = new Config() + global.setInterval = sinon.stub() + + appsecTelemetry.enable(config) + + sinon.assert.notCalled(global.setInterval) + }) + }) + + describe('when telemetry is enabled', () => { + beforeEach(() => { + process.env.DD_INSTRUMENTATION_TELEMETRY_ENABLED = 'true' + }) + + it('should call to gauge.track metric when is enabled by remote config', () => { + const config = new Config() + + appsecTelemetry.enable(config) + + const metrics = appsecNamespace.metrics.toJSON() + + assert.equal(metrics.series.length, 1) + assert.equal(metrics.series[0].metric, 'enabled') + assert.equal(metrics.series[0].type, 'gauge') + assert.equal(metrics.series[0].points.length, 1) + assert.deepEqual(metrics.series[0].tags, ['origin:remote_config']) + }) + + it('should call to gauge.track metric when is enabled by environment variable', () => { + process.env.DD_APPSEC_ENABLED = 'true' + const config = new Config() + + appsecTelemetry.enable(config) + + const metrics = appsecNamespace.metrics.toJSON() + + assert.equal(metrics.series.length, 1) + assert.equal(metrics.series[0].metric, 'enabled') + assert.equal(metrics.series[0].type, 'gauge') + assert.equal(metrics.series[0].points.length, 1) + assert.deepEqual(metrics.series[0].tags, ['origin:env_var']) + }) + + it('should call to gauge.track metric when is enabled by code', () => { + const config = new Config({ appsec: true }) + + appsecTelemetry.enable(config) + + const metrics = appsecNamespace.metrics.toJSON() + + assert.equal(metrics.series.length, 1) + assert.equal(metrics.series[0].metric, 'enabled') + assert.equal(metrics.series[0].type, 'gauge') + assert.equal(metrics.series[0].points.length, 1) + assert.deepEqual(metrics.series[0].tags, ['origin:code']) + }) + + it('should call to gauge.track metric with unknown where is calculated', () => { + const config = new Config({ appsec: true }) + config.getOrigin = () => 'calculated' + + appsecTelemetry.enable(config) + + const metrics = appsecNamespace.metrics.toJSON() + + assert.equal(metrics.series.length, 1) + assert.equal(metrics.series[0].metric, 'enabled') + assert.equal(metrics.series[0].type, 'gauge') + assert.equal(metrics.series[0].points.length, 1) + assert.deepEqual(metrics.series[0].tags, ['origin:unknown']) + }) + + it('should call to track each heartbeatInterval', () => { + const unref = sinon.stub() + global.setInterval = sinon.stub() + global.setInterval.returns({ unref }) + + const config = new Config() + config.telemetry.heartbeatInterval = 10_000 // in milliseconds + + appsecTelemetry.enable(config) + + sinon.assert.calledOnce(global.setInterval) + assert.equal(global.setInterval.firstCall.args[1], 10_000) + const intervalCb = global.setInterval.firstCall.args[0] + sinon.assert.calledOnce(unref) + intervalCb() + intervalCb() + + const metrics = appsecNamespace.metrics.toJSON() + + assert.equal(metrics.series.length, 1) + assert.equal(metrics.series[0].metric, 'enabled') + assert.equal(metrics.series[0].type, 'gauge') + assert.equal(metrics.series[0].points.length, 3) + assert.deepEqual(metrics.series[0].tags, ['origin:remote_config']) + }) + }) +}) diff --git a/packages/dd-trace/test/appsec/telemetry/rasp.spec.js b/packages/dd-trace/test/appsec/telemetry/rasp.spec.js index 8ed5739e11a..7a03cdacb01 100644 --- a/packages/dd-trace/test/appsec/telemetry/rasp.spec.js +++ b/packages/dd-trace/test/appsec/telemetry/rasp.spec.js @@ -4,6 +4,7 @@ const telemetryMetrics = require('../../../src/telemetry/metrics') const appsecNamespace = telemetryMetrics.manager.namespace('appsec') const appsecTelemetry = require('../../../src/appsec/telemetry') +const Config = require('../../../src/config') describe('Appsec Rasp Telemetry metrics', () => { const wafVersion = '0.0.1' @@ -26,10 +27,11 @@ describe('Appsec Rasp Telemetry metrics', () => { describe('if enabled', () => { beforeEach(() => { - appsecTelemetry.enable({ - enabled: true, - metrics: true - }) + const config = new Config() + config.telemetry.enabled = true + config.telemetry.metrics = true + + appsecTelemetry.enable(config) }) describe('updateRaspRequestsMetricTags', () => { diff --git a/packages/dd-trace/test/appsec/telemetry/user.spec.js b/packages/dd-trace/test/appsec/telemetry/user.spec.js index 35f0af60982..ee54fe2d046 100644 --- a/packages/dd-trace/test/appsec/telemetry/user.spec.js +++ b/packages/dd-trace/test/appsec/telemetry/user.spec.js @@ -4,6 +4,7 @@ const telemetryMetrics = require('../../../src/telemetry/metrics') const appsecNamespace = telemetryMetrics.manager.namespace('appsec') const appsecTelemetry = require('../../../src/appsec/telemetry') +const Config = require('../../../src/config') describe('Appsec User Telemetry metrics', () => { let count, inc @@ -22,10 +23,11 @@ describe('Appsec User Telemetry metrics', () => { describe('if enabled', () => { beforeEach(() => { - appsecTelemetry.enable({ - enabled: true, - metrics: true - }) + const config = new Config() + config.telemetry.enabled = true + config.telemetry.metrics = true + + appsecTelemetry.enable(config) }) describe('incrementMissingUserLoginMetric', () => { diff --git a/packages/dd-trace/test/appsec/telemetry/waf.spec.js b/packages/dd-trace/test/appsec/telemetry/waf.spec.js index a7b7e3c1eb5..d562357147a 100644 --- a/packages/dd-trace/test/appsec/telemetry/waf.spec.js +++ b/packages/dd-trace/test/appsec/telemetry/waf.spec.js @@ -4,6 +4,7 @@ const telemetryMetrics = require('../../../src/telemetry/metrics') const appsecNamespace = telemetryMetrics.manager.namespace('appsec') const appsecTelemetry = require('../../../src/appsec/telemetry') +const Config = require('../../../src/config') describe('Appsec Waf Telemetry metrics', () => { const wafVersion = '0.0.1' @@ -36,10 +37,11 @@ describe('Appsec Waf Telemetry metrics', () => { } beforeEach(() => { - appsecTelemetry.enable({ - enabled: true, - metrics: true - }) + const config = new Config() + config.telemetry.enabled = true + config.telemetry.metrics = true + + appsecTelemetry.enable(config) }) describe('updateWafRequestsMetricTags', () => { @@ -212,13 +214,13 @@ describe('Appsec Waf Telemetry metrics', () => { appsecTelemetry.incrementWafInitMetric(wafVersion, rulesVersion, true) const { metrics } = appsecNamespace.toJSON() - expect(metrics.series.length).to.be.eq(1) - expect(metrics.series[0].metric).to.be.eq('waf.init') - expect(metrics.series[0].points.length).to.be.eq(1) - expect(metrics.series[0].points[0][1]).to.be.eq(3) - expect(metrics.series[0].tags).to.include('waf_version:0.0.1') - expect(metrics.series[0].tags).to.include('event_rules_version:0.0.2') - expect(metrics.series[0].tags).to.include('success:true') + expect(metrics.series.length).to.be.eq(2) + expect(metrics.series[1].metric).to.be.eq('waf.init') + expect(metrics.series[1].points.length).to.be.eq(1) + expect(metrics.series[1].points[0][1]).to.be.eq(3) + expect(metrics.series[1].tags).to.include('waf_version:0.0.1') + expect(metrics.series[1].tags).to.include('event_rules_version:0.0.2') + expect(metrics.series[1].tags).to.include('success:true') }) it('should increment waf.init and waf.config_errors on failed init', () => { @@ -227,15 +229,15 @@ describe('Appsec Waf Telemetry metrics', () => { appsecTelemetry.incrementWafInitMetric(wafVersion, rulesVersion, false) const { metrics } = appsecNamespace.toJSON() - expect(metrics.series.length).to.be.eq(2) - expect(metrics.series[0].metric).to.be.eq('waf.init') - expect(metrics.series[0].tags).to.include('waf_version:0.0.1') - expect(metrics.series[0].tags).to.include('event_rules_version:0.0.2') - expect(metrics.series[0].tags).to.include('success:false') - - expect(metrics.series[1].metric).to.be.eq('waf.config_errors') + expect(metrics.series.length).to.be.eq(3) + expect(metrics.series[1].metric).to.be.eq('waf.init') expect(metrics.series[1].tags).to.include('waf_version:0.0.1') expect(metrics.series[1].tags).to.include('event_rules_version:0.0.2') + expect(metrics.series[1].tags).to.include('success:false') + + expect(metrics.series[2].metric).to.be.eq('waf.config_errors') + expect(metrics.series[2].tags).to.include('waf_version:0.0.1') + expect(metrics.series[2].tags).to.include('event_rules_version:0.0.2') }) }) @@ -259,13 +261,13 @@ describe('Appsec Waf Telemetry metrics', () => { appsecTelemetry.incrementWafUpdatesMetric(wafVersion, rulesVersion, true) const { metrics } = appsecNamespace.toJSON() - expect(metrics.series.length).to.be.eq(1) - expect(metrics.series[0].metric).to.be.eq('waf.updates') - expect(metrics.series[0].points.length).to.be.eq(1) - expect(metrics.series[0].points[0][1]).to.be.eq(3) - expect(metrics.series[0].tags).to.include('waf_version:0.0.1') - expect(metrics.series[0].tags).to.include('event_rules_version:0.0.2') - expect(metrics.series[0].tags).to.include('success:true') + expect(metrics.series.length).to.be.eq(2) + expect(metrics.series[1].metric).to.be.eq('waf.updates') + expect(metrics.series[1].points.length).to.be.eq(1) + expect(metrics.series[1].points[0][1]).to.be.eq(3) + expect(metrics.series[1].tags).to.include('waf_version:0.0.1') + expect(metrics.series[1].tags).to.include('event_rules_version:0.0.2') + expect(metrics.series[1].tags).to.include('success:true') }) it('should increment waf.updates and waf.config_errors on failed update', () => { @@ -274,15 +276,15 @@ describe('Appsec Waf Telemetry metrics', () => { appsecTelemetry.incrementWafUpdatesMetric(wafVersion, rulesVersion, false) const { metrics } = appsecNamespace.toJSON() - expect(metrics.series.length).to.be.eq(2) - expect(metrics.series[0].metric).to.be.eq('waf.updates') - expect(metrics.series[0].tags).to.include('waf_version:0.0.1') - expect(metrics.series[0].tags).to.include('event_rules_version:0.0.2') - expect(metrics.series[0].tags).to.include('success:false') - - expect(metrics.series[1].metric).to.be.eq('waf.config_errors') + expect(metrics.series.length).to.be.eq(3) + expect(metrics.series[1].metric).to.be.eq('waf.updates') expect(metrics.series[1].tags).to.include('waf_version:0.0.1') expect(metrics.series[1].tags).to.include('event_rules_version:0.0.2') + expect(metrics.series[1].tags).to.include('success:false') + + expect(metrics.series[2].metric).to.be.eq('waf.config_errors') + expect(metrics.series[2].tags).to.include('waf_version:0.0.1') + expect(metrics.series[2].tags).to.include('event_rules_version:0.0.2') }) }) diff --git a/packages/dd-trace/test/config.spec.js b/packages/dd-trace/test/config.spec.js index 77668f7883c..49b8daebe2e 100644 --- a/packages/dd-trace/test/config.spec.js +++ b/packages/dd-trace/test/config.spec.js @@ -6,6 +6,10 @@ const { expect } = require('chai') const { readFileSync } = require('fs') const sinon = require('sinon') const { GRPC_CLIENT_ERROR_STATUSES, GRPC_SERVER_ERROR_STATUSES } = require('../src/constants') +const { it, describe } = require('tap/lib/mocha.js') +const assert = require('assert/strict') +const { getEnvironmentVariable, getEnvironmentVariables } = require('../src/config-helper') +const { once } = require('events') describe('Config', () => { let Config @@ -77,6 +81,47 @@ describe('Config', () => { existsSyncParam = undefined }) + describe('config-helper', () => { + it('should throw when accessing unknown configuration', () => { + assert.throws( + () => getEnvironmentVariable('DD_UNKNOWN_CONFIG'), + /Missing DD_UNKNOWN_CONFIG env\/configuration in "supported-configurations.json" file./ + ) + }) + + it('should return aliased value', () => { + process.env.DATADOG_API_KEY = '12345' + assert.throws(() => getEnvironmentVariable('DATADOG_API_KEY'), { + message: /Missing DATADOG_API_KEY env\/configuration in "supported-configurations.json" file./ + }) + assert.strictEqual(getEnvironmentVariable('DD_API_KEY'), '12345') + const { DD_API_KEY, DATADOG_API_KEY } = getEnvironmentVariables() + assert.strictEqual(DATADOG_API_KEY, undefined) + assert.strictEqual(DD_API_KEY, getEnvironmentVariable('DD_API_KEY')) + delete process.env.DATADOG_API_KEY + }) + + it('should log deprecation warning for deprecated configurations', async () => { + process.env.DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED = 'true' + getEnvironmentVariables() + const [warning] = await once(process, 'warning') + assert.strictEqual(warning.name, 'DeprecationWarning') + assert.match( + warning.message, + /variable DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED .+ DD_PROFILING_ENDPOINT_COLLECTION_ENABLED instead/ + ) + assert.strictEqual(warning.code, 'DATADOG_DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED') + }) + + it('should pass through random envs', async () => { + process.env.FOOBAR = 'true' + const { FOOBAR } = getEnvironmentVariables() + assert.strictEqual(FOOBAR, 'true') + assert.strictEqual(getEnvironmentVariable('FOOBAR'), FOOBAR) + delete process.env.FOOBAR + }) + }) + it('should initialize its own logging config based off the loggers config', () => { process.env.DD_TRACE_DEBUG = 'true' process.env.DD_TRACE_LOG_LEVEL = 'error' @@ -1303,14 +1348,12 @@ describe('Config', () => { process.env.DD_TRACE_CLIENT_IP_HEADER = 'foo-bar-header' process.env.DD_TRACE_GLOBAL_TAGS = 'foo:bar,baz:qux' process.env.DD_TRACE_EXPERIMENTAL_B3_ENABLED = 'true' - process.env.DD_TRACE_EXPERIMENTAL_TRACEPARENT_ENABLED = 'true' process.env.DD_TRACE_PROPAGATION_STYLE_INJECT = 'datadog' process.env.DD_TRACE_PROPAGATION_STYLE_EXTRACT = 'datadog' process.env.DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT = 'restart' process.env.DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED = 'true' process.env.DD_TRACE_EXPERIMENTAL_EXPORTER = 'log' process.env.DD_TRACE_EXPERIMENTAL_GET_RUM_DATA_ENABLED = 'true' - process.env.DD_TRACE_EXPERIMENTAL_INTERNAL_ERRORS_ENABLED = 'true' process.env.DD_TRACE_MIDDLEWARE_TRACING_ENABLED = 'false' process.env.DD_APM_TRACING_ENABLED = 'false' process.env.DD_APPSEC_ENABLED = 'false' @@ -2709,4 +2752,38 @@ apm_configuration_default: expect(stableConfig).to.not.have.property('stableConfig') }) }) + + context('getOrigin', () => { + let originalAppsecEnabled + + beforeEach(() => { + originalAppsecEnabled = process.env.DD_APPSEC_ENABLED + }) + + afterEach(() => { + process.env.DD_APPSEC_ENABLED = originalAppsecEnabled + }) + + it('should return default value', () => { + const config = new Config() + + expect(config.getOrigin('appsec.enabled')).to.be.equal('default') + }) + + it('should return env_var', () => { + process.env.DD_APPSEC_ENABLED = 'true' + + const config = new Config() + + expect(config.getOrigin('appsec.enabled')).to.be.equal('env_var') + }) + + it('should return code', () => { + const config = new Config({ + appsec: true + }) + + expect(config.getOrigin('appsec.enabled')).to.be.equal('code') + }) + }) }) diff --git a/packages/dd-trace/test/llmobs/index.spec.js b/packages/dd-trace/test/llmobs/index.spec.js index 7c6851d18df..761fc5005ba 100644 --- a/packages/dd-trace/test/llmobs/index.spec.js +++ b/packages/dd-trace/test/llmobs/index.spec.js @@ -62,7 +62,7 @@ describe('module', () => { }) describe('handle llmobs info injection', () => { - it('injects LLMObs parent ID when there is a parent LLMObs span', () => { + it('injects LLMObs info when there is a parent LLMObs span', () => { llmobsModule.enable({ llmobs: { mlApp: 'test', agentlessEnabled: false } }) store.span = { context () { @@ -79,12 +79,22 @@ describe('module', () => { } injectCh.publish({ carrier }) - expect(carrier['x-datadog-tags']).to.equal(',_dd.p.llmobs_parent_id=parent-id') + expect(carrier['x-datadog-tags']).to.equal(',_dd.p.llmobs_parent_id=parent-id,_dd.p.llmobs_ml_app=test') }) - it('does not inject LLMObs parent ID when there is no parent LLMObs span', () => { + it('does not inject LLMObs parent ID info when there is no parent LLMObs span', () => { llmobsModule.enable({ llmobs: { mlApp: 'test', agentlessEnabled: false } }) + const carrier = { + 'x-datadog-tags': '' + } + injectCh.publish({ carrier }) + expect(carrier['x-datadog-tags']).to.equal(',_dd.p.llmobs_ml_app=test') + }) + + it('does not inject LLMOBs info when there is no mlApp configured and no parent LLMObs span', () => { + llmobsModule.enable({ llmobs: { agentlessEnabled: false } }) + const carrier = { 'x-datadog-tags': '' } diff --git a/packages/dd-trace/test/llmobs/sdk/index.spec.js b/packages/dd-trace/test/llmobs/sdk/index.spec.js index 76f70a10424..b9ce3414414 100644 --- a/packages/dd-trace/test/llmobs/sdk/index.spec.js +++ b/packages/dd-trace/test/llmobs/sdk/index.spec.js @@ -1229,8 +1229,9 @@ describe('sdk', () => { describe('distributed', () => { it('adds the current llmobs span id to the injection context', () => { const carrier = { 'x-datadog-tags': '' } - let parentId - llmobs.trace({ kind: 'workflow', name: 'myWorkflow' }, span => { + let parentId, span + llmobs.trace({ kind: 'workflow', name: 'myWorkflow' }, _span => { + span = _span parentId = span.context().toSpanId() // simulate injection from http integration or from tracer @@ -1238,7 +1239,7 @@ describe('sdk', () => { injectCh.publish({ carrier }) }) - expect(carrier['x-datadog-tags']).to.equal(`,_dd.p.llmobs_parent_id=${parentId}`) + expect(carrier['x-datadog-tags']).to.equal(`,_dd.p.llmobs_parent_id=${parentId},_dd.p.llmobs_ml_app=mlApp`) }) }) }) diff --git a/packages/dd-trace/test/llmobs/sdk/integration.spec.js b/packages/dd-trace/test/llmobs/sdk/integration.spec.js index d97ae7a1d63..4b1e58ca5db 100644 --- a/packages/dd-trace/test/llmobs/sdk/integration.spec.js +++ b/packages/dd-trace/test/llmobs/sdk/integration.spec.js @@ -15,6 +15,11 @@ const EvalMetricsWriter = require('../../../src/llmobs/writers/evaluations') const tracerVersion = require('../../../../../package.json').version +function getTag (llmobsSpan, tagName) { + const tag = llmobsSpan.tags.find(tag => tag.split(':')[0] === tagName) + return tag?.split(':')[1] +} + describe('end to end sdk integration tests', () => { let tracer let llmobs @@ -199,7 +204,7 @@ describe('end to end sdk integration tests', () => { expect(evaluationMetrics).to.have.lengthOf(1) // check eval metrics content - const exptected = [ + const expected = [ { trace_id: spans[0].context().toTraceId(true), span_id: spans[0].context().toSpanId(), @@ -212,8 +217,81 @@ describe('end to end sdk integration tests', () => { } ] - check(exptected, evaluationMetrics) + check(expected, evaluationMetrics) Date.now.restore() }) + + describe('distributed', () => { + it('injects and extracts the proper llmobs context', () => { + payloadGenerator = function () { + const carrier = {} + llmobs.trace({ kind: 'workflow', name: 'parent' }, workflow => { + tracer.inject(workflow, 'text_map', carrier) + }) + + const spanContext = tracer.extract('text_map', carrier) + tracer.trace('new-service-root', { childOf: spanContext }, () => { + llmobs.trace({ kind: 'workflow', name: 'child' }, () => {}) + }) + } + + const { llmobsSpans } = run(payloadGenerator) + expect(llmobsSpans).to.have.lengthOf(2) + + expect(getTag(llmobsSpans[0], 'ml_app')).to.equal('test') + expect(getTag(llmobsSpans[1], 'ml_app')).to.equal('test') + }) + + it('injects the local mlApp', () => { + payloadGenerator = function () { + const carrier = {} + llmobs.trace({ kind: 'workflow', name: 'parent', mlApp: 'span-level-ml-app' }, workflow => { + tracer.inject(workflow, 'text_map', carrier) + }) + + const spanContext = tracer.extract('text_map', carrier) + tracer.trace('new-service-root', { childOf: spanContext }, () => { + llmobs.trace({ kind: 'workflow', name: 'child' }, () => {}) + }) + } + + const { llmobsSpans } = run(payloadGenerator) + expect(llmobsSpans).to.have.lengthOf(2) + + expect(getTag(llmobsSpans[0], 'ml_app')).to.equal('span-level-ml-app') + expect(getTag(llmobsSpans[1], 'ml_app')).to.equal('span-level-ml-app') + }) + + it('injects a distributed mlApp', () => { + payloadGenerator = function () { + let carrier = {} + llmobs.trace({ kind: 'workflow', name: 'parent' }, workflow => { + tracer.inject(workflow, 'text_map', carrier) + }) + + // distributed call to service 2 + let spanContext = tracer.extract('text_map', carrier) + carrier = {} + tracer.trace('new-service-root', { childOf: spanContext }, () => { + llmobs.trace({ kind: 'workflow', name: 'child-1' }, child => { + tracer.inject(child, 'text_map', carrier) + }) + }) + + // distributed call to service 3 + spanContext = tracer.extract('text_map', carrier) + tracer.trace('new-service-root', { childOf: spanContext }, () => { + llmobs.trace({ kind: 'workflow', name: 'child-2' }, () => {}) + }) + } + + const { llmobsSpans } = run(payloadGenerator) + expect(llmobsSpans).to.have.lengthOf(3) + + expect(getTag(llmobsSpans[0], 'ml_app')).to.equal('test') + expect(getTag(llmobsSpans[1], 'ml_app')).to.equal('test') + expect(getTag(llmobsSpans[2], 'ml_app')).to.equal('test') + }) + }) }) diff --git a/packages/dd-trace/test/llmobs/tagger.spec.js b/packages/dd-trace/test/llmobs/tagger.spec.js index 8faa1a5cb6f..69f558fbcab 100644 --- a/packages/dd-trace/test/llmobs/tagger.spec.js +++ b/packages/dd-trace/test/llmobs/tagger.spec.js @@ -158,6 +158,34 @@ describe('tagger', () => { expect(Tagger.tagMap.get(span)).to.be.undefined }) + + it('uses the propagated mlApp over the global mlApp if both are provided', () => { + spanContext._trace.tags['_dd.p.llmobs_ml_app'] = 'my-propagated-ml-app' + + tagger.registerLLMObsSpan(span, { kind: 'llm' }) + + const tags = Tagger.tagMap.get(span) + expect(tags['_ml_obs.meta.ml_app']).to.equal('my-propagated-ml-app') + }) + + describe('with no global mlApp configured', () => { + beforeEach(() => { + tagger = new Tagger({ llmobs: { enabled: true } }) + }) + + it('uses the mlApp from the propagated mlApp if no mlApp is provided', () => { + spanContext._trace.tags['_dd.p.llmobs_ml_app'] = 'my-propagated-ml-app' + + tagger.registerLLMObsSpan(span, { kind: 'llm' }) + + const tags = Tagger.tagMap.get(span) + expect(tags['_ml_obs.meta.ml_app']).to.equal('my-propagated-ml-app') + }) + + it('throws an error if no mlApp is provided and no propagated mlApp is provided', () => { + expect(() => tagger.registerLLMObsSpan(span, { kind: 'llm' })).to.throw() + }) + }) }) describe('tagMetadata', () => { diff --git a/packages/dd-trace/test/plugin_manager.spec.js b/packages/dd-trace/test/plugin_manager.spec.js index e497efce321..9abcf70e1d4 100644 --- a/packages/dd-trace/test/plugin_manager.spec.js +++ b/packages/dd-trace/test/plugin_manager.spec.js @@ -71,7 +71,12 @@ describe('Plugin Manager', () => { PluginManager = proxyquire.noPreserveCache()('../src/plugin_manager', { './plugins': { ...plugins, '@noCallThru': true }, - '../../datadog-instrumentations': {} + '../../datadog-instrumentations': {}, + '../../dd-trace/src/config-helper': { + getEnvironmentVariable (name) { + return process.env[name] + } + } }) pm = new PluginManager(tracer) }) diff --git a/packages/dd-trace/test/plugins/plugin.spec.js b/packages/dd-trace/test/plugins/plugin.spec.js index a5986b9bba3..f0d71da6feb 100644 --- a/packages/dd-trace/test/plugins/plugin.spec.js +++ b/packages/dd-trace/test/plugins/plugin.spec.js @@ -3,10 +3,12 @@ require('../setup/tap') const Plugin = require('../../src/plugins/plugin') -const plugins = require('../../src/plugins') +const { storage } = require('../../../datadog-core') const { channel } = require('dc-polyfill') describe('Plugin', () => { + let plugin + class BadPlugin extends Plugin { static get id () { return 'badPlugin' } @@ -33,20 +35,12 @@ describe('Plugin', () => { } } - const testPlugins = { badPlugin: BadPlugin, goodPlugin: GoodPlugin } - const loadChannel = channel('dd-trace:instrumentation:load') - - before(() => { - for (const [name, cls] of Object.entries(testPlugins)) { - plugins[name] = cls - loadChannel.publish({ name }) - } + after(() => { + plugin.configure({ enabled: false }) }) - after(() => { Object.keys(testPlugins).forEach(name => delete plugins[name]) }) - it('should disable upon error', () => { - const plugin = new BadPlugin() + plugin = new BadPlugin() plugin.configure({ enabled: true }) expect(plugin._enabled).to.be.true @@ -57,7 +51,7 @@ describe('Plugin', () => { }) it('should not disable with no error', () => { - const plugin = new GoodPlugin() + plugin = new GoodPlugin() plugin.configure({ enabled: true }) expect(plugin._enabled).to.be.true @@ -66,4 +60,24 @@ describe('Plugin', () => { expect(plugin._enabled).to.be.true }) + + it('should run binding transforms with an undefined current store', () => { + class TestPlugin extends Plugin { + static get id () { return 'test' } + + constructor () { + super() + this.addBind('apm:test:start', ctx => ctx.currentStore) + } + } + + plugin = new TestPlugin() + plugin.configure({ enabled: true }) + + storage('legacy').run({ noop: true }, () => { + channel('apm:test:start').runStores({ currentStore: undefined }, () => { + expect(storage('legacy').getStore()).to.equal(undefined) + }) + }) + }) }) diff --git a/packages/dd-trace/test/profiling/config.spec.js b/packages/dd-trace/test/profiling/config.spec.js index 4c67645c990..ba1a48f1b8f 100644 --- a/packages/dd-trace/test/profiling/config.spec.js +++ b/packages/dd-trace/test/profiling/config.spec.js @@ -247,14 +247,6 @@ describe('config', () => { const config = new Config(options) - expect(warnings.length).to.equal(2) - expect(warnings[0]).to.equal( - 'DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED is deprecated. ' + - 'Use DD_PROFILING_ENDPOINT_COLLECTION_ENABLED instead.') - expect(warnings[1]).to.equal( - 'DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED is deprecated. ' + - 'Use DD_PROFILING_CODEHOTSPOTS_ENABLED instead.') - expect(config.profilers).to.be.an('array') expect(config.profilers.length).to.equal(2) expect(config.profilers[0]).to.be.an.instanceOf(WallProfiler) diff --git a/packages/dd-trace/test/profiling/profiler.spec.js b/packages/dd-trace/test/profiling/profiler.spec.js index c9bd47ed1bc..afdfb86a85d 100644 --- a/packages/dd-trace/test/profiling/profiler.spec.js +++ b/packages/dd-trace/test/profiling/profiler.spec.js @@ -8,6 +8,7 @@ const sinon = require('sinon') const SpaceProfiler = require('../../src/profiling/profilers/space') const WallProfiler = require('../../src/profiling/profilers/wall') const EventsProfiler = require('../../src/profiling/profilers/events') +const { setTimeout } = require('node:timers/promises') const samplingContextsAvailable = process.platform !== 'win32' @@ -204,6 +205,7 @@ describe('profiler', function () { clock.tick(interval) await rejected.catch(() => {}) + await setTimeout(1) sinon.assert.notCalled(wallProfiler.stop) sinon.assert.notCalled(spaceProfiler.stop) @@ -220,6 +222,7 @@ describe('profiler', function () { clock.tick(interval) await rejected.catch(() => {}) + await setTimeout(1) sinon.assert.notCalled(wallProfiler.stop) sinon.assert.notCalled(spaceProfiler.stop) @@ -324,6 +327,7 @@ describe('profiler', function () { clock.tick(interval) await waitForExport() + await setTimeout(1) const [ startWall, diff --git a/packages/dd-trace/test/sampler.spec.js b/packages/dd-trace/test/sampler.spec.js index 5db554e1d48..800fc1b532b 100644 --- a/packages/dd-trace/test/sampler.spec.js +++ b/packages/dd-trace/test/sampler.spec.js @@ -40,7 +40,7 @@ describe('Sampler', () => { rates.forEach(([rate, expected]) => { sampler = new Sampler(rate) - expect(sampler._threshold).to.equal(expected) + expect(sampler.threshold).to.equal(expected) }) }) }) diff --git a/packages/dd-trace/test/startup-log.spec.js b/packages/dd-trace/test/startup-log.spec.js index 1159d0f9531..9eb3ee17f04 100644 --- a/packages/dd-trace/test/startup-log.spec.js +++ b/packages/dd-trace/test/startup-log.spec.js @@ -2,13 +2,17 @@ require('./setup/tap') -const os = require('os') -const tracerVersion = require('../../../package.json').version +const assert = require('node:assert') +const os = require('node:os') + const Config = require('../src/config') +const SamplingRule = require('../src/sampling_rule') +const tracerVersion = require('../../../package.json').version describe('startup logging', () => { let firstStderrCall let secondStderrCall + let tracerInfoMethod before(() => { sinon.stub(console, 'info') @@ -18,8 +22,10 @@ describe('startup logging', () => { setStartupLogConfig, setStartupLogPluginManager, setSamplingRules, - startupLog + startupLog, + tracerInfo } = require('../src/startup-log') + tracerInfoMethod = tracerInfo setStartupLogPluginManager({ _pluginsByName: { http: { _enabled: true }, @@ -38,13 +44,17 @@ describe('startup logging', () => { sampler: { sampleRate: 1 }, - tags: { version: '1.2.3' }, + tags: { version: '1.2.3', invalid_but_listed_due_to_mocking: 42n }, logInjection: true, runtimeMetrics: true, startupLogs: true, appsec: { enabled: true } }) - setSamplingRules(['rule1', 'rule2']) + setSamplingRules([ + new SamplingRule({ name: 'rule1', sampleRate: 0.4 }), + 'rule2', + new SamplingRule({ name: 'rule3', sampleRate: 1.4 }) + ]) startupLog({ agentError: { message: 'Error: fake error' } }) firstStderrCall = console.info.firstCall /* eslint-disable-line no-console */ secondStderrCall = console.warn.firstCall /* eslint-disable-line no-console */ @@ -54,15 +64,12 @@ describe('startup logging', () => { it('startupLog should be formatted correctly', () => { expect(firstStderrCall.args[0].startsWith('DATADOG TRACER CONFIGURATION - ')).to.equal(true) - const logObj = JSON.parse(firstStderrCall.args[0].replace('DATADOG TRACER CONFIGURATION - ', '')) - expect(typeof logObj).to.equal('object') - expect(typeof logObj.date).to.equal('string') - expect(logObj.date.length).to.equal(new Date().toISOString().length) - expect(logObj.tags).to.deep.equal({ version: '1.2.3' }) - expect(logObj.sampling_rules).to.deep.equal(['rule1', 'rule2']) - expect(logObj).to.deep.include({ + const info = JSON.parse(String(tracerInfoMethod())) + assert.deepStrictEqual(info, { + date: info.date, os_name: os.type(), os_version: os.release(), + architecture: os.arch(), version: tracerVersion, lang: 'nodejs', lang_version: process.versions.node, @@ -70,12 +77,19 @@ describe('startup logging', () => { enabled: true, service: 'test', agent_url: 'http://example.com:4321', - agent_error: 'Error: fake error', debug: true, sample_rate: 1, + sampling_rules: [ + { matchers: [{ pattern: 'rule1' }], _sampler: { _rate: 0.4 } }, + 'rule2', + { matchers: [{ pattern: 'rule3' }], _sampler: { _rate: 1 } } + ], + tags: { version: '1.2.3', invalid_but_listed_due_to_mocking: '42' }, dd_version: '1.2.3', log_injection_enabled: true, runtime_metrics_enabled: true, + profiling_enabled: false, + integrations_loaded: ['http', 'fs', 'semver'], appsec_enabled: true }) }) @@ -83,14 +97,6 @@ describe('startup logging', () => { it('startupLog should correctly also output the diagnostic message', () => { expect(secondStderrCall.args[0]).to.equal('DATADOG TRACER DIAGNOSTIC - Agent Error: Error: fake error') }) - - it('setStartupLogPlugins should add plugins to integrations_loaded', () => { - const logObj = JSON.parse(firstStderrCall.args[0].replace('DATADOG TRACER CONFIGURATION - ', '')) - const integrationsLoaded = logObj.integrations_loaded - expect(integrationsLoaded).to.include('fs') - expect(integrationsLoaded).to.include('http') - expect(integrationsLoaded).to.include('semver') - }) }) describe('profiling_enabled', () => { diff --git a/scripts/verify-ci-config.js b/scripts/verify-ci-config.js index 996bfcf445d..f4e56543cff 100644 --- a/scripts/verify-ci-config.js +++ b/scripts/verify-ci-config.js @@ -20,7 +20,7 @@ function errorMsg (title, ...message) { } /// / -/// / Verifying plugins.yml and appsec.yml that plugins are consistently tested +/// / Verifying that plugins are consistently tested in at least one GH workflow /// / if (!Module.isBuiltin) { @@ -127,9 +127,12 @@ function pluginErrorMsg (pluginName, title, message) { errorMsg(title + ' for ' + pluginName, message) } -checkPlugins(path.join(__dirname, '..', '.github', 'workflows', 'plugins.yml')) -checkPlugins(path.join(__dirname, '..', '.github', 'workflows', 'instrumentations.yml')) +// TODO: Check all YAML files instead of having to list them here. +checkPlugins(path.join(__dirname, '..', '.github', 'workflows', 'apm-integrations.yml')) checkPlugins(path.join(__dirname, '..', '.github', 'workflows', 'appsec.yml')) +checkPlugins(path.join(__dirname, '..', '.github', 'workflows', 'llmobs.yml')) +checkPlugins(path.join(__dirname, '..', '.github', 'workflows', 'platform.yml')) +checkPlugins(path.join(__dirname, '..', '.github', 'workflows', 'test-optimization.yml')) { const testDir = path.join(__dirname, '..', 'packages', 'datadog-instrumentations', 'test') const testedInstrumentations = fs.readdirSync(testDir) @@ -137,7 +140,7 @@ checkPlugins(path.join(__dirname, '..', '.github', 'workflows', 'appsec.yml')) .map(file => file.replace('.spec.js', '')) for (const instrumentation of testedInstrumentations) { if (!allTestedPlugins.has(instrumentation)) { - pluginErrorMsg(instrumentation, 'ERROR', 'Instrumentation is tested but not in plugins.yml') + pluginErrorMsg(instrumentation, 'ERROR', 'Instrumentation is tested but not in at least one GitHub workflow') } } const allPlugins = fs.readdirSync(path.join(__dirname, '..', 'packages')) @@ -146,7 +149,7 @@ checkPlugins(path.join(__dirname, '..', '.github', 'workflows', 'appsec.yml')) .map(file => file.replace('datadog-plugin-', '')) for (const plugin of allPlugins) { if (!allTestedPlugins.has(plugin)) { - pluginErrorMsg(plugin, 'ERROR', 'Plugin is tested but not in plugins.yml') + pluginErrorMsg(plugin, 'ERROR', 'Plugin is tested but not in at least one GitHub workflow') } } } @@ -170,11 +173,10 @@ const IGNORED_WORKFLOWS = { 'stale.yml' ], trigger_push: [ - 'package-size.yml', 'stale.yml' ], trigger_schedule: [ - 'yarn-dedupe.yml' + 'project.yml' ] } diff --git a/yarn.lock b/yarn.lock index f4eb0ee3913..0f37886529c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -96,7 +96,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== -"@babel/helper-string-parser@^7.25.9", "@babel/helper-string-parser@^7.27.1": +"@babel/helper-string-parser@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== @@ -119,7 +119,7 @@ "@babel/template" "^7.27.2" "@babel/types" "^7.27.3" -"@babel/parser@^7.26.10", "@babel/parser@^7.26.9", "@babel/parser@^7.27.2": +"@babel/parser@^7.26.10", "@babel/parser@^7.27.2": version "7.27.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.3.tgz#1b7533f0d908ad2ac545c4d05cbe2fb6dc8cfaaf" integrity sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw== @@ -198,7 +198,7 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.26.9", "@babel/types@^7.27.1", "@babel/types@^7.27.3": +"@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.27.1", "@babel/types@^7.27.3": version "7.27.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.3.tgz#c0257bedf33aad6aad1f406d35c44758321eb3ec" integrity sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw== @@ -1002,7 +1002,7 @@ builtin-modules@^4.0.0: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-4.0.0.tgz#348db54ec0e6b197494423d26845f1674025ee46" integrity sha512-p1n8zyCkt1BVrKNFymOHjcDSAl7oq/gUvfgULv2EblgpPVQlQr9yHnWjg9IJ2MhfwPqiYqMMrr01OY7yQoK2yA== -busboy@^1.0.0: +busboy@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== @@ -1254,14 +1254,14 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concat-stream@^1.5.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== +concat-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" + integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== dependencies: buffer-from "^1.0.0" inherits "^2.0.3" - readable-stream "^2.2.2" + readable-stream "^3.0.2" typedarray "^0.0.6" content-disposition@0.5.4: @@ -3137,7 +3137,7 @@ minipass@^3.1.5, minipass@^3.1.6, minipass@^3.3.4: dependencies: yallist "^4.0.0" -mkdirp@^0.5.0, mkdirp@^0.5.4: +mkdirp@^0.5.0, mkdirp@^0.5.6: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -3195,18 +3195,18 @@ ms@2.1.3, ms@^2.1.1, ms@^2.1.2, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/multer/-/multer-2.0.0.tgz#47076aa0f7c2c2fd273715e767c6962bf7f94326" - integrity sha512-bS8rPZurbAuHGAnApbM9d4h1wSoYqrOqkE+6a64KLMK9yWU7gJXBDDVklKQ3TPi9DRb85cRs6yXaC0+cjxRtRg== +multer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/multer/-/multer-2.0.1.tgz#3ed335ed2b96240e3df9e23780c91cfcf5d29202" + integrity sha512-Ug8bXeTIUlxurg8xLTEskKShvcKDZALo1THEX5E41pYCD2sCVub5/kIRIGqWNoqV6szyLyQKV6mD4QUrWE5GCQ== dependencies: append-field "^1.0.0" - busboy "^1.0.0" - concat-stream "^1.5.2" - mkdirp "^0.5.4" + busboy "^1.6.0" + concat-stream "^2.0.0" + mkdirp "^0.5.6" object-assign "^4.1.1" - type-is "^1.6.4" - xtend "^4.0.0" + type-is "^1.6.18" + xtend "^4.0.2" mutexify@^1.4.0: version "1.4.0" @@ -3557,7 +3557,7 @@ pathval@^1.1.1: resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== -picocolors@^1.0.0, picocolors@^1.1.1: +picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -3772,7 +3772,16 @@ read-pkg@^9.0.0: type-fest "^4.6.0" unicorn-magic "^0.1.0" -readable-stream@^2.2.2, readable-stream@~2.3.6: +readable-stream@^3.0.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -3936,7 +3945,7 @@ safe-array-concat@^1.1.3: has-symbols "^1.1.0" isarray "^2.0.5" -safe-buffer@5.2.1, safe-buffer@^5.1.0: +safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -4280,6 +4289,13 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -4528,7 +4544,7 @@ type-fest@^4.39.1, type-fest@^4.6.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== -type-is@^1.6.4, type-is@~1.6.18: +type-is@^1.6.18, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -4654,7 +4670,7 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -4802,7 +4818,7 @@ ws@^7, ws@^7.5.5: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -xtend@^4.0.0: +xtend@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==