diff --git a/.clomonitor.yml b/.clomonitor.yml new file mode 100644 index 00000000000..4bc35d87f7d --- /dev/null +++ b/.clomonitor.yml @@ -0,0 +1,4 @@ +# see https://github.com/cncf/clomonitor/blob/main/docs/checks.md#exemptions +exemptions: + - check: artifacthub_badge + reason: "Artifact Hub doesn't support .NET packages" diff --git a/.editorconfig b/.editorconfig index cdfa4e32468..e3f693c1d13 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,9 @@ indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true +[*.sh] +end_of_line = lf + [*.{cs,cshtml,htm,html,md,py,sln,xml}] indent_size = 4 @@ -156,6 +159,13 @@ dotnet_diagnostic.IDE0005.severity = warning # RS0041: Public members should not use oblivious types dotnet_diagnostic.RS0041.severity = suggestion +[*Tests.cs] +# CA1515: Disable making types internal for Tests classes. It is required by xunit +dotnet_diagnostic.CA1515.severity = none + +# CA2007: Disable Consider calling ConfigureAwait on the awaited task. It is not working with xunit +dotnet_diagnostic.CA2007.severity = none + [**/obj/**.cs] generated_code = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..50ca329f24b --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh eol=lf diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index d503e982d1f..1d9f2dd277a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -62,7 +62,7 @@ body: - type: textarea attributes: label: Steps to Reproduce - description: Provide a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). We will close the issue if the repro project you share with us is complex or we cannot reproduce the behavior you are reporting. We cannot investigate custom projects, so don't point us to such, please. + description: Provide a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). We will close the issue if the repro project you share with us is complex or we cannot reproduce the behavior you are reporting. We cannot investigate custom projects, so don't point us to such, please. validations: required: true @@ -84,3 +84,11 @@ body: attributes: label: Additional Context description: Any additional information you think may be relevant to this issue. + + - type: dropdown + attributes: + label: Tip + description: This element is static, used to render a helpful sub-heading for end-users and community members to help prioritize issues. Please leave as is. + options: + - "[React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with :+1: to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/)." + default: 0 diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index ab958731736..9ca53fe8e7e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -53,3 +53,11 @@ body: attributes: label: Additional context description: Any additional information you think may be relevant to this feature request. + + - type: dropdown + attributes: + label: Tip + description: This element is static, used to render a helpful sub-heading for end-users and community members to help prioritize issues. Please leave as is. + options: + - "[React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with :+1: to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/)." + default: 0 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 168e352f889..dda94ddd1f0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,20 +1,10 @@ +# This file is retained solely for automated tooling to see we do automated +# dependency updates as not all such scanners recognize the use of Renovate. version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: yearly labels: - "infra" - - package-ecosystem: "dotnet-sdk" - directory: "/" - schedule: - interval: "weekly" - day: "wednesday" - labels: - - "infra" - ignore: - - dependency-name: "*" - update-types: - - "version-update:semver-major" - - "version-update:semver-minor" diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 00000000000..996d775ec7f --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "additionalBranchPrefix": "{{manager}}/", + "automerge": false, + "commitBodyTable": true, + "commitMessageAction": "Bump", + "dependencyDashboard": false, + "extends": [ + "config:best-practices", + "customManagers:dockerfileVersions", + "customManagers:githubActionsVersions", + ":automergeRequireAllStatusChecks", + ":disableRateLimiting", + ":enableVulnerabilityAlerts", + ":gitSignOff", + ":ignoreUnstable" + ], + "ignorePresets": [ + ":ignoreModulesAndTests" + ], + "ignorePaths": [ + "**/node_modules/**", + "**/bower_components/**", + "**/vendor/**", + "**/__tests__/**", + "**/__fixtures__/**" + ], + "labels": ["dependencies", "infra"], + "packageRules": [ + { + "matchManagers": ["dockerfile"], + "addLabels": ["docker"] + }, + { + "matchManagers": ["github-actions"], + "addLabels": ["github_actions"] + }, + { + "matchManagers": ["nuget"], + "addLabels": [".NET"] + }, + { + "matchManagers": ["pypi"], + "addLabels": ["python"] + }, + { + "description": ["Skip pinned NuGet package versions"], + "matchManagers": ["nuget"], + "matchCurrentValue": "^\\[[^,]+,\\)$", + "enabled": false + }, + { + "extends": ["monorepo:dotnet"], + "description": ["Disable major version updates for .NET"], + "matchUpdateTypes": ["major"], + "enabled": false + }, + { + "matchDepNames": ["xunit"], + "description": ["Disable major version updates for xunit"], + "matchUpdateTypes": ["major"], + "enabled": false + } + ], + "schedule": ["* 8-17 * * 3"], + "timezone": "Etc/UTC", + "vulnerabilityAlerts": { + "addLabels": ["security"] + } +} diff --git a/.github/workflows/Component.BuildTest.yml b/.github/workflows/Component.BuildTest.yml index 4cffbbe989a..406be841884 100644 --- a/.github/workflows/Component.BuildTest.yml +++ b/.github/workflows/Component.BuildTest.yml @@ -20,7 +20,7 @@ on: required: false type: string os-list: - default: '[ "windows-latest", "ubuntu-22.04", "otel-linux-arm64" ]' + default: '[ "windows-latest", "windows-11-arm", "ubuntu-22.04", "ubuntu-22.04-arm" ]' required: false type: string tfm-list: @@ -28,6 +28,9 @@ on: required: false type: string +permissions: + contents: read + jobs: build-test: @@ -39,22 +42,31 @@ jobs: exclude: - os: ubuntu-22.04 version: net462 - - os: otel-linux-arm64 + - os: ubuntu-22.04-arm version: net462 - - os: otel-linux-arm64 + - os: ubuntu-22.04-arm + version: net8.0 + - os: windows-11-arm version: net8.0 runs-on: ${{ matrix.os }} + timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: # Note: By default GitHub only fetches 1 commit. MinVer needs to find # the version tag which is typically NOT on the first commit so we # retrieve them all. fetch-depth: 0 - - name: Setup dotnet - uses: actions/setup-dotnet@v4 + - name: Setup previous .NET runtimes + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 + with: + dotnet-version: | + 8.0.x + + - name: Setup .NET + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: dotnet restore ${{ inputs.project-name }} run: dotnet restore ${{ inputs.project-name }} ${{ inputs.project-build-commands }} @@ -63,7 +75,18 @@ jobs: run: dotnet build ${{ inputs.project-name }} --configuration Release --no-restore ${{ inputs.project-build-commands }} - name: dotnet test ${{ inputs.project-name }} - run: dotnet test ${{ inputs.project-name }} --collect:"Code Coverage" --results-directory:TestResults --framework ${{ matrix.version }} --configuration Release --no-restore --no-build --logger:"console;verbosity=detailed" -- RunConfiguration.DisableAppDomain=true + run: > + dotnet test ${{ inputs.project-name }} + --collect:"Code Coverage" + --results-directory:TestResults + --framework ${{ matrix.version }} + --configuration Release + --no-restore + --no-build + --logger:"console;verbosity=detailed" + --logger:"GitHubActions;report-warnings=false" + --logger:"junit;LogFilePath=TestResults/junit.xml" + -- RunConfiguration.DisableAppDomain=true - name: Install coverage tool run: dotnet tool install -g dotnet-coverage @@ -72,15 +95,24 @@ jobs: run: dotnet-coverage merge -f cobertura -o ./TestResults/Cobertura.xml ./TestResults/**/*.coverage - name: Upload code coverage ${{ inputs.code-cov-prefix }}-${{ inputs.code-cov-name }} - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 continue-on-error: true # Note: Don't fail for upload failures env: OS: ${{ matrix.os }} TFM: ${{ matrix.version }} - token: ${{ secrets.CODECOV_TOKEN }} with: - file: TestResults/Cobertura.xml + files: TestResults/Cobertura.xml env_vars: OS,TFM flags: ${{ inputs.code-cov-prefix }}-${{ inputs.code-cov-name }} name: Code Coverage for ${{ inputs.code-cov-prefix }}-${{ inputs.code-cov-name }} on [${{ matrix.os }}.${{ matrix.version }}] codecov_yml_path: .github/codecov.yml + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload test results ${{ inputs.code-cov-prefix }}-${{ inputs.code-cov-name }} + if: ${{ !cancelled() && hashFiles('./**/TestResults/junit.xml') != '' }} + uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1 + with: + env_vars: OS,TFM + flags: ${{ inputs.code-cov-prefix }}-${{ inputs.code-cov-name }} + name: Test results for ${{ inputs.code-cov-prefix }}-${{ inputs.code-cov-name }} on [${{ matrix.os }}.${{ matrix.version }}] + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/add-labels.yml b/.github/workflows/add-labels.yml index 48639374fb2..19d25be7e44 100644 --- a/.github/workflows/add-labels.yml +++ b/.github/workflows/add-labels.yml @@ -7,18 +7,19 @@ on: branches: [ 'main*' ] permissions: - issues: write - pull-requests: write + contents: read jobs: add-labels-on-issues: + permissions: + issues: write if: github.event_name == 'issues' && !github.event.issue.pull_request - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: check out code - uses: actions/checkout@v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Add labels for package found in bug issue descriptions shell: pwsh @@ -33,13 +34,15 @@ jobs: ISSUE_BODY: ${{ github.event.issue.body }} add-labels-on-pull-requests: + permissions: + pull-requests: write if: github.event_name == 'pull_request_target' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: check out code - uses: actions/checkout@v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: ${{ github.event.repository.default_branch }} # Note: Do not run on the PR branch we want to execute add-labels.psm1 from main on the base repo only because pull_request_target can see secrets diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml index 9ea74f72211..2daa116be62 100644 --- a/.github/workflows/automation.yml +++ b/.github/workflows/automation.yml @@ -5,30 +5,33 @@ on: outputs: enabled: value: ${{ jobs.resolve-automation.outputs.enabled == 'true' }} - token-secret-name: - value: ${{ jobs.resolve-automation.outputs.token-secret-name }} username: value: ${{ vars.AUTOMATION_USERNAME }} email: value: ${{ vars.AUTOMATION_EMAIL }} + application-name: + value: ${{ vars.AUTOMATION_APPLICATION_NAME }} + application-username: + value: ${{ vars.AUTOMATION_APPLICATION_USERNAME }} secrets: - OPENTELEMETRYBOT_GITHUB_TOKEN: + OTELBOT_DOTNET_PRIVATE_KEY: required: false +permissions: + contents: read + jobs: resolve-automation: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 outputs: enabled: ${{ steps.evaluate.outputs.enabled }} - token-secret-name: ${{ steps.evaluate.outputs.token-secret-name }} env: - OPENTELEMETRYBOT_GITHUB_TOKEN_EXISTS: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN != '' }} + OTELBOT_DOTNET_PRIVATE_KEY_EXISTS: ${{ secrets.OTELBOT_DOTNET_PRIVATE_KEY != '' }} steps: - id: evaluate run: | - echo "enabled=${{ env.OPENTELEMETRYBOT_GITHUB_TOKEN_EXISTS == 'true' }}" >> "$GITHUB_OUTPUT" - echo "token-secret-name=OPENTELEMETRYBOT_GITHUB_TOKEN" >> "$GITHUB_OUTPUT" + echo "enabled=${{ env.OTELBOT_DOTNET_PRIVATE_KEY_EXISTS == 'true' }}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5640ee079b5..3110eef0cc6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,17 +9,27 @@ on: pull_request: branches: [ 'main*' ] +permissions: + contents: read + jobs: lint-misspell-sanitycheck: uses: ./.github/workflows/sanitycheck.yml + code-ql: + uses: ./.github/workflows/codeql-analysis-steps.yml + permissions: + actions: read + contents: read + security-events: write + detect-changes: runs-on: windows-latest outputs: changes: ${{ steps.changes.outputs.changes }} steps: - - uses: actions/checkout@v4 - - uses: AurorNZ/paths-filter@v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: AurorNZ/paths-filter@3b1f3abc3371cca888d8eb03dfa70bc8a9867629 # v4.0.0 id: changes with: filters: | @@ -107,13 +117,13 @@ jobs: || contains(needs.detect-changes.outputs.changes, 'otlp') || contains(needs.detect-changes.outputs.changes, 'build') || contains(needs.detect-changes.outputs.changes, 'shared') - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: version: [ net8.0, net9.0 ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Run OTLP Exporter docker compose run: docker compose --file=test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/docker-compose.yml --file=build/docker-compose.${{ matrix.version }}.yml --project-directory=. up --exit-code-from=tests --build @@ -125,13 +135,13 @@ jobs: || contains(needs.detect-changes.outputs.changes, 'instrumentation') || contains(needs.detect-changes.outputs.changes, 'build') || contains(needs.detect-changes.outputs.changes, 'shared') - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: version: [ net8.0, net9.0 ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Run W3C Trace Context docker compose run: docker compose --file=test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/docker-compose.yml --file=build/docker-compose.${{ matrix.version }}.yml --project-directory=. up --exit-code-from=tests --build @@ -172,6 +182,7 @@ jobs: build-test: needs: [ detect-changes, + code-ql, lint-misspell-sanitycheck, lint-md, lint-dotnet-format, @@ -187,7 +198,17 @@ jobs: concurrency-tests ] if: always() && !cancelled() - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - - run: | - if ( ${{ contains(needs.*.result, 'failure') }} == true ); then echo 'build failed'; exit 1; else echo 'build complete'; fi + - name: Report CI status + shell: bash + env: + CI_SUCCESS: ${{ !contains(needs.*.result, 'failure') }} + run: | + if [ "${CI_SUCCESS}" == "true" ] + then + echo 'Build complete' + else + echo 'Build failed' + exit 1 + fi diff --git a/.github/workflows/codeql-analysis-steps.yml b/.github/workflows/codeql-analysis-steps.yml new file mode 100644 index 00000000000..47a6ba61535 --- /dev/null +++ b/.github/workflows/codeql-analysis-steps.yml @@ -0,0 +1,65 @@ +name: codeql-analysis-steps + +on: + workflow_call: + +permissions: {} + +jobs: + analyze: + permissions: + actions: read # for github/codeql-action/init to get workflow details + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/analyze to upload SARIF results + runs-on: windows-latest + + strategy: + fail-fast: false + matrix: + language: ['actions', 'csharp'] + + steps: + - name: Configure Pagefile + if: matrix.language == 'csharp' + uses: al-cheb/configure-pagefile-action@a3b6ebd6b634da88790d9c58d4b37a7f4a7b8708 # v1.4 + with: + minimum-size: 8GB + maximum-size: 32GB + disk-root: "D:" + + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + filter: 'tree:0' + persist-credentials: false + show-progress: false + + - name: Initialize CodeQL + uses: github/codeql-action/init@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # v3.30.4 + with: + build-mode: none + languages: ${{ matrix.language }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # v3.30.4 + with: + category: '/language:${{ matrix.language }}' + + results: + if: ${{ !cancelled() }} + needs: [ analyze ] + runs-on: ubuntu-latest + + steps: + - name: Report status + shell: bash + env: + SCAN_SUCCESS: ${{ !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }} + run: | + if [ "${SCAN_SUCCESS}" == "true" ] + then + echo 'CodeQL analysis successful' + else + echo 'CodeQL analysis failed' + exit 1 + fi diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3cdd6a57b6d..44fc56c2444 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,8 +1,3 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. name: "CodeQL" on: @@ -10,37 +5,12 @@ on: - cron: '0 0 * * *' # once in a day at 00:00 workflow_dispatch: -jobs: - analyze: - name: Analyze - runs-on: windows-latest - - strategy: - fail-fast: false - matrix: - language: ['csharp'] - - steps: - - name: configure Pagefile - uses: al-cheb/configure-pagefile-action@v1.4 - with: - minimum-size: 8GB - maximum-size: 32GB - disk-root: "D:" - - - name: Checkout repository - uses: actions/checkout@v4 +permissions: {} - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - - - name: Setup dotnet - uses: actions/setup-dotnet@v4 - - - name: dotnet pack - run: dotnet pack ./build/OpenTelemetry.proj --configuration Release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 +jobs: + code-ql: + uses: ./.github/workflows/codeql-analysis-steps.yml + permissions: + actions: read + contents: read + security-events: write diff --git a/.github/workflows/concurrency-tests.yml b/.github/workflows/concurrency-tests.yml index 907d7260049..ae1e64bfcf9 100644 --- a/.github/workflows/concurrency-tests.yml +++ b/.github/workflows/concurrency-tests.yml @@ -5,6 +5,9 @@ name: Concurrency Tests on: workflow_call: +permissions: + contents: read + jobs: run-concurrency-tests: @@ -17,10 +20,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: Run Coyote Tests shell: pwsh @@ -28,7 +31,7 @@ jobs: - name: Publish Artifacts if: always() && !cancelled() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: ${{ matrix.os }}-${{ matrix.project }}-${{ matrix.version }}-coyoteoutput path: '**/*_CoyoteOutput.*' diff --git a/.github/workflows/docfx.yml b/.github/workflows/docfx.yml index 76ab4f2e1ec..fcf07c097b3 100644 --- a/.github/workflows/docfx.yml +++ b/.github/workflows/docfx.yml @@ -5,16 +5,19 @@ name: Build docfx on: workflow_call: +permissions: + contents: read + jobs: run-docfx-build: runs-on: windows-latest steps: - name: check out code - uses: actions/checkout@v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: install docfx run: dotnet tool install -g docfx diff --git a/.github/workflows/dotnet-format.yml b/.github/workflows/dotnet-format.yml index 2ea4834570f..dc72da32565 100644 --- a/.github/workflows/dotnet-format.yml +++ b/.github/workflows/dotnet-format.yml @@ -5,16 +5,19 @@ name: Lint - dotnet format on: workflow_call: +permissions: + contents: read + jobs: run-dotnet-format-stable: runs-on: windows-latest steps: - name: check out code - uses: actions/checkout@v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: dotnet restore run: dotnet restore OpenTelemetry.sln @@ -29,10 +32,10 @@ jobs: steps: - name: check out code - uses: actions/checkout@v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: dotnet restore run: dotnet restore OpenTelemetry.sln diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml new file mode 100644 index 00000000000..f86b5a99322 --- /dev/null +++ b/.github/workflows/fossa.yml @@ -0,0 +1,20 @@ +name: FOSSA scanning + +on: + push: + branches: + - main + +permissions: + contents: read + +jobs: + fossa: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - uses: fossas/fossa-action@3ebcea1862c6ffbd5cf1b4d0bd6b3fe7bd6f2cac # v1.7.0 + with: + api-key: ${{secrets.FOSSA_API_KEY}} + team: OpenTelemetry diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yml index b013c52a57a..2ce6431d9cc 100644 --- a/.github/workflows/markdownlint.yml +++ b/.github/workflows/markdownlint.yml @@ -5,16 +5,19 @@ name: Lint - Markdown on: workflow_call: +permissions: + contents: read + jobs: run-markdownlint: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: check out code - uses: actions/checkout@v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: run markdownlint - uses: DavidAnson/markdownlint-cli2-action@v19.1.0 + uses: DavidAnson/markdownlint-cli2-action@992badcdf24e3b8eb7e87ff9287fe931bcb00c6e # v20.0.0 with: globs: | **/*.md diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml new file mode 100644 index 00000000000..3d950859b51 --- /dev/null +++ b/.github/workflows/ossf-scorecard.yml @@ -0,0 +1,47 @@ +name: OSSF Scorecard + +on: + push: + branches: + - main + schedule: + - cron: "24 5 * * 0" # once a week + workflow_dispatch: + +permissions: read-all + +jobs: + analysis: + runs-on: ubuntu-latest + permissions: + # Needed for Code scanning upload + security-events: write + # Needed for GitHub OIDC token if publish_results is true + id-token: write + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + + - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable + # uploads of run results in SARIF format to the repository Actions tab. + # https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts + - name: "Upload artifact" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard (optional). + # Commenting out will disable upload of results to your repo's Code Scanning dashboard + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # v3.30.4 + with: + sarif_file: results.sarif diff --git a/.github/workflows/package-validation.yml b/.github/workflows/package-validation.yml index d1f7658ee2d..3216646f45c 100644 --- a/.github/workflows/package-validation.yml +++ b/.github/workflows/package-validation.yml @@ -5,12 +5,15 @@ name: Package Validation on: workflow_call: +permissions: + contents: read + jobs: run-package-validation-stable: runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: # Note: By default GitHub only fetches 1 commit. MinVer needs to find # the version tag which is typically NOT on the first commit so we @@ -18,16 +21,23 @@ jobs: fetch-depth: 0 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: dotnet pack run: dotnet pack ./build/OpenTelemetry.proj --configuration Release /p:EnablePackageValidation=true /p:ExposeExperimentalFeatures=false /p:RunningDotNetPack=true + - name: Publish stable NuGet packages to Artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: packages-stable + path: ./artifacts/package/release + if-no-files-found: error + run-package-validation-experimental: runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: # Note: By default GitHub only fetches 1 commit. MinVer needs to find # the version tag which is typically NOT on the first commit so we @@ -35,7 +45,14 @@ jobs: fetch-depth: 0 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: dotnet pack run: dotnet pack ./build/OpenTelemetry.proj --configuration Release /p:EnablePackageValidation=true /p:ExposeExperimentalFeatures=true /p:RunningDotNetPack=true + + - name: Publish experimental NuGet packages to Artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: packages-experimental + path: ./artifacts/package/release + if-no-files-found: error diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml index bb67ead1e47..7a06a0c6580 100644 --- a/.github/workflows/post-release.yml +++ b/.github/workflows/post-release.yml @@ -16,107 +16,148 @@ on: types: - created +permissions: + contents: read + jobs: automation: uses: ./.github/workflows/automation.yml - secrets: inherit + secrets: + OTELBOT_DOTNET_PRIVATE_KEY: ${{ secrets.OTELBOT_DOTNET_PRIVATE_KEY }} push-packages-and-publish-release: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: automation + permissions: + id-token: write + if: | - github.event_name == 'issue_comment' - && github.event.issue.pull_request - && github.event.issue.locked == true - && github.event.comment.user.login != needs.automation.outputs.username - && contains(github.event.comment.body, '/PushPackages') - && startsWith(github.event.issue.title, '[release] Prepare release ') - && github.event.issue.pull_request.merged_at - && needs.automation.outputs.enabled - - env: - GH_TOKEN: ${{ secrets[needs.automation.outputs.token-secret-name] }} + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.issue.locked == true && + github.event.comment.user.login != needs.automation.outputs.application-username && + contains(github.event.comment.body, '/PushPackages') && + startsWith(github.event.issue.title, '[release] Prepare release ') && + github.event.issue.pull_request.merged_at && + needs.automation.outputs.enabled steps: - - name: check out code - uses: actions/checkout@v4 + - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + id: otelbot-token + with: + app-id: ${{ vars.OTELBOT_DOTNET_APP_ID }} + private-key: ${{ secrets.OTELBOT_DOTNET_PRIVATE_KEY }} + + - name: Check out code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: - token: ${{ secrets[needs.automation.outputs.token-secret-name] }} + token: ${{ steps.otelbot-token.outputs.token }} ref: ${{ github.event.repository.default_branch }} + - name: Setup .NET + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 + + - name: NuGet log in + uses: NuGet/login@d22cc5f58ff5b88bf9bd452535b4335137e24544 # v1.1.0 + id: nuget-login + with: + user: ${{ secrets.NUGET_USER }} + - name: Push packages and publish release shell: pwsh env: - NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }} + GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} + EXPECTED_PR_AUTHOR_USER_NAME: ${{ needs.automation.outputs.application-name }} + COMMENT_USER_NAME: ${{ github.event.comment.user.login }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + NUGET_TOKEN: ${{ steps.nuget-login.outputs.NUGET_API_KEY }} run: | Import-Module .\build\scripts\post-release.psm1 + $HasToken = -Not [string]::IsNullOrEmpty($env:NUGET_TOKEN) PushPackagesPublishReleaseUnlockAndPostNoticeOnPrepareReleasePullRequest ` - -gitRepository '${{ github.repository }}' ` - -pullRequestNumber '${{ github.event.issue.number }}' ` - -botUserName '${{ needs.automation.outputs.username }}' ` - -commentUserName '${{ github.event.comment.user.login }}' ` - -artifactDownloadPath '${{ github.workspace }}/artifacts' ` - -pushToNuget '${{ secrets.NUGET_TOKEN != '' }}' + -gitRepository ${env:GITHUB_REPOSITORY} ` + -pullRequestNumber ${env:ISSUE_NUMBER} ` + -expectedPrAuthorUserName ${env:EXPECTED_PR_AUTHOR_USER_NAME} ` + -commentUserName ${env:COMMENT_USER_NAME} ` + -artifactDownloadPath "${env:GITHUB_WORKSPACE}/artifacts" ` + -pushToNuget $HasToken post-release-published: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: - automation if: | - needs.automation.outputs.enabled - && (github.event_name == 'release' || github.event_name == 'workflow_dispatch') - - env: - GH_TOKEN: ${{ secrets[needs.automation.outputs.token-secret-name] }} + needs.automation.outputs.enabled && + (github.event_name == 'release' || github.event_name == 'workflow_dispatch') steps: - - uses: actions/checkout@v4 + - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + id: otelbot-token + with: + app-id: ${{ vars.OTELBOT_DOTNET_APP_ID }} + private-key: ${{ secrets.OTELBOT_DOTNET_PRIVATE_KEY }} + + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: # Note: By default GitHub only fetches 1 commit. We need all the tags # for this work. fetch-depth: 0 ref: ${{ github.event.repository.default_branch }} - token: ${{ secrets[needs.automation.outputs.token-secret-name] }} + token: ${{ steps.otelbot-token.outputs.token }} - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: Create GitHub Pull Request to update stable build version in props if: | (github.ref_type == 'tag' && startsWith(github.ref_name, 'core-') && !contains(github.ref_name, '-alpha') && !contains(github.ref_name, '-beta') && !contains(github.ref_name, '-rc')) || (inputs.tag && startsWith(inputs.tag, 'core-') && !contains(inputs.tag, '-alpha') && !contains(inputs.tag, '-beta') && !contains(inputs.tag, '-rc')) shell: pwsh + env: + GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} + BOT_USER_EMAIL: ${{ needs.automation.outputs.email }} + BOT_USER_NAME: ${{ needs.automation.outputs.username }} + TAG: ${{ inputs.tag || github.ref_name }} + TARGET_BRANCH: ${{ github.event.repository.default_branch }} run: | Import-Module .\build\scripts\post-release.psm1 CreateStableVersionUpdatePullRequest ` - -gitRepository '${{ github.repository }}' ` - -tag '${{ inputs.tag || github.ref_name }}' ` - -targetBranch '${{ github.event.repository.default_branch }}' ` - -gitUserName '${{ needs.automation.outputs.username }}' ` - -gitUserEmail '${{ needs.automation.outputs.email }}' + -gitRepository ${env:GITHUB_REPOSITORY} ` + -tag ${env:TAG} ` + -targetBranch ${env:TARGET_BRANCH} ` + -gitUserName ${env:BOT_USER_NAME} ` + -gitUserEmail ${env:BOT_USER_EMAIL} - name: Invoke core version update workflow in opentelemetry-dotnet-contrib repository if: vars.CONTRIB_REPO shell: pwsh + env: + GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} + CONTRIB_REPO: ${{ vars.CONTRIB_REPO }} + TAG: ${{ inputs.tag || github.ref_name }} run: | Import-Module .\build\scripts\post-release.psm1 InvokeCoreVersionUpdateWorkflowInRemoteRepository ` - -remoteGitRepository '${{ vars.CONTRIB_REPO }}' ` - -tag '${{ inputs.tag || github.ref_name }}' + -remoteGitRepository ${env:CONTRIB_REPO} ` + -tag ${env:TAG} - name: Post notice when release is published shell: pwsh + env: + GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} + EXPECTED_PR_AUTHOR_USER_NAME: ${{ needs.automation.outputs.application-name }} + TAG: ${{ inputs.tag || github.ref_name }} run: | Import-Module .\build\scripts\post-release.psm1 TryPostReleasePublishedNoticeOnPrepareReleasePullRequest ` - -gitRepository '${{ github.repository }}' ` - -botUserName '${{ needs.automation.outputs.username }}' ` - -tag '${{ inputs.tag || github.ref_name }}' + -gitRepository ${env:GITHUB_REPOSITORY} ` + -expectedPrAuthorUserName ${env:EXPECTED_PR_AUTHOR_USER_NAME} ` + -tag ${env:TAG} diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index fbccc656063..5d8eb56ae39 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -23,187 +23,238 @@ on: types: - created +permissions: + contents: read + jobs: automation: uses: ./.github/workflows/automation.yml - secrets: inherit + secrets: + OTELBOT_DOTNET_PRIVATE_KEY: ${{ secrets.OTELBOT_DOTNET_PRIVATE_KEY }} prepare-release-pr: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: automation if: github.event_name == 'workflow_dispatch' && needs.automation.outputs.enabled - env: - GH_TOKEN: ${{ secrets[needs.automation.outputs.token-secret-name] }} - steps: - - name: check out code - uses: actions/checkout@v4 + - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + id: otelbot-token + with: + app-id: ${{ vars.OTELBOT_DOTNET_APP_ID }} + private-key: ${{ secrets.OTELBOT_DOTNET_PRIVATE_KEY }} + + - name: Check out code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: - token: ${{ secrets[needs.automation.outputs.token-secret-name] }} + token: ${{ steps.otelbot-token.outputs.token }} - name: Create GitHub Pull Request to prepare release shell: pwsh + env: + GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} + BOT_USER_EMAIL: ${{ needs.automation.outputs.email }} + BOT_USER_NAME: ${{ needs.automation.outputs.username }} + COMMENT_USER_NAME: ${{ github.event.sender.login }} + TAG_PREFIX: ${{ inputs.tag-prefix }} + VERSION: ${{ inputs.version }} run: | Import-Module .\build\scripts\prepare-release.psm1 CreatePullRequestToUpdateChangelogsAndPublicApis ` - -gitRepository '${{ github.repository }}' ` - -minVerTagPrefix '${{ inputs.tag-prefix }}' ` - -version '${{ inputs.version }}' ` - -requestedByUserName '${{ github.event.sender.login }}' ` - -targetBranch '${{ github.ref_name }}' ` - -gitUserName '${{ needs.automation.outputs.username }}' ` - -gitUserEmail '${{ needs.automation.outputs.email }}' + -gitRepository ${env:GITHUB_REPOSITORY} ` + -minVerTagPrefix ${env:TAG_PREFIX} ` + -version ${env:VERSION} ` + -requestedByUserName ${env:COMMENT_USER_NAME} ` + -targetBranch ${env:GITHUB_REF_NAME} ` + -gitUserName ${env:BOT_USER_NAME} ` + -gitUserEmail ${env:BOT_USER_EMAIL} lock-pr-and-post-notice-to-create-release-tag: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: automation if: | - github.event_name == 'pull_request' - && github.event.action == 'closed' - && github.event.pull_request.user.login == needs.automation.outputs.username - && github.event.pull_request.merged == true - && startsWith(github.event.pull_request.title, '[release] Prepare release ') - && needs.automation.outputs.enabled - - env: - GH_TOKEN: ${{ secrets[needs.automation.outputs.token-secret-name] }} + github.event_name == 'pull_request' && + github.event.action == 'closed' && + github.event.pull_request.user.login == needs.automation.outputs.application-username && + github.event.pull_request.merged == true && + startsWith(github.event.pull_request.title, '[release] Prepare release ') && + needs.automation.outputs.enabled steps: - - name: check out code - uses: actions/checkout@v4 + - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + id: otelbot-token + with: + app-id: ${{ vars.OTELBOT_DOTNET_APP_ID }} + private-key: ${{ secrets.OTELBOT_DOTNET_PRIVATE_KEY }} + + - name: Check out code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: - token: ${{ secrets[needs.automation.outputs.token-secret-name] }} + token: ${{ steps.otelbot-token.outputs.token }} - name: Lock GitHub Pull Request to prepare release shell: pwsh + env: + GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} + EXPECTED_PR_AUTHOR_USER_NAME: ${{ needs.automation.outputs.application-name }} + ISSUE_NUMBER: ${{ github.event.pull_request.number }} run: | Import-Module .\build\scripts\prepare-release.psm1 LockPullRequestAndPostNoticeToCreateReleaseTag ` - -gitRepository '${{ github.repository }}' ` - -pullRequestNumber '${{ github.event.pull_request.number }}' ` - -botUserName '${{ needs.automation.outputs.username }}' + -gitRepository ${env:GITHUB_REPOSITORY} ` + -pullRequestNumber ${env:ISSUE_NUMBER} ` + -expectedPrAuthorUserName ${env:EXPECTED_PR_AUTHOR_USER_NAME} create-release-tag-pr-post-notice: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: automation if: | - github.event_name == 'issue_comment' - && github.event.issue.pull_request - && github.event.issue.locked == true - && github.event.comment.user.login != needs.automation.outputs.username - && contains(github.event.comment.body, '/CreateReleaseTag') - && startsWith(github.event.issue.title, '[release] Prepare release ') - && github.event.issue.pull_request.merged_at - && needs.automation.outputs.enabled - - env: - GH_TOKEN: ${{ secrets[needs.automation.outputs.token-secret-name] }} + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.issue.locked == true && + github.event.comment.user.login != needs.automation.outputs.application-username && + contains(github.event.comment.body, '/CreateReleaseTag') && + startsWith(github.event.issue.title, '[release] Prepare release ') && + github.event.issue.pull_request.merged_at && + needs.automation.outputs.enabled steps: - - name: check out code - uses: actions/checkout@v4 + - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + id: otelbot-token + with: + app-id: ${{ vars.OTELBOT_DOTNET_APP_ID }} + private-key: ${{ secrets.OTELBOT_DOTNET_PRIVATE_KEY }} + + - name: Check out code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: # Note: By default GitHub only fetches 1 commit which fails the git tag operation below fetch-depth: 0 - token: ${{ secrets[needs.automation.outputs.token-secret-name] }} + token: ${{ steps.otelbot-token.outputs.token }} - name: Create release tag id: create-tag shell: pwsh + env: + GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} + BOT_USER_EMAIL: ${{ needs.automation.outputs.email }} + BOT_USER_NAME: ${{ needs.automation.outputs.username }} + EXPECTED_PR_AUTHOR_USER_NAME: ${{ needs.automation.outputs.application-name }} + ISSUE_NUMBER: ${{ github.event.issue.number }} run: | Import-Module .\build\scripts\prepare-release.psm1 CreateReleaseTagAndPostNoticeOnPullRequest ` - -gitRepository '${{ github.repository }}' ` - -pullRequestNumber '${{ github.event.issue.number }}' ` - -botUserName '${{ needs.automation.outputs.username }}' ` - -gitUserName '${{ needs.automation.outputs.username }}' ` - -gitUserEmail '${{ needs.automation.outputs.email }}' + -gitRepository ${env:GITHUB_REPOSITORY} ` + -pullRequestNumber ${env:ISSUE_NUMBER} ` + -expectedPrAuthorUserName ${env:EXPECTED_PR_AUTHOR_USER_NAME} ` + -gitUserName ${env:BOT_USER_NAME} ` + -gitUserEmail ${env:BOT_USER_EMAIL} update-changelog-release-dates-on-prepare-pr-post-notice: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: automation if: | - github.event_name == 'issue_comment' - && github.event.issue.pull_request - && github.event.issue.state == 'open' - && github.event.comment.user.login != needs.automation.outputs.username - && contains(github.event.comment.body, '/UpdateReleaseDates') - && startsWith(github.event.issue.title, '[release] Prepare release ') - && github.event.issue.pull_request.merged_at == null - && needs.automation.outputs.enabled - - env: - GH_TOKEN: ${{ secrets[needs.automation.outputs.token-secret-name] }} + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.issue.state == 'open' && + github.event.comment.user.login != needs.automation.outputs.application-username && + contains(github.event.comment.body, '/UpdateReleaseDates') && + startsWith(github.event.issue.title, '[release] Prepare release ') && + github.event.issue.pull_request.merged_at == null && + needs.automation.outputs.enabled steps: - - name: check out code - uses: actions/checkout@v4 + - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + id: otelbot-token + with: + app-id: ${{ vars.OTELBOT_DOTNET_APP_ID }} + private-key: ${{ secrets.OTELBOT_DOTNET_PRIVATE_KEY }} + + - name: Check out code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: # Note: By default GitHub only fetches 1 commit which fails the git tag operation below fetch-depth: 0 - token: ${{ secrets[needs.automation.outputs.token-secret-name] }} + token: ${{ steps.otelbot-token.outputs.token }} - name: Update release date shell: pwsh + env: + GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} + BOT_USER_EMAIL: ${{ needs.automation.outputs.email }} + BOT_USER_NAME: ${{ needs.automation.outputs.username }} + EXPECTED_PR_AUTHOR_USER_NAME: ${{ needs.automation.outputs.application-name }} + COMMENT_USER_NAME: ${{ github.event.comment.user.login }} + ISSUE_NUMBER: ${{ github.event.issue.number }} run: | Import-Module .\build\scripts\prepare-release.psm1 UpdateChangelogReleaseDatesAndPostNoticeOnPullRequest ` - -gitRepository '${{ github.repository }}' ` - -pullRequestNumber '${{ github.event.issue.number }}' ` - -botUserName '${{ needs.automation.outputs.username }}' ` - -commentUserName '${{ github.event.comment.user.login }}' ` - -gitUserName '${{ needs.automation.outputs.username }}' ` - -gitUserEmail '${{ needs.automation.outputs.email }}' + -gitRepository ${env:GITHUB_REPOSITORY} ` + -pullRequestNumber ${env:ISSUE_NUMBER} ` + -expectedPrAuthorUserName ${env:EXPECTED_PR_AUTHOR_USER_NAME} ` + -commentUserName ${env:COMMENT_USER_NAME} ` + -gitUserName ${env:BOT_USER_NAME} ` + -gitUserEmail ${env:BOT_USER_EMAIL} update-releasenotes-on-prepare-pr-post-notice: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: automation if: | - github.event_name == 'issue_comment' - && github.event.issue.pull_request - && github.event.issue.state == 'open' - && github.event.comment.user.login != needs.automation.outputs.username - && contains(github.event.comment.body, '/UpdateReleaseNotes') - && startsWith(github.event.issue.title, '[release] Prepare release ') - && github.event.issue.pull_request.merged_at == null - && needs.automation.outputs.enabled - - env: - GH_TOKEN: ${{ secrets[needs.automation.outputs.token-secret-name] }} + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.issue.state == 'open' && + github.event.comment.user.login != needs.automation.outputs.application-username && + contains(github.event.comment.body, '/UpdateReleaseNotes') && + startsWith(github.event.issue.title, '[release] Prepare release ') && + github.event.issue.pull_request.merged_at == null && + needs.automation.outputs.enabled steps: - - name: check out code - uses: actions/checkout@v4 + - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + id: otelbot-token + with: + app-id: ${{ vars.OTELBOT_DOTNET_APP_ID }} + private-key: ${{ secrets.OTELBOT_DOTNET_PRIVATE_KEY }} + + - name: Check out code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: # Note: By default GitHub only fetches 1 commit which fails the git tag operation below fetch-depth: 0 - token: ${{ secrets[needs.automation.outputs.token-secret-name] }} + token: ${{ steps.otelbot-token.outputs.token }} - name: Update release notes + env: + GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} + BOT_USER_EMAIL: ${{ needs.automation.outputs.email }} + BOT_USER_NAME: ${{ needs.automation.outputs.username }} + EXPECTED_PR_AUTHOR_USER_NAME: ${{ needs.automation.outputs.application-name }} + COMMENT_BODY: ${{ github.event.comment.body }} + COMMENT_USER_NAME: ${{ github.event.comment.user.login }} + ISSUE_NUMBER: ${{ github.event.issue.number }} shell: pwsh run: | Import-Module .\build\scripts\prepare-release.psm1 UpdateReleaseNotesAndPostNoticeOnPullRequest ` - -gitRepository '${{ github.repository }}' ` - -pullRequestNumber '${{ github.event.issue.number }}' ` - -botUserName '${{ needs.automation.outputs.username }}' ` - -commentUserName '${{ github.event.comment.user.login }}' ` - -commentBody '${{ github.event.comment.body }}' ` - -gitUserName '${{ needs.automation.outputs.username }}' ` - -gitUserEmail '${{ needs.automation.outputs.email }}' + -gitRepository ${env:GITHUB_REPOSITORY} ` + -pullRequestNumber ${env:ISSUE_NUMBER} ` + -expectedPrAuthorUserName ${env:EXPECTED_PR_AUTHOR_USER_NAME} ` + -commentUserName ${env:COMMENT_USER_NAME} ` + -commentBody $Env:COMMENT_BODY ` + -gitUserName ${env:BOT_USER_NAME} ` + -gitUserEmail ${env:BOT_USER_EMAIL} diff --git a/.github/workflows/publish-packages-1.0.yml b/.github/workflows/publish-packages-1.0.yml index 1d4d699381b..86661100080 100644 --- a/.github/workflows/publish-packages-1.0.yml +++ b/.github/workflows/publish-packages-1.0.yml @@ -16,10 +16,14 @@ on: schedule: - cron: '0 0 * * *' # once in a day at 00:00 +permissions: + contents: read + jobs: automation: uses: ./.github/workflows/automation.yml - secrets: inherit + secrets: + OTELBOT_DOTNET_PRIVATE_KEY: ${{ secrets.OTELBOT_DOTNET_PRIVATE_KEY }} build-pack-publish: runs-on: windows-latest @@ -35,7 +39,7 @@ jobs: artifact-id: ${{ steps.upload-artifacts.outputs.artifact-id }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: # Note: By default GitHub only fetches 1 commit. MinVer needs to find # the version tag which is typically NOT on the first commit so we @@ -43,18 +47,19 @@ jobs: fetch-depth: 0 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: Install Cosign - uses: sigstore/cosign-installer@v3 + uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 with: - cosign-release: v2.4.0 + cosign-release: v2.5.3 - name: dotnet restore run: dotnet restore ./build/OpenTelemetry.proj -p:RunningDotNetPack=true - name: dotnet build - run: dotnet build ./build/OpenTelemetry.proj --configuration Release --no-restore -p:Deterministic=true -p:BuildNumber=${{ github.run_number }} -p:RunningDotNetPack=true + shell: pwsh + run: dotnet build ./build/OpenTelemetry.proj --configuration Release --no-restore -p:Deterministic=true -p:"BuildNumber=${env:GITHUB_RUN_NUMBER}" -p:RunningDotNetPack=true - name: Sign DLLs with Cosign Keyless shell: pwsh @@ -64,7 +69,7 @@ jobs: foreach ($projectFile in $projectFiles) { $projectName = [System.IO.Path]::GetFileNameWithoutExtension($projectFile) - Get-ChildItem -Path src/$projectName/bin/Release/*/$projectName.dll -File | ForEach-Object { + Get-ChildItem -Path artifacts/bin/$projectName/release_*/$projectName.dll -File | ForEach-Object { $fileFullPath = $_.FullName Write-Host "Signing $fileFullPath" @@ -73,25 +78,31 @@ jobs: } - name: dotnet pack - run: dotnet pack ./build/OpenTelemetry.proj --configuration Release --no-restore --no-build -p:PackTag=${{ github.ref_type == 'tag' && github.ref_name || '' }} + shell: pwsh + env: + PACK_TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }} + run: dotnet pack ./build/OpenTelemetry.proj --configuration Release --no-restore --no-build -p:"PackTag=${env:PACK_TAG}" - name: Publish Artifacts id: upload-artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: ${{ github.ref_name }}-packages - path: 'src/**/*.*nupkg' + path: ./artifacts/package/release + if-no-files-found: error - name: Publish MyGet + working-directory: ./artifacts/package/release env: MYGET_TOKEN_EXISTS: ${{ secrets.MYGET_TOKEN != '' }} + API_KEY: ${{ secrets.MYGET_TOKEN }} + SOURCE: https://www.myget.org/F/opentelemetry/api/v2/package if: env.MYGET_TOKEN_EXISTS == 'true' # Skip MyGet publish if run on a fork without the secret - run: | - nuget setApiKey ${{ secrets.MYGET_TOKEN }} -Source https://www.myget.org/F/opentelemetry/api/v2/package - nuget push src/**/*.nupkg -Source https://www.myget.org/F/opentelemetry/api/v2/package + shell: pwsh + run: dotnet nuget push *.nupkg --api-key ${env:API_KEY} --skip-duplicate --source ${env:SOURCE} post-build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: - automation @@ -99,45 +110,55 @@ jobs: if: needs.automation.outputs.enabled && github.event_name == 'push' - env: - GH_TOKEN: ${{ secrets[needs.automation.outputs.token-secret-name] }} - steps: - - name: check out code - uses: actions/checkout@v4 + - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + id: otelbot-token with: - token: ${{ secrets[needs.automation.outputs.token-secret-name] }} + app-id: ${{ vars.OTELBOT_DOTNET_APP_ID }} + private-key: ${{ secrets.OTELBOT_DOTNET_PRIVATE_KEY }} + + - name: Check out code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ steps.otelbot-token.outputs.token }} - name: Download Artifacts + env: + ARTIFACT_ID: ${{ needs.build-pack-publish.outputs.artifact-id }} + ARTIFACT_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | curl \ -H "Accept: application/vnd.github+json" \ - -H "Authorization: token ${{ github.token }}" \ + -H "Authorization: token ${ARTIFACT_TOKEN}" \ -L \ - -o '${{ github.workspace }}/artifacts/${{ github.ref_name }}-packages.zip' \ + -o "${GITHUB_WORKSPACE}/artifacts/${GITHUB_REF_NAME}-packages.zip" \ --create-dirs \ - "https://api.github.com/repos/${{ github.repository }}/actions/artifacts/${{ needs.build-pack-publish.outputs.artifact-id }}/zip" + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/actions/artifacts/${ARTIFACT_ID}/zip" - name: Create GitHub Release draft shell: pwsh + env: + GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} run: | Import-Module .\build\scripts\post-release.psm1 CreateDraftRelease ` - -gitRepository '${{ github.repository }}' ` - -tag '${{ github.ref_name }}' ` - -releaseFiles '${{ github.workspace }}/artifacts/${{ github.ref_name }}-packages.zip#Packages' + -gitRepository ${env:GITHUB_REPOSITORY} ` + -tag ${env:GITHUB_REF_NAME} ` + -releaseFiles "${env:GITHUB_WORKSPACE}/artifacts/${env:GITHUB_REF_NAME}-packages.zip" - name: Post notice when packages are ready shell: pwsh + env: + GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} + EXPECTED_PR_AUTHOR_USER_NAME: ${{ needs.automation.outputs.application-name }} + PACKAGES_URL: ${{ needs.build-pack-publish.outputs.artifact-url }} run: | Import-Module .\build\scripts\post-release.psm1 TryPostPackagesReadyNoticeOnPrepareReleasePullRequest ` - -gitRepository '${{ github.repository }}' ` - -tag '${{ github.ref_name }}' ` - -tagSha '${{ github.sha }}' ` - -packagesUrl '${{ needs.build-pack-publish.outputs.artifact-url }}' ` - -botUserName '${{ needs.automation.outputs.username }}' - - + -gitRepository ${env:GITHUB_REPOSITORY} ` + -tag ${env:GITHUB_REF_NAME} ` + -tagSha ${env:GITHUB_SHA} ` + -packagesUrl ${env:PACKAGES_URL} ` + -expectedPrAuthorUserName ${env:EXPECTED_PR_AUTHOR_USER_NAME} diff --git a/.github/workflows/sanitycheck.yml b/.github/workflows/sanitycheck.yml index 335d389a9c7..54f89668af6 100644 --- a/.github/workflows/sanitycheck.yml +++ b/.github/workflows/sanitycheck.yml @@ -5,13 +5,16 @@ name: Lint - Spelling & Encoding on: workflow_call: +permissions: + contents: read + jobs: run-misspell: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: check out code - uses: actions/checkout@v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: install misspell run: | @@ -22,11 +25,11 @@ jobs: run: ./bin/misspell -error . run-sanitycheck: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: check out code - uses: actions/checkout@v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: detect non-ASCII encoding and trailing space run: python3 ./build/scripts/sanitycheck.py diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index da30232dc9c..16b246d66aa 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -6,11 +6,17 @@ on: schedule: - cron: "12 3 * * *" # arbitrary time not to DDOS GitHub +permissions: + contents: read + jobs: stale: - runs-on: ubuntu-22.04 + permissions: + issues: write # for actions/stale to close stale issues + pull-requests: write # for actions/stale to close stale PRs + runs-on: ubuntu-24.04 steps: - - uses: actions/stale@v9 + - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 with: stale-issue-message: 'This issue was marked stale due to lack of activity and will be closed in 7 days. Commenting will instruct the bot to automatically remove the label. This bot runs once per day.' close-issue-message: 'Closed as inactive. Feel free to reopen if this issue is still a concern.' @@ -22,4 +28,5 @@ jobs: days-before-pr-close: 7 days-before-issue-close: 7 exempt-all-issue-milestones: true - exempt-issue-labels: needs-triage + exempt-issue-labels: 'keep-open,needs-triage' + exempt-pr-labels: 'keep-open' diff --git a/.github/workflows/survey-on-merged-pr.yml b/.github/workflows/survey-on-merged-pr.yml new file mode 100644 index 00000000000..3b3c80be6c5 --- /dev/null +++ b/.github/workflows/survey-on-merged-pr.yml @@ -0,0 +1,29 @@ +name: Survey on Merged PR by Non-Member + +on: + pull_request_target: + branches: [main] + types: [closed] + +permissions: {} + +jobs: + comment-on-pr: + name: Add survey to PR if author is not a member + permissions: + pull-requests: write + runs-on: ubuntu-latest + # Only run for merged pull requests by non-members users (i.e. not bots) + if: | + github.event.pull_request.merged && + github.event.pull_request.user.type == 'User' && + contains(fromJson('["CONTRIBUTOR", "FIRST_TIME_CONTRIBUTOR", "FIRST_TIMER"]'), github.event.pull_request.author_association) + steps: + - name: Add comment + run: | + gh pr comment "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --body "Thank you for your contribution @${USERNAME}! :tada: We would like to hear from you about your experience contributing to OpenTelemetry by taking a few minutes to fill out this [survey](${SURVEY_URL})." + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + SURVEY_URL: https://docs.google.com/forms/d/e/1FAIpQLSf2FfCsW-DimeWzdQgfl0KDzT2UEAqu69_f7F2BVPSxVae1cQ/viewform?entry.1540511742=${{ github.repository }} + USERNAME: ${{ github.event.pull_request.user.login }} diff --git a/.github/workflows/verifyaotcompat.yml b/.github/workflows/verifyaotcompat.yml index b85eaf5ca2d..affabafaf72 100644 --- a/.github/workflows/verifyaotcompat.yml +++ b/.github/workflows/verifyaotcompat.yml @@ -5,6 +5,9 @@ name: Publish & Verify AOT Compatibility on: workflow_call: +permissions: + contents: read + jobs: run-verify-aot-compat: @@ -16,10 +19,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: publish AOT testApp, assert static analysis warning count, and run the app shell: pwsh diff --git a/.gitignore b/.gitignore index e06229e460f..851ee867e69 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ x64/ x86/ bld/ +[Aa]rtifacts/ [Bb]in/ [Oo]bj/ [Ll]og/ @@ -351,3 +352,6 @@ tempo-data/ # Coyote Rewrite Files rewrite.coyote.json + +# Test results +TestResults/ diff --git a/.markdownlint.yaml b/.markdownlint.yaml index c388b69fd5f..6efb6aeb05c 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -10,3 +10,6 @@ MD013: MD033: # Allowed elements allowed_elements: [ 'details', 'summary' ] + +# MD059/link-text-should-be-descriptive : Inline HTML : https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md059.md +MD059: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 02f8d2dd573..9ca8868d46a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,10 +14,33 @@ Anyone may contribute but there are benefits of being a member of our community. See the [community membership document](https://github.com/open-telemetry/community/blob/main/community-membership.md) on how to become a -[**Member**](https://github.com/open-telemetry/community/blob/main/community-membership.md#member), -[**Approver**](https://github.com/open-telemetry/community/blob/main/community-membership.md#approver) +[**Member**](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#member), +[**Triager**](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#triager), +[**Approver**](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#approver), and -[**Maintainer**](https://github.com/open-telemetry/community/blob/main/community-membership.md#maintainer). +[**Maintainer**](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#maintainer). + +## Give feedback + +We are always looking for your feedback. + +You can do this by [submitting a GitHub issue](https://github.com/open-telemetry/opentelemetry-dotnet/issues/new). + +You may also prefer writing on [#otel-dotnet Slack channel](https://cloud-native.slack.com/archives/C01N3BC2W7Q). + +### Report a bug + +Reporting bugs is an important contribution. Please make sure to include: + +* Expected and actual behavior; +* OpenTelemetry, OS, and .NET versions you are using; +* Steps to reproduce; +* [Minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). + +### Request a feature + +If you would like to work on something that is not listed as an issue +(e.g. a new feature or enhancement) please create an issue and describe your proposal. ## Find a buddy and get started quickly @@ -300,9 +323,6 @@ types](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-type * Pass [static analysis](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/overview). - New projects MUST enable static analysis by specifying - `latest-all` in the project file (`.csproj`). - > [!NOTE] > There are other project-level features enabled automatically via [Common.props](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/build/Common.props) diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000000..e157295e687 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,6 @@ + + + $([System.IO.Path]::Combine('$(MSBuildThisFileDirectory)', 'artifacts')) + true + + diff --git a/Directory.Packages.props b/Directory.Packages.props index 70564887f83..0e3f4ce78a8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,18 +2,7 @@ true - 1.11.0 - - - 9.0.0 - - - 8.0.0 - 8.0.5 + 1.12.0 - - - - - - - + + + + - - - - - - - + @@ -85,9 +62,10 @@ + - - + + - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - + + - + - + diff --git a/NuGet.config b/NuGet.config index ca17f1b8088..ec945cd0620 100644 --- a/NuGet.config +++ b/NuGet.config @@ -3,19 +3,14 @@ - - - - - diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index a072b792a9b..0384bcce8aa 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -254,9 +254,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A49299 src\Shared\ExceptionExtensions.cs = src\Shared\ExceptionExtensions.cs src\Shared\Guard.cs = src\Shared\Guard.cs src\Shared\MathHelper.cs = src\Shared\MathHelper.cs - src\Shared\PeerServiceResolver.cs = src\Shared\PeerServiceResolver.cs src\Shared\PeriodicExportingMetricReaderHelper.cs = src\Shared\PeriodicExportingMetricReaderHelper.cs - src\Shared\PooledList.cs = src\Shared\PooledList.cs src\Shared\ResourceSemanticConventions.cs = src\Shared\ResourceSemanticConventions.cs src\Shared\SemanticConventions.cs = src\Shared\SemanticConventions.cs src\Shared\SpanAttributeConstants.cs = src\Shared\SpanAttributeConstants.cs @@ -280,6 +278,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Options", "Options", "{4949 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shims", "Shims", "{A0CB9A10-F22D-4E66-A449-74B3D0361A9C}" ProjectSection(SolutionItems) = preProject + src\Shared\Shims\ExperimentalAttribute.cs = src\Shared\Shims\ExperimentalAttribute.cs src\Shared\Shims\IsExternalInit.cs = src\Shared\Shims\IsExternalInit.cs src\Shared\Shims\Lock.cs = src\Shared\Shims\Lock.cs src\Shared\Shims\NullableAttributes.cs = src\Shared\Shims\NullableAttributes.cs diff --git a/README.md b/README.md index 7da40f69cd9..303f2ce3f79 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ [![NuGet](https://img.shields.io/nuget/dt/OpenTelemetry.svg)](https://www.nuget.org/profiles/OpenTelemetry) [![Build](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/ci.yml) +[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/open-telemetry/opentelemetry-dotnet/badge)](https://scorecard.dev/viewer/?uri=github.com/open-telemetry/opentelemetry-dotnet) +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/10017/badge)](https://www.bestpractices.dev/projects/10017) +[![FOSSA License Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Fopen-telemetry%2Fopentelemetry-dotnet.svg?type=shield&issueType=license)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fopen-telemetry%2Fopentelemetry-dotnet?ref=badge_shield&issueType=license) +[![FOSSA Security Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Fopen-telemetry%2Fopentelemetry-dotnet.svg?type=shield&issueType=security)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fopen-telemetry%2Fopentelemetry-dotnet?ref=badge_shield&issueType=security) + The .NET [OpenTelemetry](https://opentelemetry.io/) implementation.
@@ -231,26 +236,38 @@ regardless of your experience level. Whether you're a seasoned OpenTelemetry developer, just starting your journey, or simply curious about the work we do, you're more than welcome to participate! -[Maintainers](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#maintainer) -([@open-telemetry/dotnet-maintainers](https://github.com/orgs/open-telemetry/teams/dotnet-maintainers)): +### Maintainers * [Alan West](https://github.com/alanwest), New Relic +* [Piotr Kiełkowicz](https://github.com/Kielek), Splunk +* [Rajkumar Rangaraj](https://github.com/rajkumar-rangaraj), Microsoft + +For more information about the maintainer role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#maintainer). + +### Approvers + +* [Cijo Thomas](https://github.com/cijothomas), Microsoft +* [Martin Costello](https://github.com/martincostello), Grafana Labs * [Mikel Blanchard](https://github.com/CodeBlanch), Microsoft -[Emeritus Maintainers](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager): +For more information about the approver role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#approver). + +### Triagers + +* [Martin Thwaites](https://github.com/martinjt), Honeycomb +* [Timothy "Mothra" Lee](https://github.com/TimothyMothra) + +For more information about the triager role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#triager). + +### Emeritus Maintainers * [Mike Goldsmith](https://github.com/MikeGoldsmith) * [Sergey Kanzhelev](https://github.com/SergeyKanzhelev) * [Utkarsh Umesan Pillai](https://github.com/utpilla) -[Approvers](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#approver) -([@open-telemetry/dotnet-approvers](https://github.com/orgs/open-telemetry/teams/dotnet-approvers)): +For more information about the emeritus role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager). -* [Cijo Thomas](https://github.com/cijothomas), Microsoft -* [Piotr Kiełkowicz](https://github.com/Kielek), Splunk -* [Rajkumar Rangaraj](https://github.com/rajkumar-rangaraj), Microsoft - -[Emeritus Approvers](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager): +### Emeritus Approvers * [Bruno Garcia](https://github.com/bruno-garcia) * [Eddy Nakamura](https://github.com/eddynaka) @@ -260,16 +277,14 @@ you're more than welcome to participate! * [Robert Pająk](https://github.com/pellared) * [Vishwesh Bankwar](https://github.com/vishweshbankwar) -[Triagers](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#triager) -([@open-telemetry/dotnet-triagers](https://github.com/orgs/open-telemetry/teams/dotnet-triagers)): - -* [Martin Thwaites](https://github.com/martinjt), Honeycomb -* [Timothy "Mothra" Lee](https://github.com/TimothyMothra), Microsoft +For more information about the emeritus role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager). -[Emeritus Triagers](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager): +### Emeritus Triagers * [Victor Lu](https://github.com/victlu) +For more information about the emeritus role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager). + ### Thanks to all the people who have contributed [![contributors](https://contributors-img.web.app/image?repo=open-telemetry/opentelemetry-dotnet)](https://github.com/open-telemetry/opentelemetry-dotnet/graphs/contributors) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2d001c4ca8b..3a26d50aec8 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -4,13 +4,39 @@ This file contains highlights and announcements covering all components. For more details see `CHANGELOG.md` files maintained in the root source directory of each individual package. +## 1.13.0 + +Release details: [1.13.0](https://github.com/open-telemetry/opentelemetry-dotnet/releases/tag/core-1.13.0) + +* gRPC calls to export traces, logs, and metrics using `OtlpExportProtocol.Grpc` + now set the `TE=trailers` HTTP request header to improve interoperability. +* `EventName` is now exported by default as `EventName` instead of + `logrecord.event.name` when specified through `ILogger` or the experimental + log bridge API. + +## 1.12.0 + +Release details: [1.12.0](https://github.com/open-telemetry/opentelemetry-dotnet/releases/tag/core-1.12.0) + +* **Breaking Change**: `OpenTelemetry.Exporter.OpenTelemetryProtocol` now + defaults to using OTLP/HTTP instead of OTLP/gRPC when targeting .NET Framework + and .NET Standard. This change may cause telemetry export to fail unless + appropriate adjustments are made. Explicitly setting OTLP/gRPC may result in a + `NotSupportedException` unless further configuration is applied. See + [#6209](https://github.com/open-telemetry/opentelemetry-dotnet/issues/6209) for + full details and mitigation guidance. [#6229](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6229) + ## 1.11.1 +Release details: [1.11.1](https://github.com/open-telemetry/opentelemetry-dotnet/releases/tag/core-1.11.1) + * Fixed a bug preventing `OpenTelemetry.Exporter.OpenTelemetryProtocol` from exporting telemetry on .NET Framework. ## 1.11.0 +Release details: [1.11.0](https://github.com/open-telemetry/opentelemetry-dotnet/releases/tag/core-1.11.0) + * `OpenTelemetry.Exporter.OpenTelemetryProtocol` no longer depends on the `Google.Protobuf`, `Grpc`, or `Grpc.Net.Client` packages. Serialization and transmission of outgoing data is now performed manually to improve the overall @@ -18,6 +44,8 @@ directory of each individual package. ## 1.10.0 +Release details: [1.10.0](https://github.com/open-telemetry/opentelemetry-dotnet/releases/tag/core-1.10.0) + * Bumped the package versions of `System.Diagnostic.DiagnosticSource` and other Microsoft.Extensions.* packages to `9.0.0`. @@ -62,6 +90,8 @@ directory of each individual package. ## 1.9.0 +Release details: [1.9.0](https://github.com/open-telemetry/opentelemetry-dotnet/releases/tag/core-1.9.0) + * `Exemplars` are now part of the stable API! For details see: [customizing exemplars collection](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/docs/metrics/customizing-the-sdk#exemplars). @@ -72,6 +102,8 @@ directory of each individual package. ## 1.8.0 +Release details: [1.8.0](https://github.com/open-telemetry/opentelemetry-dotnet/releases/tag/core-1.8.0) + * `TracerProvider` sampler can now be configured via the `OTEL_TRACES_SAMPLER` & `OTEL_TRACES_SAMPLER_ARG` envvars. @@ -84,6 +116,8 @@ directory of each individual package. ## 1.7.0 +Release details: [1.7.0](https://github.com/open-telemetry/opentelemetry-dotnet/releases/tag/core-1.7.0) + * Bumped the package versions of System.Diagnostic.DiagnosticSource and other Microsoft.Extensions.* packages to `8.0.0`. diff --git a/build/Common.nonprod.props b/build/Common.nonprod.props index ce99895ccba..d5a959fd253 100644 --- a/build/Common.nonprod.props +++ b/build/Common.nonprod.props @@ -2,7 +2,7 @@ - $(NoWarn),1574,1591 + $(NoWarn),CS1574,CS1591 false $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'OpenTelemetry.sln'))\build\OpenTelemetry.test.ruleset @@ -21,6 +21,18 @@ + + + + + + + + + + + + diff --git a/build/Common.prod.props b/build/Common.prod.props index 832306e1405..5a13bf20617 100644 --- a/build/Common.prod.props +++ b/build/Common.prod.props @@ -18,7 +18,6 @@ https://opentelemetry.io OpenTelemetry Authors Copyright The OpenTelemetry Authors - $(Build_ArtifactStagingDirectory) true snupkg Apache-2.0 @@ -36,16 +35,8 @@ true - - - - - - @@ -185,10 +176,13 @@ + + + $(NoWarn);OTEL1000;OTEL1001;OTEL1002;OTEL1004 - - + latest-All + + + + 002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898 diff --git a/build/RELEASING.md b/build/RELEASING.md index 28fdc9a078e..bb430aaf9e8 100644 --- a/build/RELEASING.md +++ b/build/RELEASING.md @@ -80,11 +80,21 @@ Maintainers (admins) are needed to merge PRs and for the push to NuGet.** for the projects being released.
- 5. :stop_sign: Wait for the [Build, pack, and publish to + 6. :stop_sign: Wait for the [Build, pack, and publish to MyGet](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/publish-packages-1.0.yml) workflow to complete. When complete a trigger will automatically add a comment on the PR opened by [Prepare for a @@ -187,14 +197,14 @@ Maintainers (admins) are needed to merge PRs and for the push to NuGet.** draft Release and click `Publish release`. - 6. If a new stable version of the core packages was released, a PR should have + 7. If a new stable version of the core packages was released, a PR should have been automatically created by the [Complete release](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/post-release.yml) workflow to update the `OTelLatestStableVer` property in `Directory.Packages.props` to the just released stable version. Merge that PR once the build passes (this requires the packages be available on NuGet). - 7. The [Complete + 8. The [Complete release](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/post-release.yml) workflow should have invoked the [Core version update](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/actions/workflows/core-version-update.yml) @@ -203,7 +213,6 @@ Maintainers (admins) are needed to merge PRs and for the push to NuGet.** repository which opens a PR to update dependencies. Verify this PR was opened successfully. - 8. For stable releases, update [Release Notes](../RELEASENOTES.md) with any big - or interesting new features and then post an announcement in the [Slack - channel](https://cloud-native.slack.com/archives/C01N3BC2W7Q) with the same - information. + 9. For stable releases post an announcement in the [Slack + channel](https://cloud-native.slack.com/archives/C01N3BC2W7Q) announcing the + release and link to the release notes. diff --git a/build/docker-compose.net8.0.yml b/build/docker-compose.net8.0.yml index 22787bff66e..b93fc24655e 100644 --- a/build/docker-compose.net8.0.yml +++ b/build/docker-compose.net8.0.yml @@ -1,5 +1,3 @@ -version: '3.7' - services: tests: build: diff --git a/build/docker-compose.net9.0.yml b/build/docker-compose.net9.0.yml index 29663b3246c..94176258b24 100644 --- a/build/docker-compose.net9.0.yml +++ b/build/docker-compose.net9.0.yml @@ -1,5 +1,3 @@ -version: '3.7' - services: tests: build: diff --git a/build/scripts/post-release.psm1 b/build/scripts/post-release.psm1 index ae20f939e29..6edbe955474 100644 --- a/build/scripts/post-release.psm1 +++ b/build/scripts/post-release.psm1 @@ -125,7 +125,7 @@ function TryPostPackagesReadyNoticeOnPrepareReleasePullRequest { [Parameter(Mandatory=$true)][string]$tag, [Parameter(Mandatory=$true)][string]$tagSha, [Parameter(Mandatory=$true)][string]$packagesUrl, - [Parameter(Mandatory=$true)][string]$botUserName + [Parameter(Mandatory=$true)][string]$expectedPrAuthorUserName ) $prListResponse = gh pr list --search $tagSha --state merged --json number,author,title,comments | ConvertFrom-Json @@ -138,7 +138,7 @@ function TryPostPackagesReadyNoticeOnPrepareReleasePullRequest { foreach ($pr in $prListResponse) { - if ($pr.author.login -ne $botUserName -or $pr.title -ne "[release] Prepare release $tag") + if ($pr.author.login -ne $expectedPrAuthorUserName -or $pr.title -ne "[release] Prepare release $tag") { continue } @@ -146,7 +146,7 @@ function TryPostPackagesReadyNoticeOnPrepareReleasePullRequest { $foundComment = $false foreach ($comment in $pr.comments) { - if ($comment.author.login -eq $botUserName -and $comment.body.StartsWith("I just pushed the [$tag]")) + if ($comment.author.login -eq $expectedPrAuthorUserName -and $comment.body.StartsWith("I just pushed the [$tag]")) { $foundComment = $true break @@ -180,15 +180,15 @@ function PushPackagesPublishReleaseUnlockAndPostNoticeOnPrepareReleasePullReques param( [Parameter(Mandatory=$true)][string]$gitRepository, [Parameter(Mandatory=$true)][string]$pullRequestNumber, - [Parameter(Mandatory=$true)][string]$botUserName, + [Parameter(Mandatory=$true)][string]$expectedPrAuthorUserName, [Parameter(Mandatory=$true)][string]$commentUserName, [Parameter(Mandatory=$true)][string]$artifactDownloadPath, - [Parameter(Mandatory=$true)][string]$pushToNuget + [Parameter(Mandatory=$true)][bool]$pushToNuget ) $prViewResponse = gh pr view $pullRequestNumber --json author,title,comments | ConvertFrom-Json - if ($prViewResponse.author.login -ne $botUserName) + if ($prViewResponse.author.login -ne $expectedPrAuthorUserName) { throw 'PR author was unexpected' } @@ -213,7 +213,7 @@ function PushPackagesPublishReleaseUnlockAndPostNoticeOnPrepareReleasePullReques $packagesUrl = '' foreach ($comment in $prViewResponse.comments) { - if ($comment.author.login -eq $botUserName -and $comment.body.StartsWith("The packages for [$tag](https://github.com/$gitRepository/releases/tag/$tag) are now available:")) + if ($comment.author.login -eq $expectedPrAuthorUserName -and $comment.body.StartsWith("The packages for [$tag](https://github.com/$gitRepository/releases/tag/$tag) are now available:")) { $foundComment = $true break @@ -231,12 +231,12 @@ function PushPackagesPublishReleaseUnlockAndPostNoticeOnPrepareReleasePullReques Expand-Archive -LiteralPath "$artifactDownloadPath/$tag-packages.zip" -DestinationPath "$artifactDownloadPath\" - if ($pushToNuget -eq 'true') + if ($pushToNuget) { gh pr comment $pullRequestNumber ` --body "I am uploading the packages for ``$tag`` to NuGet and then I will publish the release." - nuget push "$artifactDownloadPath/**/*.nupkg" -Source https://api.nuget.org/v3/index.json -ApiKey "$env:NUGET_TOKEN" -SymbolApiKey "$env:NUGET_TOKEN" + dotnet nuget push "$artifactDownloadPath/**/*.nupkg" --source https://api.nuget.org/v3/index.json --api-key "$env:NUGET_TOKEN" --symbol-api-key "$env:NUGET_TOKEN" if ($LASTEXITCODE -gt 0) { @@ -276,7 +276,7 @@ function CreateStableVersionUpdatePullRequest { $version = $match.Groups[1].Value - $branch="release/post-stable-${tag}-update" + $branch="otelbot/post-stable-${tag}-update" if ([string]::IsNullOrEmpty($gitUserName) -eq $false) { @@ -539,7 +539,7 @@ Export-ModuleMember -Function InvokeCoreVersionUpdateWorkflowInRemoteRepository function TryPostReleasePublishedNoticeOnPrepareReleasePullRequest { param( [Parameter(Mandatory=$true)][string]$gitRepository, - [Parameter(Mandatory=$true)][string]$botUserName, + [Parameter(Mandatory=$true)][string]$expectedPrAuthorUserName, [Parameter(Mandatory=$true)][string]$tag ) @@ -559,7 +559,7 @@ function TryPostReleasePublishedNoticeOnPrepareReleasePullRequest { foreach ($pr in $prListResponse) { - if ($pr.author.login -ne $botUserName -or $pr.title -ne "[release] Prepare release $tag") + if ($pr.author.login -ne $expectedPrAuthorUserName -or $pr.title -ne "[release] Prepare release $tag") { continue } @@ -567,7 +567,7 @@ function TryPostReleasePublishedNoticeOnPrepareReleasePullRequest { $foundComment = $false foreach ($comment in $pr.comments) { - if ($comment.author.login -eq $botUserName -and $comment.body.StartsWith("The packages for [$tag](https://github.com/$gitRepository/releases/tag/$tag) are now available:")) + if ($comment.author.login -eq $expectedPrAuthorUserName -and $comment.body.StartsWith("The packages for [$tag](https://github.com/$gitRepository/releases/tag/$tag) are now available:")) { $foundComment = $true break diff --git a/build/scripts/prepare-release.psm1 b/build/scripts/prepare-release.psm1 index c1998e08149..4a6ee4678f4 100644 --- a/build/scripts/prepare-release.psm1 +++ b/build/scripts/prepare-release.psm1 @@ -17,7 +17,7 @@ function CreatePullRequestToUpdateChangelogsAndPublicApis { $isPrerelease = $version -match '-alpha' -or $version -match '-beta' -or $version -match '-rc' $tag="${minVerTagPrefix}${version}" - $branch="release/prepare-${tag}-release" + $branch="otelbot/prepare-${tag}-release" if ([string]::IsNullOrEmpty($gitUserName) -eq $false) { @@ -136,12 +136,12 @@ function LockPullRequestAndPostNoticeToCreateReleaseTag { param( [Parameter(Mandatory=$true)][string]$gitRepository, [Parameter(Mandatory=$true)][string]$pullRequestNumber, - [Parameter(Mandatory=$true)][string]$botUserName + [Parameter(Mandatory=$true)][string]$expectedPrAuthorUserName ) $prViewResponse = gh pr view $pullRequestNumber --json mergeCommit,author,title | ConvertFrom-Json - if ($prViewResponse.author.login -ne $botUserName) + if ($prViewResponse.author.login -ne $expectedPrAuthorUserName) { throw 'PR author was unexpected' } @@ -178,14 +178,14 @@ function CreateReleaseTagAndPostNoticeOnPullRequest { param( [Parameter(Mandatory=$true)][string]$gitRepository, [Parameter(Mandatory=$true)][string]$pullRequestNumber, - [Parameter(Mandatory=$true)][string]$botUserName, + [Parameter(Mandatory=$true)][string]$expectedPrAuthorUserName, [Parameter()][string]$gitUserName, [Parameter()][string]$gitUserEmail ) $prViewResponse = gh pr view $pullRequestNumber --json mergeCommit,author,title | ConvertFrom-Json - if ($prViewResponse.author.login -ne $botUserName) + if ($prViewResponse.author.login -ne $expectedPrAuthorUserName) { throw 'PR author was unexpected' } @@ -241,7 +241,7 @@ function UpdateChangelogReleaseDatesAndPostNoticeOnPullRequest { param( [Parameter(Mandatory=$true)][string]$gitRepository, [Parameter(Mandatory=$true)][string]$pullRequestNumber, - [Parameter(Mandatory=$true)][string]$botUserName, + [Parameter(Mandatory=$true)][string]$expectedPrAuthorUserName, [Parameter(Mandatory=$true)][string]$commentUserName, [Parameter()][string]$gitUserName, [Parameter()][string]$gitUserEmail @@ -249,7 +249,7 @@ function UpdateChangelogReleaseDatesAndPostNoticeOnPullRequest { $prViewResponse = gh pr view $pullRequestNumber --json headRefName,author,title | ConvertFrom-Json - if ($prViewResponse.author.login -ne $botUserName) + if ($prViewResponse.author.login -ne $expectedPrAuthorUserName) { throw 'PR author was unexpected' } @@ -344,7 +344,7 @@ function UpdateReleaseNotesAndPostNoticeOnPullRequest { param( [Parameter(Mandatory=$true)][string]$gitRepository, [Parameter(Mandatory=$true)][string]$pullRequestNumber, - [Parameter(Mandatory=$true)][string]$botUserName, + [Parameter(Mandatory=$true)][string]$expectedPrAuthorUserName, [Parameter(Mandatory=$true)][string]$commentUserName, [Parameter(Mandatory=$true)][string]$commentBody, [Parameter()][string]$gitUserName, @@ -353,7 +353,7 @@ function UpdateReleaseNotesAndPostNoticeOnPullRequest { $prViewResponse = gh pr view $pullRequestNumber --json headRefName,author,title | ConvertFrom-Json - if ($prViewResponse.author.login -ne $botUserName) + if ($prViewResponse.author.login -ne $expectedPrAuthorUserName) { throw 'PR author was unexpected' } @@ -420,6 +420,8 @@ function UpdateReleaseNotesAndPostNoticeOnPullRequest { @" ## $version +Release details: [$version](https://github.com/$gitRepository/releases/tag/$tagPrefix$version) + $content ## diff --git a/build/scripts/test-aot-compatibility.ps1 b/build/scripts/test-aot-compatibility.ps1 index 895055a1b48..99def7badd5 100644 --- a/build/scripts/test-aot-compatibility.ps1 +++ b/build/scripts/test-aot-compatibility.ps1 @@ -24,10 +24,9 @@ if ($LastExitCode -ne 0) Write-Host $publishOutput } -$runtime = $IsWindows ? "win-x64" : ($IsMacOS ? "macos-x64" : "linux-x64") $app = $IsWindows ? "./OpenTelemetry.AotCompatibility.TestApp.exe" : "./OpenTelemetry.AotCompatibility.TestApp" -Push-Location $rootDirectory/test/OpenTelemetry.AotCompatibility.TestApp/bin/Release/$targetNetFramework/$runtime +Push-Location $rootDirectory/artifacts/publish/OpenTelemetry.AotCompatibility.TestApp/release_$targetNetFramework Write-Host "Executing test App..." $app diff --git a/build/scripts/test-threadSafety.ps1 b/build/scripts/test-threadSafety.ps1 index 73cc1f27d27..6fbaba60e5c 100644 --- a/build/scripts/test-threadSafety.ps1 +++ b/build/scripts/test-threadSafety.ps1 @@ -1,22 +1,32 @@ param( - [Parameter()][string]$coyoteVersion="1.7.10", + [Parameter()][string]$coyoteVersion="1.7.11", [Parameter(Mandatory=$true)][string]$testProjectName, [Parameter(Mandatory=$true)][string]$targetFramework, [Parameter()][string]$categoryName="CoyoteConcurrencyTests", [Parameter()][string]$configuration="Release" ) +$ErrorActionPreference = "Stop" + $env:OTEL_RUN_COYOTE_TESTS = 'true' $rootDirectory = Get-Location Write-Host "Install Coyote CLI." -dotnet tool install --global Microsoft.Coyote.CLI +dotnet tool install --global Microsoft.Coyote.CLI --version $coyoteVersion + +if ($LASTEXITCODE -ne 0) { + throw "Microsoft.Coyote.CLI installation failed with exit code $LASTEXITCODE" +} Write-Host "Build $testProjectName project." dotnet build "$rootDirectory/test/$testProjectName/$testProjectName.csproj" --configuration $configuration -$artifactsPath = Join-Path $rootDirectory "test/$testProjectName/bin/$configuration/$targetFramework" +if ($LASTEXITCODE -ne 0) { + throw "dotnet build failed with exit code $LASTEXITCODE" +} + +$artifactsPath = Join-Path $rootDirectory "artifacts/bin/$testProjectName/$($configuration.ToLowerInvariant())_$targetFramework" Write-Host "Generate Coyote rewriting options JSON file." $assemblies = Get-ChildItem $artifactsPath -Filter OpenTelemetry*.dll | ForEach-Object {$_.Name} @@ -29,6 +39,13 @@ $RewriteOptionsJson | ConvertTo-Json -Compress | Set-Content -Path "$rootDirecto Write-Host "Run Coyote rewrite." coyote rewrite "$rootDirectory/test/$testProjectName/rewrite.coyote.json" +if ($LASTEXITCODE -ne 0) { + throw "coyote rewrite failed with exit code $LASTEXITCODE" +} + Write-Host "Execute re-written binary." dotnet test "$artifactsPath/$testProjectName.dll" --framework $targetFramework --filter CategoryName=$categoryName +if ($LASTEXITCODE -ne 0) { + throw "dotnet test failed with exit code $LASTEXITCODE" +} diff --git a/docs/Directory.Build.props b/docs/Directory.Build.props index 942eea5974c..9bc6a965577 100644 --- a/docs/Directory.Build.props +++ b/docs/Directory.Build.props @@ -1,8 +1,11 @@ + Exe $(TargetFrameworksForDocs) + + false diff --git a/docs/logs/complex-objects/FoodRecallNotice.cs b/docs/logs/complex-objects/FoodRecallNotice.cs index 40ca59e3b8d..e771fc7adc4 100644 --- a/docs/logs/complex-objects/FoodRecallNotice.cs +++ b/docs/logs/complex-objects/FoodRecallNotice.cs @@ -1,7 +1,9 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -public struct FoodRecallNotice +namespace ComplexObjects; + +internal struct FoodRecallNotice { public string? BrandName { get; set; } diff --git a/docs/logs/complex-objects/Program.cs b/docs/logs/complex-objects/Program.cs index c09cfc5c772..d4aa278f89e 100644 --- a/docs/logs/complex-objects/Program.cs +++ b/docs/logs/complex-objects/Program.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using ComplexObjects; using Microsoft.Extensions.Logging; using OpenTelemetry.Logs; diff --git a/docs/logs/correlation/Program.cs b/docs/logs/correlation/Program.cs index b1a3284fbcc..6eee75f3657 100644 --- a/docs/logs/correlation/Program.cs +++ b/docs/logs/correlation/Program.cs @@ -7,7 +7,9 @@ using OpenTelemetry.Logs; using OpenTelemetry.Trace; -public class Program +namespace Correlation; + +internal sealed class Program { private static readonly ActivitySource MyActivitySource = new("MyCompany.MyProduct.MyLibrary"); diff --git a/docs/logs/dedicated-pipeline/DedicatedLogging/DedicatedLoggingServiceCollectionExtensions.cs b/docs/logs/dedicated-pipeline/DedicatedLogging/DedicatedLoggingServiceCollectionExtensions.cs index c9343185e21..6652c195586 100644 --- a/docs/logs/dedicated-pipeline/DedicatedLogging/DedicatedLoggingServiceCollectionExtensions.cs +++ b/docs/logs/dedicated-pipeline/DedicatedLogging/DedicatedLoggingServiceCollectionExtensions.cs @@ -6,7 +6,7 @@ namespace DedicatedLogging; -public static class DedicatedLoggingServiceCollectionExtensions +internal static class DedicatedLoggingServiceCollectionExtensions { public static IServiceCollection AddDedicatedLogging( this IServiceCollection services, diff --git a/docs/logs/dedicated-pipeline/DedicatedLogging/IDedicatedLogger.cs b/docs/logs/dedicated-pipeline/DedicatedLogging/IDedicatedLogger.cs index ba09f27e271..975300a60e0 100644 --- a/docs/logs/dedicated-pipeline/DedicatedLogging/IDedicatedLogger.cs +++ b/docs/logs/dedicated-pipeline/DedicatedLogging/IDedicatedLogger.cs @@ -3,10 +3,10 @@ namespace DedicatedLogging; -public interface IDedicatedLogger : ILogger +internal interface IDedicatedLogger : ILogger { } -public interface IDedicatedLogger : IDedicatedLogger +internal interface IDedicatedLogger : IDedicatedLogger { } diff --git a/docs/logs/dedicated-pipeline/dedicated-pipeline.csproj b/docs/logs/dedicated-pipeline/dedicated-pipeline.csproj index cebc8460c42..6426d0f7ddd 100644 --- a/docs/logs/dedicated-pipeline/dedicated-pipeline.csproj +++ b/docs/logs/dedicated-pipeline/dedicated-pipeline.csproj @@ -1,4 +1,8 @@ + + $(NoWarn);CA1812;CA2213 + + diff --git a/docs/logs/extending-the-sdk/MyExporter.cs b/docs/logs/extending-the-sdk/MyExporter.cs index 6093f57394b..25b2d9809e1 100644 --- a/docs/logs/extending-the-sdk/MyExporter.cs +++ b/docs/logs/extending-the-sdk/MyExporter.cs @@ -5,7 +5,7 @@ using OpenTelemetry; using OpenTelemetry.Logs; -internal class MyExporter : BaseExporter +internal sealed class MyExporter : BaseExporter { private readonly string name; @@ -59,6 +59,7 @@ protected override bool OnShutdown(int timeoutMilliseconds) protected override void Dispose(bool disposing) { + base.Dispose(disposing); Console.WriteLine($"{this.name}.Dispose({disposing})"); } } diff --git a/docs/logs/extending-the-sdk/MyProcessor.cs b/docs/logs/extending-the-sdk/MyProcessor.cs index 5fcd34e1d8d..739f9f6f232 100644 --- a/docs/logs/extending-the-sdk/MyProcessor.cs +++ b/docs/logs/extending-the-sdk/MyProcessor.cs @@ -4,7 +4,7 @@ using OpenTelemetry; using OpenTelemetry.Logs; -internal class MyProcessor : BaseProcessor +internal sealed class MyProcessor : BaseProcessor { private readonly string name; @@ -32,6 +32,7 @@ protected override bool OnShutdown(int timeoutMilliseconds) protected override void Dispose(bool disposing) { + base.Dispose(disposing); Console.WriteLine($"{this.name}.Dispose({disposing})"); } } diff --git a/docs/logs/extending-the-sdk/Program.cs b/docs/logs/extending-the-sdk/Program.cs index cada6ec1f5d..1de67683841 100644 --- a/docs/logs/extending-the-sdk/Program.cs +++ b/docs/logs/extending-the-sdk/Program.cs @@ -6,7 +6,7 @@ namespace ExtendingTheSdk; -public class Program +internal sealed class Program { public static void Main() { @@ -29,16 +29,16 @@ public static void Main() // logger.LogInformation($"Hello from potato {0.99}."); // structured log with template - logger.LogInformation("Hello from {name} {price}.", "tomato", 2.99); + logger.LogInformation("Hello from {Name} {Price}.", "tomato", 2.99); // structured log with strong type - logger.LogInformation("{food}", new Food { Name = "artichoke", Price = 3.99 }); + logger.LogInformation("{Food}", new Food { Name = "artichoke", Price = 3.99 }); // structured log with anonymous type - logger.LogInformation("{food}", new { Name = "pumpkin", Price = 5.99 }); + logger.LogInformation("{Food}", new { Name = "pumpkin", Price = 5.99 }); // structured log with general type - logger.LogInformation("{food}", new Dictionary + logger.LogInformation("{Food}", new Dictionary { ["Name"] = "truffle", ["Price"] = 299.99, @@ -48,11 +48,11 @@ public static void Main() using (logger.BeginScope("[operation]")) using (logger.BeginScope("[hardware]")) { - logger.LogError("{name} is broken.", "refrigerator"); + logger.LogError("{Name} is broken.", "refrigerator"); } // message will be redacted by MyRedactionProcessor - logger.LogInformation("OpenTelemetry {sensitiveString}.", ""); + logger.LogInformation("OpenTelemetry {SensitiveString}.", ""); } internal struct Food diff --git a/docs/logs/extending-the-sdk/extending-the-sdk.csproj b/docs/logs/extending-the-sdk/extending-the-sdk.csproj index 4d96c349671..e775de84b5d 100644 --- a/docs/logs/extending-the-sdk/extending-the-sdk.csproj +++ b/docs/logs/extending-the-sdk/extending-the-sdk.csproj @@ -1,4 +1,8 @@  + + $(NoWarn);CA2000;CA1848;CA1510;CA1305 + + diff --git a/docs/logs/redaction/redaction.csproj b/docs/logs/redaction/redaction.csproj index 19aa9791432..f9c04fa7bff 100644 --- a/docs/logs/redaction/redaction.csproj +++ b/docs/logs/redaction/redaction.csproj @@ -1,4 +1,8 @@ + + $(NoWarn);CA2213;CA1812;CA1307 + + diff --git a/docs/metrics/README.md b/docs/metrics/README.md index 91736713579..a51c76319bb 100644 --- a/docs/metrics/README.md +++ b/docs/metrics/README.md @@ -59,7 +59,7 @@ too frequently. `Meter` is fairly expensive and meant to be reused throughout the application. For most applications, it can be modeled as static readonly field (e.g. [Program.cs](./getting-started-console/Program.cs)) or singleton via dependency injection (e.g. -[Instrumentation.cs](../../examples/AspNetCore/Instrumentation.cs)). +[InstrumentationSource.cs](../../examples/AspNetCore/InstrumentationSource.cs)). :heavy_check_mark: You should use dot-separated [UpperCamelCase](https://en.wikipedia.org/wiki/Camel_case) as the @@ -97,7 +97,7 @@ frequently. Instruments are fairly expensive and meant to be reused throughout the application. For most applications, instruments can be modeled as static readonly fields (e.g. [Program.cs](./getting-started-console/Program.cs)) or singleton via dependency injection (e.g. -[Instrumentation.cs](../../examples/AspNetCore/Instrumentation.cs)). +[InstrumentationSource.cs](../../examples/AspNetCore/InstrumentationSource.cs)). :stop_sign: You should avoid invalid instrument names. diff --git a/docs/metrics/customizing-the-sdk/Program.cs b/docs/metrics/customizing-the-sdk/Program.cs index bbfd4bea98f..cdcb6dcbbaf 100644 --- a/docs/metrics/customizing-the-sdk/Program.cs +++ b/docs/metrics/customizing-the-sdk/Program.cs @@ -8,7 +8,7 @@ namespace CustomizingTheSdk; -public class Program +internal static class Program { private static readonly Meter Meter1 = new("CompanyA.ProductA.Library1", "1.0"); private static readonly Meter Meter2 = new("CompanyA.ProductB.Library2", "1.0"); @@ -29,19 +29,19 @@ public static void Main() .AddView(instrumentName: "MyCounter", name: "MyCounterRenamed") // Change Histogram boundaries using the Explicit Bucket Histogram aggregation. - .AddView(instrumentName: "MyHistogram", new ExplicitBucketHistogramConfiguration() { Boundaries = new double[] { 10, 20 } }) + .AddView(instrumentName: "MyHistogram", new ExplicitBucketHistogramConfiguration() { Boundaries = [10.0, 20.0] }) // Change Histogram to use the Base2 Exponential Bucket Histogram aggregation. .AddView(instrumentName: "MyExponentialBucketHistogram", new Base2ExponentialBucketHistogramConfiguration()) // For the instrument "MyCounterCustomTags", aggregate with only the keys "tag1", "tag2". - .AddView(instrumentName: "MyCounterCustomTags", new MetricStreamConfiguration() { TagKeys = new string[] { "tag1", "tag2" } }) + .AddView(instrumentName: "MyCounterCustomTags", new MetricStreamConfiguration() { TagKeys = ["tag1", "tag2"] }) // Drop the instrument "MyCounterDrop". .AddView(instrumentName: "MyCounterDrop", MetricStreamConfiguration.Drop) // Configure the Explicit Bucket Histogram aggregation with custom boundaries and new name. - .AddView(instrumentName: "histogramWithMultipleAggregations", new ExplicitBucketHistogramConfiguration() { Boundaries = new double[] { 10, 20 }, Name = "MyHistogramWithExplicitHistogram" }) + .AddView(instrumentName: "histogramWithMultipleAggregations", new ExplicitBucketHistogramConfiguration() { Boundaries = [10.0, 20.0], Name = "MyHistogramWithExplicitHistogram" }) // Use Base2 Exponential Bucket Histogram aggregation and new name. .AddView(instrumentName: "histogramWithMultipleAggregations", new Base2ExponentialBucketHistogramConfiguration() { Name = "MyHistogramWithBase2ExponentialBucketHistogram" }) diff --git a/docs/metrics/customizing-the-sdk/customizing-the-sdk.csproj b/docs/metrics/customizing-the-sdk/customizing-the-sdk.csproj index 19aa9791432..97876c06b9c 100644 --- a/docs/metrics/customizing-the-sdk/customizing-the-sdk.csproj +++ b/docs/metrics/customizing-the-sdk/customizing-the-sdk.csproj @@ -1,4 +1,8 @@ + + $(NoWarn);CA5394 + + diff --git a/docs/metrics/exemplars/Program.cs b/docs/metrics/exemplars/Program.cs index cd63ac77ecc..d54e0ff6a2b 100644 --- a/docs/metrics/exemplars/Program.cs +++ b/docs/metrics/exemplars/Program.cs @@ -8,7 +8,9 @@ using OpenTelemetry.Metrics; using OpenTelemetry.Trace; -public class Program +namespace Exemplars; + +internal static class Program { private static readonly ActivitySource MyActivitySource = new("OpenTelemetry.Demo.Exemplar"); private static readonly Meter MyMeter = new("OpenTelemetry.Demo.Exemplar"); diff --git a/docs/metrics/exemplars/README.md b/docs/metrics/exemplars/README.md index 89f88866560..1f532c2ff0e 100644 --- a/docs/metrics/exemplars/README.md +++ b/docs/metrics/exemplars/README.md @@ -58,7 +58,7 @@ and enabled: ```sh -./prometheus --enable-feature=exemplar-storage --enable-feature=otlp-write-receiver +./prometheus --enable-feature=exemplar-storage --web.enable-otlp-receiver ``` ## Install and configure Grafana diff --git a/docs/metrics/exemplars/exemplars.csproj b/docs/metrics/exemplars/exemplars.csproj index cce12eec60d..686278e7a42 100644 --- a/docs/metrics/exemplars/exemplars.csproj +++ b/docs/metrics/exemplars/exemplars.csproj @@ -1,4 +1,8 @@ + + $(NoWarn);CA5394 + + diff --git a/docs/metrics/extending-the-sdk/MyExporter.cs b/docs/metrics/extending-the-sdk/MyExporter.cs index 228d3e0c342..376fa2f5b97 100644 --- a/docs/metrics/extending-the-sdk/MyExporter.cs +++ b/docs/metrics/extending-the-sdk/MyExporter.cs @@ -5,7 +5,7 @@ using OpenTelemetry; using OpenTelemetry.Metrics; -internal class MyExporter : BaseExporter +internal sealed class MyExporter : BaseExporter { private readonly string name; @@ -28,7 +28,7 @@ public override ExportResult Export(in Batch batch) sb.Append(", "); } - sb.Append($"{metric.Name}"); + sb.Append(metric.Name); foreach (ref readonly var metricPoint in metric.GetMetricPoints()) { @@ -52,6 +52,7 @@ protected override bool OnShutdown(int timeoutMilliseconds) protected override void Dispose(bool disposing) { + base.Dispose(disposing); Console.WriteLine($"{this.name}.Dispose({disposing})"); } } diff --git a/docs/metrics/extending-the-sdk/Program.cs b/docs/metrics/extending-the-sdk/Program.cs index a2952062261..0a7cf838cd1 100644 --- a/docs/metrics/extending-the-sdk/Program.cs +++ b/docs/metrics/extending-the-sdk/Program.cs @@ -8,7 +8,7 @@ namespace ExtendingTheSdk; -public class Program +internal static class Program { private static readonly Meter MyMeter = new("MyCompany.MyProduct.MyLibrary", "1.0"); private static readonly Counter MyFruitCounter = MyMeter.CreateCounter("MyFruitCounter"); diff --git a/docs/metrics/extending-the-sdk/extending-the-sdk.csproj b/docs/metrics/extending-the-sdk/extending-the-sdk.csproj index 4d96c349671..98a53a13c5a 100644 --- a/docs/metrics/extending-the-sdk/extending-the-sdk.csproj +++ b/docs/metrics/extending-the-sdk/extending-the-sdk.csproj @@ -1,4 +1,8 @@  + + $(NoWarn);CA2000;CA1510;CA1305 + + diff --git a/docs/metrics/getting-started-console/Program.cs b/docs/metrics/getting-started-console/Program.cs index 89425c6d41c..dbc42339877 100644 --- a/docs/metrics/getting-started-console/Program.cs +++ b/docs/metrics/getting-started-console/Program.cs @@ -5,7 +5,9 @@ using OpenTelemetry; using OpenTelemetry.Metrics; -public class Program +namespace GettingStartedConsole; + +internal static class Program { private static readonly Meter MyMeter = new("MyCompany.MyProduct.MyLibrary", "1.0"); private static readonly Counter MyFruitCounter = MyMeter.CreateCounter("MyFruitCounter"); diff --git a/docs/metrics/getting-started-prometheus-grafana/Program.cs b/docs/metrics/getting-started-prometheus-grafana/Program.cs index 0fd2437eefd..848116636c0 100644 --- a/docs/metrics/getting-started-prometheus-grafana/Program.cs +++ b/docs/metrics/getting-started-prometheus-grafana/Program.cs @@ -6,7 +6,9 @@ using OpenTelemetry.Exporter; using OpenTelemetry.Metrics; -public class Program +namespace GettingStartedPrometheusGrafana; + +internal static class Program { private static readonly Meter MyMeter = new("MyCompany.MyProduct.MyLibrary", "1.0"); private static readonly Counter MyFruitCounter = MyMeter.CreateCounter("MyFruitCounter"); diff --git a/docs/metrics/getting-started-prometheus-grafana/README.md b/docs/metrics/getting-started-prometheus-grafana/README.md index dfc3e09c1a3..75a09cb1226 100644 --- a/docs/metrics/getting-started-prometheus-grafana/README.md +++ b/docs/metrics/getting-started-prometheus-grafana/README.md @@ -88,7 +88,7 @@ access. Run the `prometheus(.exe)` server executable with feature flag enabled: ```sh -./prometheus --enable-feature=otlp-write-receiver +./prometheus --web.enable-otlp-receiver ``` ### View results in Prometheus diff --git a/docs/metrics/learning-more-instruments/Program.cs b/docs/metrics/learning-more-instruments/Program.cs index c887e281461..9acbcac5639 100644 --- a/docs/metrics/learning-more-instruments/Program.cs +++ b/docs/metrics/learning-more-instruments/Program.cs @@ -8,7 +8,7 @@ namespace LearningMoreInstruments; -public class Program +internal static class Program { private static readonly Meter MyMeter = new("MyCompany.MyProduct.MyLibrary", "1.0"); private static readonly Histogram MyHistogram = MyMeter.CreateHistogram("MyHistogram"); diff --git a/docs/metrics/learning-more-instruments/learning-more-instruments.csproj b/docs/metrics/learning-more-instruments/learning-more-instruments.csproj index 9f5b6b79bc3..001e041f0f7 100644 --- a/docs/metrics/learning-more-instruments/learning-more-instruments.csproj +++ b/docs/metrics/learning-more-instruments/learning-more-instruments.csproj @@ -1,4 +1,8 @@ + + $(NoWarn);CA5394 + + diff --git a/docs/resources/extending-the-sdk/LoggerExtensions.cs b/docs/resources/extending-the-sdk/LoggerExtensions.cs new file mode 100644 index 00000000000..a57502fc0f8 --- /dev/null +++ b/docs/resources/extending-the-sdk/LoggerExtensions.cs @@ -0,0 +1,12 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.Logging; + +namespace ExtendingTheSdk; + +internal static partial class LoggerExtensions +{ + [LoggerMessage(LogLevel.Information, "Hello from {Name} {Price}")] + public static partial void HelloFrom(this ILogger logger, string name, double price); +} diff --git a/docs/resources/extending-the-sdk/MyResourceDetector.cs b/docs/resources/extending-the-sdk/MyResourceDetector.cs index df40d9b3302..978e4a7edb6 100644 --- a/docs/resources/extending-the-sdk/MyResourceDetector.cs +++ b/docs/resources/extending-the-sdk/MyResourceDetector.cs @@ -3,7 +3,7 @@ using OpenTelemetry.Resources; -internal class MyResourceDetector : IResourceDetector +internal sealed class MyResourceDetector : IResourceDetector { public Resource Detect() { diff --git a/docs/resources/extending-the-sdk/Program.cs b/docs/resources/extending-the-sdk/Program.cs index 849bdcc182b..6877d0c5cc7 100644 --- a/docs/resources/extending-the-sdk/Program.cs +++ b/docs/resources/extending-the-sdk/Program.cs @@ -11,7 +11,7 @@ namespace ExtendingTheSdk; -public class Program +internal static class Program { private static readonly ActivitySource DemoSource = new("OTel.Demo"); private static readonly Meter MeterDemoSource = new("OTel.Demo"); @@ -55,7 +55,6 @@ public static void Main() } var logger = loggerFactory.CreateLogger("OTel.Demo"); - logger - .LogInformation("Hello from {Name} {Price}", "tomato", 2.99); + logger.HelloFrom("tomato", 2.99); } } diff --git a/docs/trace/README.md b/docs/trace/README.md index f0cdb973e1e..80f7c48978e 100644 --- a/docs/trace/README.md +++ b/docs/trace/README.md @@ -49,7 +49,7 @@ too frequently. `ActivitySource` is fairly expensive and meant to be reused throughout the application. For most applications, it can be modeled as static readonly field (e.g. [Program.cs](./getting-started-console/Program.cs)) or singleton via dependency injection (e.g. -[Instrumentation.cs](../../examples/AspNetCore/Instrumentation.cs)). +[InstrumentationSource.cs](../../examples/AspNetCore/InstrumentationSource.cs)). :heavy_check_mark: You should use dot-separated [UpperCamelCase](https://en.wikipedia.org/wiki/Camel_case) as the diff --git a/docs/trace/customizing-the-sdk/Program.cs b/docs/trace/customizing-the-sdk/Program.cs index fead3aa0be2..1cc63b0fcc5 100644 --- a/docs/trace/customizing-the-sdk/Program.cs +++ b/docs/trace/customizing-the-sdk/Program.cs @@ -8,7 +8,7 @@ namespace CustomizingTheSdk; -public class Program +internal static class Program { private static readonly ActivitySource MyLibraryActivitySource = new( "MyCompany.MyProduct.MyLibrary"); diff --git a/docs/trace/extending-the-sdk/MyEnrichingProcessor.cs b/docs/trace/extending-the-sdk/MyEnrichingProcessor.cs index a6107330485..4372f9e59f3 100644 --- a/docs/trace/extending-the-sdk/MyEnrichingProcessor.cs +++ b/docs/trace/extending-the-sdk/MyEnrichingProcessor.cs @@ -4,7 +4,7 @@ using System.Diagnostics; using OpenTelemetry; -internal class MyEnrichingProcessor : BaseProcessor +internal sealed class MyEnrichingProcessor : BaseProcessor { public override void OnEnd(Activity activity) { diff --git a/docs/trace/extending-the-sdk/MyExporter.cs b/docs/trace/extending-the-sdk/MyExporter.cs index d37b9daac46..f00674d1ffc 100644 --- a/docs/trace/extending-the-sdk/MyExporter.cs +++ b/docs/trace/extending-the-sdk/MyExporter.cs @@ -5,7 +5,7 @@ using System.Text; using OpenTelemetry; -internal class MyExporter : BaseExporter +internal sealed class MyExporter : BaseExporter { private readonly string name; @@ -43,6 +43,7 @@ protected override bool OnShutdown(int timeoutMilliseconds) protected override void Dispose(bool disposing) { + base.Dispose(disposing); Console.WriteLine($"{this.name}.Dispose({disposing})"); } } diff --git a/docs/trace/extending-the-sdk/MyProcessor.cs b/docs/trace/extending-the-sdk/MyProcessor.cs index 4171821d49a..d6ab394ce03 100644 --- a/docs/trace/extending-the-sdk/MyProcessor.cs +++ b/docs/trace/extending-the-sdk/MyProcessor.cs @@ -4,7 +4,7 @@ using System.Diagnostics; using OpenTelemetry; -internal class MyProcessor : BaseProcessor +internal sealed class MyProcessor : BaseProcessor { private readonly string name; @@ -37,6 +37,7 @@ protected override bool OnShutdown(int timeoutMilliseconds) protected override void Dispose(bool disposing) { + base.Dispose(disposing); Console.WriteLine($"{this.name}.Dispose({disposing})"); } } diff --git a/docs/trace/extending-the-sdk/MyResourceDetector.cs b/docs/trace/extending-the-sdk/MyResourceDetector.cs index df40d9b3302..978e4a7edb6 100644 --- a/docs/trace/extending-the-sdk/MyResourceDetector.cs +++ b/docs/trace/extending-the-sdk/MyResourceDetector.cs @@ -3,7 +3,7 @@ using OpenTelemetry.Resources; -internal class MyResourceDetector : IResourceDetector +internal sealed class MyResourceDetector : IResourceDetector { public Resource Detect() { diff --git a/docs/trace/extending-the-sdk/MySampler.cs b/docs/trace/extending-the-sdk/MySampler.cs index bbb4ae04793..2d733aed25d 100644 --- a/docs/trace/extending-the-sdk/MySampler.cs +++ b/docs/trace/extending-the-sdk/MySampler.cs @@ -3,7 +3,7 @@ using OpenTelemetry.Trace; -internal class MySampler : Sampler +internal sealed class MySampler : Sampler { public override SamplingResult ShouldSample(in SamplingParameters param) { diff --git a/docs/trace/extending-the-sdk/Program.cs b/docs/trace/extending-the-sdk/Program.cs index ce001aee3e3..e051d4ed71a 100644 --- a/docs/trace/extending-the-sdk/Program.cs +++ b/docs/trace/extending-the-sdk/Program.cs @@ -8,7 +8,7 @@ namespace ExtendingTheSdk; -public class Program +internal static class Program { private static readonly ActivitySource DemoSource = new("OTel.Demo"); diff --git a/docs/trace/extending-the-sdk/extending-the-sdk.csproj b/docs/trace/extending-the-sdk/extending-the-sdk.csproj index 85aab7a7ed3..2f56fd33163 100644 --- a/docs/trace/extending-the-sdk/extending-the-sdk.csproj +++ b/docs/trace/extending-the-sdk/extending-the-sdk.csproj @@ -1,4 +1,8 @@ + + $(NoWarn);CA2000;CA1812;CA1510 + + diff --git a/docs/trace/getting-started-console/Program.cs b/docs/trace/getting-started-console/Program.cs index 4acaf7b8a80..3ab95d4d24f 100644 --- a/docs/trace/getting-started-console/Program.cs +++ b/docs/trace/getting-started-console/Program.cs @@ -5,7 +5,9 @@ using OpenTelemetry; using OpenTelemetry.Trace; -public class Program +namespace GettingStartedConsole; + +internal static class Program { private static readonly ActivitySource MyActivitySource = new("MyCompany.MyProduct.MyLibrary"); @@ -18,9 +20,10 @@ public static void Main() using (var activity = MyActivitySource.StartActivity("SayHello")) { + int[] intArray = [1, 2, 3]; activity?.SetTag("foo", 1); activity?.SetTag("bar", "Hello, World!"); - activity?.SetTag("baz", new int[] { 1, 2, 3 }); + activity?.SetTag("baz", intArray); activity?.SetStatus(ActivityStatusCode.Ok); } diff --git a/docs/trace/getting-started-jaeger/Program.cs b/docs/trace/getting-started-jaeger/Program.cs index d4603a71bd0..bff8079f132 100644 --- a/docs/trace/getting-started-jaeger/Program.cs +++ b/docs/trace/getting-started-jaeger/Program.cs @@ -11,7 +11,7 @@ namespace GettingStartedJaeger; -public class Program +internal static class Program { private static readonly ActivitySource MyActivitySource = new("OpenTelemetry.Demo.Jaeger"); @@ -33,13 +33,13 @@ public static async Task Main() { using (var slow = MyActivitySource.StartActivity("SomethingSlow")) { - await client.GetStringAsync("https://httpstat.us/200?sleep=1000"); - await client.GetStringAsync("https://httpstat.us/200?sleep=1000"); + await client.GetStringAsync(new Uri("https://httpstat.us/200?sleep=1000")).ConfigureAwait(false); + await client.GetStringAsync(new Uri("https://httpstat.us/200?sleep=1000")).ConfigureAwait(false); } using (var fast = MyActivitySource.StartActivity("SomethingFast")) { - await client.GetStringAsync("https://httpstat.us/301"); + await client.GetStringAsync(new Uri("https://httpstat.us/301")).ConfigureAwait(false); } } } diff --git a/docs/trace/links-based-sampler/LinksAndParentBasedSampler.cs b/docs/trace/links-based-sampler/LinksAndParentBasedSampler.cs index bca076a2734..af44cb92072 100644 --- a/docs/trace/links-based-sampler/LinksAndParentBasedSampler.cs +++ b/docs/trace/links-based-sampler/LinksAndParentBasedSampler.cs @@ -13,7 +13,7 @@ namespace LinksAndParentBasedSamplerExample; /// links based sampler. If either of these samplers decide to sample, /// this composite sampler decides to sample. /// -internal class LinksAndParentBasedSampler : Sampler +internal sealed class LinksAndParentBasedSampler : Sampler { private readonly ParentBasedSampler parentBasedSampler; private readonly LinksBasedSampler linksBasedSampler; diff --git a/docs/trace/links-based-sampler/LinksBasedSampler.cs b/docs/trace/links-based-sampler/LinksBasedSampler.cs index b9306fab232..226b6591693 100644 --- a/docs/trace/links-based-sampler/LinksBasedSampler.cs +++ b/docs/trace/links-based-sampler/LinksBasedSampler.cs @@ -10,7 +10,7 @@ namespace LinksAndParentBasedSamplerExample; /// A non-probabilistic sampler that samples an activity if ANY of the linked activities /// is sampled. /// -internal class LinksBasedSampler : Sampler +internal sealed class LinksBasedSampler : Sampler { public override SamplingResult ShouldSample(in SamplingParameters samplingParameters) { diff --git a/docs/trace/links-based-sampler/Program.cs b/docs/trace/links-based-sampler/Program.cs index 7ef89aa5fdb..03827633820 100644 --- a/docs/trace/links-based-sampler/Program.cs +++ b/docs/trace/links-based-sampler/Program.cs @@ -7,7 +7,7 @@ namespace LinksAndParentBasedSamplerExample; -internal class Program +internal static class Program { private static readonly ActivitySource MyActivitySource = new("LinksAndParentBasedSampler.Example"); @@ -37,7 +37,7 @@ public static void Main(string[] args) /// Generates a list of activity links. A linked activity is sampled with a probability of 0.1. /// /// A list of links. - private static IEnumerable GetActivityLinks(int seed) + private static List GetActivityLinks(int seed) { var random = new Random(seed); var linkedActivitiesList = new List(); diff --git a/docs/trace/links-based-sampler/links-sampler.csproj b/docs/trace/links-based-sampler/links-sampler.csproj index 19aa9791432..97876c06b9c 100644 --- a/docs/trace/links-based-sampler/links-sampler.csproj +++ b/docs/trace/links-based-sampler/links-sampler.csproj @@ -1,4 +1,8 @@ + + $(NoWarn);CA5394 + + diff --git a/docs/trace/links-creation-with-new-activities/Program.cs b/docs/trace/links-creation-with-new-activities/Program.cs index c03af7bcc82..1568930d998 100644 --- a/docs/trace/links-creation-with-new-activities/Program.cs +++ b/docs/trace/links-creation-with-new-activities/Program.cs @@ -7,7 +7,7 @@ namespace LinksCreationWithNewRootActivitiesDemo; -internal class Program +internal static class Program { private static readonly ActivitySource MyActivitySource = new("LinksCreationWithNewRootActivities"); @@ -21,7 +21,7 @@ public static async Task Main(string[] args) using (var activity = MyActivitySource.StartActivity("OrchestratingActivity")) { activity?.SetTag("foo", 1); - await DoFanoutAsync(); + await DoFanoutAsync().ConfigureAwait(false); using (var nestedActivity = MyActivitySource.StartActivity("WrapUp")) { @@ -77,7 +77,7 @@ public static async Task DoFanoutAsync() } // Wait for all tasks to complete - await Task.WhenAll(tasks); + await Task.WhenAll(tasks).ConfigureAwait(false); // Reset to the previous activity now that we are done with the fanout // This will ensure that the rest of the code executes in the context of the original activity. diff --git a/docs/trace/reporting-exceptions/Program.cs b/docs/trace/reporting-exceptions/Program.cs index 62f2a11914e..a6e4997ff79 100644 --- a/docs/trace/reporting-exceptions/Program.cs +++ b/docs/trace/reporting-exceptions/Program.cs @@ -7,7 +7,7 @@ namespace ReportingExceptions; -public class Program +internal static class Program { private static readonly ActivitySource MyActivitySource = new( "MyCompany.MyProduct.MyLibrary"); @@ -27,7 +27,7 @@ public static void Main() { using (MyActivitySource.StartActivity("Bar")) { - throw new Exception("Oops!"); + throw new InvalidOperationException("Oops!"); } } } diff --git a/docs/trace/stratified-sampling-example/Program.cs b/docs/trace/stratified-sampling-example/Program.cs index 49d8e1b7e7b..8063dd4f046 100644 --- a/docs/trace/stratified-sampling-example/Program.cs +++ b/docs/trace/stratified-sampling-example/Program.cs @@ -7,7 +7,7 @@ namespace StratifiedSamplingByQueryTypeDemo; -internal class Program +internal sealed class Program { private static readonly ActivitySource MyActivitySource = new("StratifiedSampling.POC"); diff --git a/docs/trace/stratified-sampling-example/StratifiedSampler.cs b/docs/trace/stratified-sampling-example/StratifiedSampler.cs index cb01a23dee0..64cbe8a4c84 100644 --- a/docs/trace/stratified-sampling-example/StratifiedSampler.cs +++ b/docs/trace/stratified-sampling-example/StratifiedSampler.cs @@ -5,7 +5,7 @@ namespace StratifiedSamplingByQueryTypeDemo; -internal class StratifiedSampler : Sampler +internal sealed class StratifiedSampler : Sampler { // For this POC, we have two groups. // 0 is the group corresponding to user-initiated queries where we want a 100% sampling rate. diff --git a/docs/trace/stratified-sampling-example/stratified-sampling-example.csproj b/docs/trace/stratified-sampling-example/stratified-sampling-example.csproj index 19aa9791432..97876c06b9c 100644 --- a/docs/trace/stratified-sampling-example/stratified-sampling-example.csproj +++ b/docs/trace/stratified-sampling-example/stratified-sampling-example.csproj @@ -1,4 +1,8 @@ + + $(NoWarn);CA5394 + + diff --git a/docs/trace/tail-based-sampling-span-level/ParentBasedElseAlwaysRecordSampler.cs b/docs/trace/tail-based-sampling-span-level/ParentBasedElseAlwaysRecordSampler.cs index bce691b0c16..4d933dbf2ec 100644 --- a/docs/trace/tail-based-sampling-span-level/ParentBasedElseAlwaysRecordSampler.cs +++ b/docs/trace/tail-based-sampling-span-level/ParentBasedElseAlwaysRecordSampler.cs @@ -15,7 +15,7 @@ namespace SDKBasedSpanLevelTailSamplingSample; /// changed later by a span processor based on span attributes (e.g., failure) that become /// available only by the end of the span. /// -internal class ParentBasedElseAlwaysRecordSampler : Sampler +internal sealed class ParentBasedElseAlwaysRecordSampler : Sampler { private const double DefaultSamplingProbabilityForRootSpan = 0.1; private readonly ParentBasedSampler parentBasedSampler; diff --git a/docs/trace/tail-based-sampling-span-level/Program.cs b/docs/trace/tail-based-sampling-span-level/Program.cs index de4f8ea9f17..d2114248442 100644 --- a/docs/trace/tail-based-sampling-span-level/Program.cs +++ b/docs/trace/tail-based-sampling-span-level/Program.cs @@ -7,7 +7,7 @@ namespace SDKBasedSpanLevelTailSamplingSample; -internal class Program +internal static class Program { private static readonly ActivitySource MyActivitySource = new("SDK.TailSampling.POC"); diff --git a/docs/trace/tail-based-sampling-span-level/TailSamplingProcessor.cs b/docs/trace/tail-based-sampling-span-level/TailSamplingProcessor.cs index 5aa22092e75..10aee54e38d 100644 --- a/docs/trace/tail-based-sampling-span-level/TailSamplingProcessor.cs +++ b/docs/trace/tail-based-sampling-span-level/TailSamplingProcessor.cs @@ -26,7 +26,7 @@ public override void OnEnd(Activity activity) } else { - this.IncludeForExportIfFailedActivity(activity); + IncludeForExportIfFailedActivity(activity); } base.OnEnd(activity); @@ -42,7 +42,7 @@ public override void OnEnd(Activity activity) // 2. Traces will not be complete: Since this sampling is at a span level, the generated trace will be partial and won't be complete. // For example, if another part of the call tree is successful, those spans may not be sampled in leading to a partial trace. // 3. If multiple exporters are used, this decision will impact all of them: https://github.com/open-telemetry/opentelemetry-dotnet/issues/3861. - private void IncludeForExportIfFailedActivity(Activity activity) + private static void IncludeForExportIfFailedActivity(Activity activity) { if (activity.Status == ActivityStatusCode.Error) { diff --git a/docs/trace/tail-based-sampling-span-level/tail-based-sampling-example.csproj b/docs/trace/tail-based-sampling-span-level/tail-based-sampling-example.csproj index 19aa9791432..b7947e79e17 100644 --- a/docs/trace/tail-based-sampling-span-level/tail-based-sampling-example.csproj +++ b/docs/trace/tail-based-sampling-span-level/tail-based-sampling-example.csproj @@ -1,4 +1,8 @@ + + $(NoWarn);CA5394;CA2000 + + diff --git a/examples/AspNetCore/Controllers/WeatherForecastController.cs b/examples/AspNetCore/Controllers/WeatherForecastController.cs index 3d09fe9ed61..bbb257334e7 100644 --- a/examples/AspNetCore/Controllers/WeatherForecastController.cs +++ b/examples/AspNetCore/Controllers/WeatherForecastController.cs @@ -5,6 +5,7 @@ namespace Examples.AspNetCore.Controllers; using System.Diagnostics; using System.Diagnostics.Metrics; +using System.Security.Cryptography; using Examples.AspNetCore; using Microsoft.AspNetCore.Mvc; @@ -12,10 +13,10 @@ namespace Examples.AspNetCore.Controllers; [Route("[controller]")] public class WeatherForecastController : ControllerBase { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching", - }; + private static readonly string[] Summaries = + [ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + ]; private static readonly HttpClient HttpClient = new(); @@ -23,24 +24,24 @@ public class WeatherForecastController : ControllerBase private readonly ActivitySource activitySource; private readonly Counter freezingDaysCounter; - public WeatherForecastController(ILogger logger, Instrumentation instrumentation) + public WeatherForecastController(ILogger logger, InstrumentationSource instrumentationSource) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - ArgumentNullException.ThrowIfNull(instrumentation); - this.activitySource = instrumentation.ActivitySource; - this.freezingDaysCounter = instrumentation.FreezingDaysCounter; + ArgumentNullException.ThrowIfNull(instrumentationSource); + this.activitySource = instrumentationSource.ActivitySource; + this.freezingDaysCounter = instrumentationSource.FreezingDaysCounter; } [HttpGet] public IEnumerable Get() { - using var scope = this.logger.BeginScope("{Id}", Guid.NewGuid().ToString("N")); + using var scope = this.logger.BeginIdScope(Guid.NewGuid().ToString("N")); - // Making an http call here to serve as an example of + // Making a http call here to serve as an example of // how dependency calls will be captured and treated // automatically as child of incoming request. - var res = HttpClient.GetStringAsync("http://google.com").Result; + var res = HttpClient.GetStringAsync(new Uri("http://google.com")).Result; // Optional: Manually create an activity. This will become a child of // the activity created from the instrumentation library for AspNetCore. @@ -52,22 +53,18 @@ public IEnumerable Get() // a manual activity using Activity.Current?.SetTag() using var activity = this.activitySource.StartActivity("calculate forecast"); - var rng = new Random(); var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), - TemperatureC = rng.Next(-20, 55), - Summary = Summaries[rng.Next(Summaries.Length)], + TemperatureC = RandomNumberGenerator.GetInt32(-20, 55), + Summary = Summaries[RandomNumberGenerator.GetInt32(Summaries.Length)], }) .ToArray(); // Optional: Count the freezing days this.freezingDaysCounter.Add(forecast.Count(f => f.TemperatureC < 0)); - this.logger.LogInformation( - "WeatherForecasts generated {count}: {forecasts}", - forecast.Length, - forecast); + this.logger.WeatherForecastGenerated(LogLevel.Information, forecast.Length, forecast); return forecast; } diff --git a/examples/AspNetCore/Controllers/WeatherForecastControllerLog.cs b/examples/AspNetCore/Controllers/WeatherForecastControllerLog.cs new file mode 100644 index 00000000000..eb6bc292673 --- /dev/null +++ b/examples/AspNetCore/Controllers/WeatherForecastControllerLog.cs @@ -0,0 +1,14 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +namespace Examples.AspNetCore.Controllers; + +internal static partial class WeatherForecastControllerLog +{ + private static readonly Func Scope = LoggerMessage.DefineScope("{Id}"); + + public static IDisposable? BeginIdScope(this ILogger logger, string id) => Scope(logger, id); + + [LoggerMessage(EventId = 1, Message = "WeatherForecasts generated {Count}: {Forecasts}")] + public static partial void WeatherForecastGenerated(this ILogger logger, LogLevel logLevel, int count, WeatherForecast[] forecasts); +} diff --git a/examples/AspNetCore/Examples.AspNetCore.csproj b/examples/AspNetCore/Examples.AspNetCore.csproj index e476b1ed4d8..459213fe2ba 100644 --- a/examples/AspNetCore/Examples.AspNetCore.csproj +++ b/examples/AspNetCore/Examples.AspNetCore.csproj @@ -2,6 +2,7 @@ $(DefaultTargetFrameworkForExampleApps) + $(NoWarn);CA1515 diff --git a/examples/AspNetCore/Instrumentation.cs b/examples/AspNetCore/InstrumentationSource.cs similarity index 85% rename from examples/AspNetCore/Instrumentation.cs rename to examples/AspNetCore/InstrumentationSource.cs index 4b0ede1157f..be6d5764033 100644 --- a/examples/AspNetCore/Instrumentation.cs +++ b/examples/AspNetCore/InstrumentationSource.cs @@ -11,15 +11,15 @@ namespace Examples.AspNetCore; /// ActivitySource and Instruments. This avoids possible type collisions /// with other components in the DI container. /// -public class Instrumentation : IDisposable +public sealed class InstrumentationSource : IDisposable { internal const string ActivitySourceName = "Examples.AspNetCore"; internal const string MeterName = "Examples.AspNetCore"; private readonly Meter meter; - public Instrumentation() + public InstrumentationSource() { - string? version = typeof(Instrumentation).Assembly.GetName().Version?.ToString(); + string? version = typeof(InstrumentationSource).Assembly.GetName().Version?.ToString(); this.ActivitySource = new ActivitySource(ActivitySourceName, version); this.meter = new Meter(MeterName, version); this.FreezingDaysCounter = this.meter.CreateCounter("weather.days.freezing", description: "The number of days where the temperature is below freezing"); diff --git a/examples/AspNetCore/Program.cs b/examples/AspNetCore/Program.cs index 809534f5c76..afd59db31d7 100644 --- a/examples/AspNetCore/Program.cs +++ b/examples/AspNetCore/Program.cs @@ -13,20 +13,20 @@ var appBuilder = WebApplication.CreateBuilder(args); // Note: Switch between Zipkin/OTLP/Console by setting UseTracingExporter in appsettings.json. -var tracingExporter = appBuilder.Configuration.GetValue("UseTracingExporter", defaultValue: "console")!.ToLowerInvariant(); +var tracingExporter = appBuilder.Configuration.GetValue("UseTracingExporter", defaultValue: "CONSOLE").ToUpperInvariant(); // Note: Switch between Prometheus/OTLP/Console by setting UseMetricsExporter in appsettings.json. -var metricsExporter = appBuilder.Configuration.GetValue("UseMetricsExporter", defaultValue: "console")!.ToLowerInvariant(); +var metricsExporter = appBuilder.Configuration.GetValue("UseMetricsExporter", defaultValue: "CONSOLE").ToUpperInvariant(); // Note: Switch between Console/OTLP by setting UseLogExporter in appsettings.json. -var logExporter = appBuilder.Configuration.GetValue("UseLogExporter", defaultValue: "console")!.ToLowerInvariant(); +var logExporter = appBuilder.Configuration.GetValue("UseLogExporter", defaultValue: "CONSOLE").ToUpperInvariant(); // Note: Switch between Explicit/Exponential by setting HistogramAggregation in appsettings.json -var histogramAggregation = appBuilder.Configuration.GetValue("HistogramAggregation", defaultValue: "explicit")!.ToLowerInvariant(); +var histogramAggregation = appBuilder.Configuration.GetValue("HistogramAggregation", defaultValue: "EXPLICIT").ToUpperInvariant(); // Create a service to expose ActivitySource, and Metric Instruments // for manual instrumentation -appBuilder.Services.AddSingleton(); +appBuilder.Services.AddSingleton(); // Clear default logging providers used by WebApplication host. appBuilder.Logging.ClearProviders(); @@ -45,7 +45,7 @@ // Ensure the TracerProvider subscribes to any custom ActivitySources. builder - .AddSource(Instrumentation.ActivitySourceName) + .AddSource(InstrumentationSource.ActivitySourceName) .SetSampler(new AlwaysOnSampler()) .AddHttpClientInstrumentation() .AddAspNetCoreInstrumentation(); @@ -55,7 +55,7 @@ switch (tracingExporter) { - case "zipkin": + case "ZIPKIN": builder.AddZipkinExporter(); builder.ConfigureServices(services => @@ -65,11 +65,11 @@ }); break; - case "otlp": + case "OTLP": builder.AddOtlpExporter(otlpOptions => { // Use IConfiguration directly for Otlp exporter endpoint option. - otlpOptions.Endpoint = new Uri(appBuilder.Configuration.GetValue("Otlp:Endpoint", defaultValue: "http://localhost:4317")!); + otlpOptions.Endpoint = new Uri(appBuilder.Configuration.GetValue("Otlp:Endpoint", defaultValue: "http://localhost:4317")); }); break; @@ -84,7 +84,7 @@ // Ensure the MeterProvider subscribes to any custom Meters. builder - .AddMeter(Instrumentation.MeterName) + .AddMeter(InstrumentationSource.MeterName) .SetExemplarFilter(ExemplarFilterType.TraceBased) .AddRuntimeInstrumentation() .AddHttpClientInstrumentation() @@ -92,7 +92,7 @@ switch (histogramAggregation) { - case "exponential": + case "EXPONENTIAL": builder.AddView(instrument => { return instrument.GetType().GetGenericTypeDefinition() == typeof(Histogram<>) @@ -108,10 +108,10 @@ switch (metricsExporter) { - case "prometheus": + case "PROMETHEUS": builder.AddPrometheusExporter(); break; - case "otlp": + case "OTLP": builder.AddOtlpExporter(otlpOptions => { // Use IConfiguration directly for Otlp exporter endpoint option. @@ -129,11 +129,11 @@ switch (logExporter) { - case "otlp": + case "OTLP": builder.AddOtlpExporter(otlpOptions => { // Use IConfiguration directly for Otlp exporter endpoint option. - otlpOptions.Endpoint = new Uri(appBuilder.Configuration.GetValue("Otlp:Endpoint", defaultValue: "http://localhost:4317")!); + otlpOptions.Endpoint = new Uri(appBuilder.Configuration.GetValue("Otlp:Endpoint", defaultValue: "http://localhost:4317")); }); break; default: diff --git a/examples/AspNetCore/docker-compose.yaml b/examples/AspNetCore/docker-compose.yaml index 7cdb95106e1..444e79eca81 100644 --- a/examples/AspNetCore/docker-compose.yaml +++ b/examples/AspNetCore/docker-compose.yaml @@ -1,9 +1,8 @@ -version: "3" services: # OTEL Collector to receive logs, metrics and traces from the application otel-collector: - image: otel/opentelemetry-collector:0.111.0 + image: otel/opentelemetry-collector:0.136.0@sha256:98fd3b410ae8a939be9588f1580c4b7c3da6ebba49f5363df4259a827aabb779 command: [ "--config=/etc/otel-collector.yaml" ] volumes: - ./otel-collector.yaml:/etc/otel-collector.yaml @@ -14,7 +13,7 @@ services: # Exports Traces to Tempo tempo: - image: grafana/tempo:latest + image: grafana/tempo:2.8.2@sha256:0ef775495967cd5d7a6b2e146b6ea695d624803c8db8349fb8ce4164f719f9b7 command: [ "-config.file=/etc/tempo.yaml" ] volumes: - ./tempo.yaml:/etc/tempo.yaml @@ -26,7 +25,7 @@ services: # Exports Metrics to Prometheus prometheus: - image: prom/prometheus:latest + image: prom/prometheus:v3.6.0@sha256:76947e7ef22f8a698fc638f706685909be425dbe09bd7a2cd7aca849f79b5f64 command: - --config.file=/etc/prometheus.yaml - --web.enable-remote-write-receiver @@ -38,7 +37,7 @@ services: # UI to query traces and metrics grafana: - image: grafana/grafana:9.3.2 + image: grafana/grafana:12.2.0@sha256:74144189b38447facf737dfd0f3906e42e0776212bf575dc3334c3609183adf7 volumes: - ./grafana-datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml environment: diff --git a/examples/AspNetCore/tempo.yaml b/examples/AspNetCore/tempo.yaml index 0d46d4718e8..1217bc05760 100644 --- a/examples/AspNetCore/tempo.yaml +++ b/examples/AspNetCore/tempo.yaml @@ -6,7 +6,9 @@ distributor: otlp: protocols: http: + endpoint: 0.0.0.0:4318 grpc: + endpoint: 0.0.0.0:4317 storage: trace: diff --git a/examples/Console/Examples.Console.csproj b/examples/Console/Examples.Console.csproj index 86c6b1df037..ade79a55665 100644 --- a/examples/Console/Examples.Console.csproj +++ b/examples/Console/Examples.Console.csproj @@ -1,9 +1,9 @@ - + Exe $(DefaultTargetFrameworkForExampleApps) - $(NoWarn),CS0618 + $(NoWarn),CA1812 diff --git a/examples/Console/InstrumentationWithActivitySource.cs b/examples/Console/InstrumentationWithActivitySource.cs index 706d68ad993..e591ea12505 100644 --- a/examples/Console/InstrumentationWithActivitySource.cs +++ b/examples/Console/InstrumentationWithActivitySource.cs @@ -8,7 +8,7 @@ namespace Examples.Console; -internal class InstrumentationWithActivitySource : IDisposable +internal sealed class InstrumentationWithActivitySource : IDisposable { private const string RequestPath = "/api/request"; private readonly SampleServer server = new(); @@ -27,7 +27,7 @@ public void Dispose() this.server.Dispose(); } - private class SampleServer : IDisposable + private sealed class SampleServer : IDisposable { private readonly HttpListener listener = new(); @@ -66,7 +66,7 @@ public void Start(string url) } activity?.SetTag("request.content", requestContent); - activity?.SetTag("request.length", requestContent.Length.ToString()); + activity?.SetTag("request.length", requestContent.Length); var echo = Encoding.UTF8.GetBytes("echo: " + requestContent); context.Response.ContentEncoding = Encoding.UTF8; @@ -88,7 +88,7 @@ public void Dispose() } } - private class SampleClient : IDisposable + private sealed class SampleClient : IDisposable { private CancellationTokenSource? cts; private Task? requestTask; @@ -114,7 +114,7 @@ public void Start(string url) count++; activity?.AddEvent(new ActivityEvent("PostAsync:Started")); - using var response = await client.PostAsync(url, content, cancellationToken).ConfigureAwait(false); + using var response = await client.PostAsync(new Uri(url, UriKind.Absolute), content, cancellationToken).ConfigureAwait(false); activity?.AddEvent(new ActivityEvent("PostAsync:Ended")); activity?.SetTag("http.status_code", (int)response.StatusCode); diff --git a/examples/Console/Program.cs b/examples/Console/Program.cs index bca2e4339c7..a7d0e822f47 100644 --- a/examples/Console/Program.cs +++ b/examples/Console/Program.cs @@ -8,7 +8,7 @@ namespace Examples.Console; /// /// Main samples entry point. /// -public class Program +internal static class Program { /// /// Main method - invoke this using command line. @@ -51,21 +51,21 @@ public static void Main(string[] args) #pragma warning disable SA1402 // File may only contain a single type [Verb("zipkin", HelpText = "Specify the options required to test Zipkin exporter")] -internal class ZipkinOptions +internal sealed class ZipkinOptions { [Option('u', "uri", HelpText = "Please specify the uri of Zipkin backend", Required = true)] public required string Uri { get; set; } } [Verb("prometheus", HelpText = "Specify the options required to test Prometheus")] -internal class PrometheusOptions +internal sealed class PrometheusOptions { [Option('p', "port", Default = 9464, HelpText = "The port to expose metrics. The endpoint will be http://localhost:port/metrics/ (this is the port from which your Prometheus server scraps metrics from.)", Required = false)] public int Port { get; set; } } [Verb("metrics", HelpText = "Specify the options required to test Metrics")] -internal class MetricsOptions +internal sealed class MetricsOptions { [Option('d', "IsDelta", HelpText = "Export Delta metrics", Required = false, Default = false)] public bool IsDelta { get; set; } @@ -93,32 +93,32 @@ internal class MetricsOptions } [Verb("grpc", HelpText = "Specify the options required to test Grpc.Net.Client")] -internal class GrpcNetClientOptions +internal sealed class GrpcNetClientOptions { } [Verb("httpclient", HelpText = "Specify the options required to test HttpClient")] -internal class HttpClientOptions +internal sealed class HttpClientOptions { } [Verb("console", HelpText = "Specify the options required to test console exporter")] -internal class ConsoleOptions +internal sealed class ConsoleOptions { } [Verb("otelshim", HelpText = "Specify the options required to test OpenTelemetry Shim with console exporter")] -internal class OpenTelemetryShimOptions +internal sealed class OpenTelemetryShimOptions { } [Verb("opentracing", HelpText = "Specify the options required to test OpenTracing Shim with console exporter")] -internal class OpenTracingShimOptions +internal sealed class OpenTracingShimOptions { } [Verb("otlp", HelpText = "Specify the options required to test OpenTelemetry Protocol (OTLP)")] -internal class OtlpOptions +internal sealed class OtlpOptions { [Option('e', "endpoint", HelpText = "Target to which the exporter is going to send traces (default value depends on protocol).", Default = null)] public string? Endpoint { get; set; } @@ -128,7 +128,7 @@ internal class OtlpOptions } [Verb("logs", HelpText = "Specify the options required to test Logs")] -internal class LogsOptions +internal sealed class LogsOptions { [Option("useExporter", Default = "otlp", HelpText = "Options include otlp or console.", Required = false)] public string? UseExporter { get; set; } @@ -147,7 +147,7 @@ internal class LogsOptions } [Verb("inmemory", HelpText = "Specify the options required to test InMemory Exporter")] -internal class InMemoryOptions +internal sealed class InMemoryOptions { } diff --git a/examples/Console/TestConsoleExporter.cs b/examples/Console/TestConsoleExporter.cs index 7a70c0ef1e4..fa59c3a9954 100644 --- a/examples/Console/TestConsoleExporter.cs +++ b/examples/Console/TestConsoleExporter.cs @@ -8,7 +8,7 @@ namespace Examples.Console; -internal class TestConsoleExporter +internal sealed class TestConsoleExporter { // To run this example, run the following command from // the reporoot\examples\Console\. @@ -27,21 +27,21 @@ private static int RunWithActivitySource() using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource("Samples.SampleClient", "Samples.SampleServer") .ConfigureResource(res => res.AddService("console-test")) +#pragma warning disable CA2000 // Dispose objects before losing scope .AddProcessor(new MyProcessor()) // This must be added before ConsoleExporter +#pragma warning restore CA2000 // Dispose objects before losing scope .AddConsoleExporter() .Build(); // The above line is required only in applications // which decide to use OpenTelemetry. - using (var sample = new InstrumentationWithActivitySource()) - { - sample.Start(); + using var sample = new InstrumentationWithActivitySource(); + sample.Start(); - System.Console.WriteLine("Traces are being created and exported " + - "to Console in the background. " + - "Press ENTER to stop."); - System.Console.ReadLine(); - } + System.Console.WriteLine("Traces are being created and exported " + + "to Console in the background. " + + "Press ENTER to stop."); + System.Console.ReadLine(); return 0; } @@ -50,7 +50,7 @@ private static int RunWithActivitySource() /// An example of custom processor which /// can be used to add more tags to an activity. /// - internal class MyProcessor : BaseProcessor + internal sealed class MyProcessor : BaseProcessor { public override void OnStart(Activity activity) { diff --git a/examples/Console/TestGrpcNetClient.cs b/examples/Console/TestGrpcNetClient.cs index 31dce4a1679..ab2e0378a6b 100644 --- a/examples/Console/TestGrpcNetClient.cs +++ b/examples/Console/TestGrpcNetClient.cs @@ -10,7 +10,7 @@ namespace Examples.Console; -internal class TestGrpcNetClient +internal sealed class TestGrpcNetClient { internal static int Run(GrpcNetClientOptions options) { diff --git a/examples/Console/TestHttpClient.cs b/examples/Console/TestHttpClient.cs index 7629a622af7..5c72029378c 100644 --- a/examples/Console/TestHttpClient.cs +++ b/examples/Console/TestHttpClient.cs @@ -8,7 +8,7 @@ namespace Examples.Console; -internal class TestHttpClient +internal sealed class TestHttpClient { // To run this example, run the following command from // the reporoot\examples\Console\. @@ -32,7 +32,7 @@ internal static int Run(HttpClientOptions options) using (var parent = source.StartActivity("incoming request", ActivityKind.Server)) { using var client = new HttpClient(); - client.GetStringAsync("http://bing.com").GetAwaiter().GetResult(); + client.GetStringAsync(new Uri("http://bing.com", UriKind.Absolute)).GetAwaiter().GetResult(); } System.Console.WriteLine("Press Enter key to exit."); diff --git a/examples/Console/TestInMemoryExporter.cs b/examples/Console/TestInMemoryExporter.cs index 057bc19cdec..d3b30db60ff 100644 --- a/examples/Console/TestInMemoryExporter.cs +++ b/examples/Console/TestInMemoryExporter.cs @@ -8,7 +8,7 @@ namespace Examples.Console; -internal class TestInMemoryExporter +internal sealed class TestInMemoryExporter { // To run this example, run the following command from // the reporoot\examples\Console\. diff --git a/examples/Console/TestLogs.cs b/examples/Console/TestLogs.cs index 427193799ac..b38cc230217 100644 --- a/examples/Console/TestLogs.cs +++ b/examples/Console/TestLogs.cs @@ -7,7 +7,7 @@ namespace Examples.Console; -internal class TestLogs +internal sealed class TestLogs { internal static int Run(LogsOptions options) { @@ -28,10 +28,10 @@ internal static int Run(LogsOptions options) * launch the OpenTelemetry Collector with an OTLP receiver, by running: * * - On Unix based systems use: - * docker run --rm -it -p 4317:4317 -p 4318:4318 -v $(pwd):/cfg otel/opentelemetry-collector:0.48.0 --config=/cfg/otlp-collector-example/config.yaml + * docker run --rm -it -p 4317:4317 -p 4318:4318 -v $(pwd):/cfg otel/opentelemetry-collector:0.123.0 --config=/cfg/otlp-collector-example/config.yaml * * - On Windows use: - * docker run --rm -it -p 4317:4317 -p 4318:4318 -v "%cd%":/cfg otel/opentelemetry-collector:0.48.0 --config=/cfg/otlp-collector-example/config.yaml + * docker run --rm -it -p 4317:4317 -p 4318:4318 -v "%cd%":/cfg otel/opentelemetry-collector:0.123.0 --config=/cfg/otlp-collector-example/config.yaml * * Open another terminal window at the examples/Console/ directory and * launch the OTLP example by running: @@ -112,11 +112,11 @@ internal static int Run(LogsOptions options) }); }); - var logger = loggerFactory.CreateLogger(); - using (logger.BeginScope("{city}", "Seattle")) - using (logger.BeginScope("{storeType}", "Physical")) + var logger = loggerFactory.CreateLogger(); + using (logger.BeginCityScope("Seattle")) + using (logger.BeginStoreTypeScope("Physical")) { - logger.LogInformation("Hello from {name} {price}.", "tomato", 2.99); + logger.HelloFrom("tomato", 2.99); } return 0; diff --git a/examples/Console/TestLogsExtensions.cs b/examples/Console/TestLogsExtensions.cs new file mode 100644 index 00000000000..f76eb03b3c8 --- /dev/null +++ b/examples/Console/TestLogsExtensions.cs @@ -0,0 +1,19 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.Logging; + +namespace Examples.Console; + +internal static partial class TestLogsExtensions +{ + private static readonly Func CityScope = LoggerMessage.DefineScope("{City}"); + private static readonly Func StoreTypeScope = LoggerMessage.DefineScope("{StoreType}"); + + public static IDisposable? BeginCityScope(this ILogger logger, string city) => CityScope(logger, city); + + public static IDisposable? BeginStoreTypeScope(this ILogger logger, string storeType) => StoreTypeScope(logger, storeType); + + [LoggerMessage(LogLevel.Information, "Hello from {Name} {Price}.")] + public static partial void HelloFrom(this ILogger logger, string name, double price); +} diff --git a/examples/Console/TestMetrics.cs b/examples/Console/TestMetrics.cs index 9582a0f561e..308acee8d4c 100644 --- a/examples/Console/TestMetrics.cs +++ b/examples/Console/TestMetrics.cs @@ -10,7 +10,7 @@ namespace Examples.Console; -internal class TestMetrics +internal sealed class TestMetrics { internal static int Run(MetricsOptions options) { @@ -37,10 +37,10 @@ internal static int Run(MetricsOptions options) * launch the OpenTelemetry Collector with an OTLP receiver, by running: * * - On Unix based systems use: - * docker run --rm -it -p 4317:4317 -v $(pwd):/cfg otel/opentelemetry-collector:0.33.0 --config=/cfg/otlp-collector-example/config.yaml + * docker run --rm -it -p 4317:4317 -v $(pwd):/cfg otel/opentelemetry-collector:0.123.0 --config=/cfg/otlp-collector-example/config.yaml * * - On Windows use: - * docker run --rm -it -p 4317:4317 -v "%cd%":/cfg otel/opentelemetry-collector:0.33.0 --config=/cfg/otlp-collector-example/config.yaml + * docker run --rm -it -p 4317:4317 -v "%cd%":/cfg otel/opentelemetry-collector:0.123.0 --config=/cfg/otlp-collector-example/config.yaml * * Open another terminal window at the examples/Console/ directory and * launch the OTLP example by running: @@ -97,7 +97,7 @@ internal static int Run(MetricsOptions options) { return new List>() { - new Measurement( + new( (int)Process.GetCurrentProcess().PrivateMemorySize64, new KeyValuePair("tag1", "value1")), }; diff --git a/examples/Console/TestOTelShimWithConsoleExporter.cs b/examples/Console/TestOTelShimWithConsoleExporter.cs index 42e6f368f54..4dbb22ff9db 100644 --- a/examples/Console/TestOTelShimWithConsoleExporter.cs +++ b/examples/Console/TestOTelShimWithConsoleExporter.cs @@ -7,7 +7,7 @@ namespace Examples.Console; -internal class TestOTelShimWithConsoleExporter +internal sealed class TestOTelShimWithConsoleExporter { internal static int Run(OpenTelemetryShimOptions options) { diff --git a/examples/Console/TestOpenTracingShim.cs b/examples/Console/TestOpenTracingShim.cs index 386cfe48ab1..77ab8d6c7d0 100644 --- a/examples/Console/TestOpenTracingShim.cs +++ b/examples/Console/TestOpenTracingShim.cs @@ -10,7 +10,7 @@ namespace Examples.Console; -internal class TestOpenTracingShim +internal sealed class TestOpenTracingShim { internal static int Run(OpenTracingShimOptions options) { diff --git a/examples/Console/TestOtlpExporter.cs b/examples/Console/TestOtlpExporter.cs index 3af0ceea14b..c38ac518a18 100644 --- a/examples/Console/TestOtlpExporter.cs +++ b/examples/Console/TestOtlpExporter.cs @@ -20,10 +20,10 @@ internal static int Run(OtlpOptions options) * launch the OpenTelemetry Collector with an OTLP receiver, by running: * * - On Unix based systems use: - * docker run --rm -it -p 4317:4317 -p 4318:4318 -v $(pwd):/cfg otel/opentelemetry-collector:latest --config=/cfg/otlp-collector-example/config.yaml + * docker run --rm -it -p 4317:4317 -p 4318:4318 -v $(pwd):/cfg otel/opentelemetry-collector:0.123.0 --config=/cfg/otlp-collector-example/config.yaml * * - On Windows use: - * docker run --rm -it -p 4317:4317 -p 4318:4318 -v "%cd%":/cfg otel/opentelemetry-collector:latest --config=/cfg/otlp-collector-example/config.yaml + * docker run --rm -it -p 4317:4317 -p 4318:4318 -v "%cd%":/cfg otel/opentelemetry-collector:0.123.0 --config=/cfg/otlp-collector-example/config.yaml * * Open another terminal window at the examples/Console/ directory and * launch the OTLP example by running: @@ -69,24 +69,22 @@ private static int RunWithActivitySource(OtlpOptions options) // The above line is required only in Applications // which decide to use OpenTelemetry. - using (var sample = new InstrumentationWithActivitySource()) - { - sample.Start(); + using var sample = new InstrumentationWithActivitySource(); + sample.Start(); - System.Console.WriteLine("Traces are being created and exported " + - "to the OpenTelemetry Collector in the background. " + - "Press ENTER to stop."); - System.Console.ReadLine(); - } + System.Console.WriteLine("Traces are being created and exported " + + "to the OpenTelemetry Collector in the background. " + + "Press ENTER to stop."); + System.Console.ReadLine(); return 0; } private static OtlpExportProtocol? ToOtlpExportProtocol(string? protocol) => - protocol?.Trim().ToLower() switch + protocol?.Trim().ToUpperInvariant() switch { - "grpc" => OtlpExportProtocol.Grpc, - "http/protobuf" => OtlpExportProtocol.HttpProtobuf, + "GRPC" => OtlpExportProtocol.Grpc, + "HTTP/PROTOBUF" => OtlpExportProtocol.HttpProtobuf, _ => null, }; } diff --git a/examples/Console/TestPrometheusExporter.cs b/examples/Console/TestPrometheusExporter.cs index a6bc1888f2b..3dc9ca0d89d 100644 --- a/examples/Console/TestPrometheusExporter.cs +++ b/examples/Console/TestPrometheusExporter.cs @@ -3,19 +3,18 @@ using System.Diagnostics; using System.Diagnostics.Metrics; +using System.Security.Cryptography; using OpenTelemetry; using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; namespace Examples.Console; -internal class TestPrometheusExporter +internal sealed class TestPrometheusExporter { private static readonly Meter MyMeter = new("MyMeter"); private static readonly Meter MyMeter2 = new("MyMeter2"); private static readonly Counter Counter = MyMeter.CreateCounter("myCounter", description: "A counter for demonstration purpose."); private static readonly Histogram MyHistogram = MyMeter.CreateHistogram("myHistogram"); - private static readonly ThreadLocal ThreadLocalRandom = new(() => new Random()); internal static int Run(PrometheusOptions options) { @@ -35,7 +34,7 @@ internal static int Run(PrometheusOptions options) .AddMeter(MyMeter.Name) .AddMeter(MyMeter2.Name) .AddPrometheusHttpListener( - o => o.UriPrefixes = new string[] { $"http://localhost:{options.Port}/" }) + o => o.UriPrefixes = [$"http://localhost:{options.Port}/"]) .Build(); var process = Process.GetCurrentProcess(); @@ -57,7 +56,7 @@ internal static int Run(PrometheusOptions options) { Counter.Add(9.9, new("name", "apple"), new("color", "red")); Counter.Add(99.9, new("name", "lemon"), new("color", "yellow")); - MyHistogram.Record(ThreadLocalRandom.Value!.Next(1, 1500), new("tag1", "value1"), new("tag2", "value2")); + MyHistogram.Record(RandomNumberGenerator.GetInt32(1, 1500), new("tag1", "value1"), new("tag2", "value2")); Task.Delay(10).Wait(); } }); diff --git a/examples/Console/TestZipkinExporter.cs b/examples/Console/TestZipkinExporter.cs index 0bb64323442..d7d579270f9 100644 --- a/examples/Console/TestZipkinExporter.cs +++ b/examples/Console/TestZipkinExporter.cs @@ -7,7 +7,7 @@ namespace Examples.Console; -internal class TestZipkinExporter +internal sealed class TestZipkinExporter { internal static int Run(ZipkinOptions options) { @@ -33,15 +33,13 @@ internal static int Run(ZipkinOptions options) }) .Build(); - using (var sample = new InstrumentationWithActivitySource()) - { - sample.Start(); + using var sample = new InstrumentationWithActivitySource(); + sample.Start(); - System.Console.WriteLine("Traces are being created and exported " + - "to Zipkin in the background. Use Zipkin to view them. " + - "Press ENTER to stop."); - System.Console.ReadLine(); - } + System.Console.WriteLine("Traces are being created and exported " + + "to Zipkin in the background. Use Zipkin to view them. " + + "Press ENTER to stop."); + System.Console.ReadLine(); return 0; } diff --git a/examples/Directory.Build.props b/examples/Directory.Build.props index 032e1e27ec9..4c709d51681 100644 --- a/examples/Directory.Build.props +++ b/examples/Directory.Build.props @@ -1,3 +1,4 @@ + diff --git a/examples/GrpcService/Program.cs b/examples/GrpcService/Program.cs index 5ed8ceb4c34..5f80792c640 100644 --- a/examples/GrpcService/Program.cs +++ b/examples/GrpcService/Program.cs @@ -1,22 +1,58 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -namespace Examples.GrpcService; +using Examples.GrpcService; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; -public class Program -{ - public static void Main(string[] args) +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddGrpc(); + +builder.Services.AddOpenTelemetry() + .WithTracing(tracerBuilder => { - CreateHostBuilder(args).Build().Run(); - } - - // Additional configuration is required to successfully run gRPC on macOS. - // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682 - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - webBuilder.UseUrls("https://localhost:44335"); - }); + tracerBuilder.ConfigureResource(r => r.AddService( + builder.Configuration.GetValue("ServiceName", defaultValue: "otel-test"))) + .AddAspNetCoreInstrumentation(); + + var exporter = builder.Configuration.GetValue("UseExporter", defaultValue: "console").ToUpperInvariant(); + switch (exporter) + { + case "OTLP": + tracerBuilder.AddOtlpExporter(otlpOptions => + { + otlpOptions.Endpoint = new Uri(builder.Configuration.GetValue("Otlp:Endpoint", defaultValue: "http://localhost:4317")); + }); + break; + case "ZIPKIN": + tracerBuilder.AddZipkinExporter(zipkinOptions => + { + zipkinOptions.Endpoint = new Uri(builder.Configuration.GetValue("Zipkin:Endpoint", defaultValue: "http://localhost:9411/api/v2/spans")); + }); + break; + default: + tracerBuilder.AddConsoleExporter(); + break; + } + }); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); } + +app.UseRouting(); + +app.MapGrpcService(); +app.MapGet("/", () => +{ + return Results.Text("Communication with gRPC endpoints must be made through a gRPC client. " + + "To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); +}); + +app.Run(); diff --git a/examples/GrpcService/Services/GreeterService.cs b/examples/GrpcService/Services/GreeterService.cs index 011439970fb..9963b7aeb35 100644 --- a/examples/GrpcService/Services/GreeterService.cs +++ b/examples/GrpcService/Services/GreeterService.cs @@ -5,15 +5,10 @@ namespace Examples.GrpcService; -public class GreeterService : Greeter.GreeterBase +#pragma warning disable CA1812 // Avoid uninstantiated internal classes +internal sealed class GreeterService : Greeter.GreeterBase +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { - private readonly ILogger logger; - - public GreeterService(ILogger logger) - { - this.logger = logger; - } - public override Task SayHello(HelloRequest request, ServerCallContext context) { return Task.FromResult(new HelloReply diff --git a/examples/GrpcService/Startup.cs b/examples/GrpcService/Startup.cs deleted file mode 100644 index d23af73550f..00000000000 --- a/examples/GrpcService/Startup.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using OpenTelemetry.Resources; -using OpenTelemetry.Trace; - -namespace Examples.GrpcService; - -public class Startup -{ - public Startup(IConfiguration configuration) - { - this.Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public void ConfigureServices(IServiceCollection services) - { - services.AddGrpc(); - - services.AddOpenTelemetry() - .WithTracing(builder => - { - builder - .ConfigureResource(r => r.AddService(this.Configuration.GetValue("ServiceName", defaultValue: "otel-test")!)) - .AddAspNetCoreInstrumentation(); - - // Switch between Otlp/Zipkin/Console by setting UseExporter in appsettings.json. - var exporter = this.Configuration.GetValue("UseExporter", defaultValue: "console")!.ToLowerInvariant(); - switch (exporter) - { - case "otlp": - builder.AddOtlpExporter(otlpOptions => - { - otlpOptions.Endpoint = new Uri(this.Configuration.GetValue("Otlp:Endpoint", defaultValue: "http://localhost:4317")!); - }); - break; - case "zipkin": - builder.AddZipkinExporter(zipkinOptions => - { - zipkinOptions.Endpoint = new Uri(this.Configuration.GetValue("Zipkin:Endpoint", defaultValue: "http://localhost:9411/api/v2/spans")!); - }); - break; - default: - builder.AddConsoleExporter(); - break; - } - }); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapGrpcService(); - - endpoints.MapGet("/", async context => - { - await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909").ConfigureAwait(false); - }); - }); - } -} diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs index 09de208df21..e3137d987a4 100644 --- a/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs +++ b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Text; using Microsoft.Extensions.Logging; using OpenTelemetry; @@ -11,35 +12,39 @@ namespace Utils.Messaging; -public class MessageReceiver : IDisposable +public sealed class MessageReceiver : IDisposable { private static readonly ActivitySource ActivitySource = new(nameof(MessageReceiver)); private static readonly TextMapPropagator Propagator = Propagators.DefaultTextMapPropagator; private readonly ILogger logger; - private readonly IConnection connection; - private readonly IModel channel; + private IConnection? connection; + private IChannel? channel; public MessageReceiver(ILogger logger) { this.logger = logger; - this.connection = RabbitMqHelper.CreateConnection(); - this.channel = RabbitMqHelper.CreateModelAndDeclareTestQueue(this.connection); } public void Dispose() { - this.channel.Dispose(); - this.connection.Dispose(); + this.channel?.Dispose(); + this.connection?.Dispose(); } - public void StartConsumer() + public async Task StartConsumerAsync() { - RabbitMqHelper.StartConsumer(this.channel, this.ReceiveMessage); + this.connection = await RabbitMqHelper.CreateConnectionAsync().ConfigureAwait(false); + this.channel = await RabbitMqHelper.CreateModelAndDeclareTestQueueAsync(this.connection).ConfigureAwait(false); + await RabbitMqHelper.StartConsumerAsync(this.channel, this.ReceiveMessageAsync).ConfigureAwait(false); } - public void ReceiveMessage(BasicDeliverEventArgs ea) + public async Task ReceiveMessageAsync(BasicDeliverEventArgs ea) { + this.EnsureStarted(); + + ArgumentNullException.ThrowIfNull(ea); + // Extract the PropagationContext of the upstream parent from the message headers. var parentContext = Propagator.Extract(default, ea.BasicProperties, this.ExtractTraceContextFromBasicProperties); Baggage.Current = parentContext.Baggage; @@ -53,7 +58,7 @@ public void ReceiveMessage(BasicDeliverEventArgs ea) { var message = Encoding.UTF8.GetString(ea.Body.Span.ToArray()); - this.logger.LogInformation($"Message received: [{message}]"); + this.logger.MessageReceived(message); activity?.SetTag("message", message); @@ -61,29 +66,38 @@ public void ReceiveMessage(BasicDeliverEventArgs ea) RabbitMqHelper.AddMessagingTags(activity); // Simulate some work - Thread.Sleep(1000); + await Task.Delay(1_000).ConfigureAwait(false); } catch (Exception ex) { - this.logger.LogError(ex, "Message processing failed."); + this.logger.MessageProcessingFailed(ex); } } - private IEnumerable ExtractTraceContextFromBasicProperties(IBasicProperties props, string key) + private IEnumerable ExtractTraceContextFromBasicProperties(IReadOnlyBasicProperties props, string key) { try { - if (props.Headers.TryGetValue(key, out var value)) + if (props.Headers?.TryGetValue(key, out var value) is true) { - var bytes = value as byte[]; - return new[] { Encoding.UTF8.GetString(bytes) }; + var bytes = (byte[])value!; + return [Encoding.UTF8.GetString(bytes)]; } } catch (Exception ex) { - this.logger.LogError(ex, "Failed to extract trace context."); + this.logger.FailedToExtractTraceContext(ex); } - return Enumerable.Empty(); + return []; + } + + [MemberNotNull(nameof(channel), nameof(connection))] + private void EnsureStarted() + { + if (this.channel == null || this.connection == null) + { + throw new InvalidOperationException("The message receiver has not been started."); + } } } diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageReceiverLog.cs b/examples/MicroserviceExample/Utils/Messaging/MessageReceiverLog.cs new file mode 100644 index 00000000000..95a3e69a93a --- /dev/null +++ b/examples/MicroserviceExample/Utils/Messaging/MessageReceiverLog.cs @@ -0,0 +1,18 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.Logging; + +namespace Utils.Messaging; + +internal static partial class MessageReceiverLog +{ + [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "Message received: [{Message}]")] + public static partial void MessageReceived(this ILogger logger, string message); + + [LoggerMessage(EventId = 2, Level = LogLevel.Error, Message = "Message processing failed.")] + public static partial void MessageProcessingFailed(this ILogger logger, Exception exception); + + [LoggerMessage(EventId = 3, Level = LogLevel.Error, Message = "Failed to extract trace context.")] + public static partial void FailedToExtractTraceContext(this ILogger logger, Exception exception); +} diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs b/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs index 969cf279cb4..e978cf01f3e 100644 --- a/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs +++ b/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs @@ -10,38 +10,42 @@ namespace Utils.Messaging; -public class MessageSender : IDisposable +public sealed class MessageSender : IDisposable { private static readonly ActivitySource ActivitySource = new(nameof(MessageSender)); private static readonly TextMapPropagator Propagator = Propagators.DefaultTextMapPropagator; private readonly ILogger logger; - private readonly IConnection connection; - private readonly IModel channel; + private IConnection? connection; + private IChannel? channel; public MessageSender(ILogger logger) { this.logger = logger; - this.connection = RabbitMqHelper.CreateConnection(); - this.channel = RabbitMqHelper.CreateModelAndDeclareTestQueue(this.connection); } public void Dispose() { - this.channel.Dispose(); - this.connection.Dispose(); + this.channel?.Dispose(); + this.connection?.Dispose(); } - public string SendMessage() + public async Task SendMessageAsync() { try { + if (this.channel is null) + { + this.connection = await RabbitMqHelper.CreateConnectionAsync().ConfigureAwait(false); + this.channel = await RabbitMqHelper.CreateModelAndDeclareTestQueueAsync(this.connection).ConfigureAwait(false); + } + // Start an activity with a name following the semantic convention of the OpenTelemetry messaging specification. // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/messaging/messaging-spans.md#span-name var activityName = $"{RabbitMqHelper.TestQueueName} send"; using var activity = ActivitySource.StartActivity(activityName, ActivityKind.Producer); - var props = this.channel.CreateBasicProperties(); + var props = new BasicProperties(); // Depending on Sampling (and whether a listener is registered or not), the // activity above may not be created. @@ -65,19 +69,20 @@ public string SendMessage() RabbitMqHelper.AddMessagingTags(activity); var body = $"Published message: DateTime.Now = {DateTime.Now}."; - this.channel.BasicPublish( + await this.channel.BasicPublishAsync( exchange: RabbitMqHelper.DefaultExchangeName, routingKey: RabbitMqHelper.TestQueueName, + mandatory: false, basicProperties: props, - body: Encoding.UTF8.GetBytes(body)); + body: Encoding.UTF8.GetBytes(body)).ConfigureAwait(false); - this.logger.LogInformation($"Message sent: [{body}]"); + this.logger.MessageSent(body); return body; } catch (Exception ex) { - this.logger.LogError(ex, "Message publishing failed."); + this.logger.MessagePublishingFailed(ex); throw; } } @@ -86,16 +91,13 @@ private void InjectTraceContextIntoBasicProperties(IBasicProperties props, strin { try { - if (props.Headers == null) - { - props.Headers = new Dictionary(); - } + props.Headers ??= new Dictionary(); props.Headers[key] = value; } catch (Exception ex) { - this.logger.LogError(ex, "Failed to inject trace context."); + this.logger.FailedToInjectTraceContext(ex); } } } diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageSenderLog.cs b/examples/MicroserviceExample/Utils/Messaging/MessageSenderLog.cs new file mode 100644 index 00000000000..f33eab31c37 --- /dev/null +++ b/examples/MicroserviceExample/Utils/Messaging/MessageSenderLog.cs @@ -0,0 +1,18 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.Logging; + +namespace Utils.Messaging; + +internal static partial class MessageSenderLog +{ + [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "Message sent: [{Body}]")] + public static partial void MessageSent(this ILogger logger, string body); + + [LoggerMessage(EventId = 2, Level = LogLevel.Error, Message = "Message publishing failed.")] + public static partial void MessagePublishingFailed(this ILogger logger, Exception exception); + + [LoggerMessage(EventId = 3, Level = LogLevel.Error, Message = "Failed to inject trace context.")] + public static partial void FailedToInjectTraceContext(this ILogger logger, Exception exception); +} diff --git a/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs b/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs index cbc8f0d8344..a1a6569abbe 100644 --- a/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs +++ b/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs @@ -12,46 +12,41 @@ public static class RabbitMqHelper public const string DefaultExchangeName = ""; public const string TestQueueName = "TestQueue"; - private static readonly ConnectionFactory ConnectionFactory; - - static RabbitMqHelper() + private static readonly ConnectionFactory ConnectionFactory = new() { - ConnectionFactory = new ConnectionFactory() - { - HostName = Environment.GetEnvironmentVariable("RABBITMQ_HOSTNAME") ?? "localhost", - UserName = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_USER") ?? "guest", - Password = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_PASS") ?? "guest", - Port = 5672, - RequestedConnectionTimeout = TimeSpan.FromMilliseconds(3000), - }; - } + HostName = Environment.GetEnvironmentVariable("RABBITMQ_HOSTNAME") ?? "localhost", + UserName = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_USER") ?? "guest", + Password = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_PASS") ?? "guest", + Port = 5672, + RequestedConnectionTimeout = TimeSpan.FromMilliseconds(3000), + }; - public static IConnection CreateConnection() - { - return ConnectionFactory.CreateConnection(); - } + public static async Task CreateConnectionAsync() => + await ConnectionFactory.CreateConnectionAsync().ConfigureAwait(false); - public static IModel CreateModelAndDeclareTestQueue(IConnection connection) + public static async Task CreateModelAndDeclareTestQueueAsync(IConnection connection) { - var channel = connection.CreateModel(); + ArgumentNullException.ThrowIfNull(connection); + + var channel = await connection.CreateChannelAsync().ConfigureAwait(false); - channel.QueueDeclare( + await channel.QueueDeclareAsync( queue: TestQueueName, durable: false, exclusive: false, autoDelete: false, - arguments: null); + arguments: null).ConfigureAwait(false); return channel; } - public static void StartConsumer(IModel channel, Action processMessage) + public static async Task StartConsumerAsync(IChannel channel, Func processMessage) { - var consumer = new EventingBasicConsumer(channel); + var consumer = new AsyncEventingBasicConsumer(channel); - consumer.Received += (bc, ea) => processMessage(ea); + consumer.ReceivedAsync += async (bc, ea) => await processMessage(ea).ConfigureAwait(false); - channel.BasicConsume(queue: TestQueueName, autoAck: true, consumer: consumer); + await channel.BasicConsumeAsync(queue: TestQueueName, autoAck: true, consumer: consumer).ConfigureAwait(false); } public static void AddMessagingTags(Activity? activity) diff --git a/examples/MicroserviceExample/Utils/Utils.csproj b/examples/MicroserviceExample/Utils/Utils.csproj index 54d07243470..1c95380e4c9 100644 --- a/examples/MicroserviceExample/Utils/Utils.csproj +++ b/examples/MicroserviceExample/Utils/Utils.csproj @@ -1,6 +1,6 @@ - netstandard2.0 + $(DefaultTargetFrameworkForExampleApps) diff --git a/examples/MicroserviceExample/WebApi/Controllers/SendMessageController.cs b/examples/MicroserviceExample/WebApi/Controllers/SendMessageController.cs index 6a8a5f9564a..e08009c5f8e 100644 --- a/examples/MicroserviceExample/WebApi/Controllers/SendMessageController.cs +++ b/examples/MicroserviceExample/WebApi/Controllers/SendMessageController.cs @@ -10,18 +10,14 @@ namespace WebApi.Controllers; [Route("[controller]")] public class SendMessageController : ControllerBase { - private readonly ILogger logger; private readonly MessageSender messageSender; - public SendMessageController(ILogger logger, MessageSender messageSender) + public SendMessageController(MessageSender messageSender) { - this.logger = logger; this.messageSender = messageSender; } [HttpGet] - public string Get() - { - return this.messageSender.SendMessage(); - } + public async Task Get() => + await this.messageSender.SendMessageAsync().ConfigureAwait(false); } diff --git a/examples/MicroserviceExample/WebApi/Dockerfile b/examples/MicroserviceExample/WebApi/Dockerfile index d74077a0a87..a11bc474dcf 100644 --- a/examples/MicroserviceExample/WebApi/Dockerfile +++ b/examples/MicroserviceExample/WebApi/Dockerfile @@ -1,12 +1,18 @@ -ARG SDK_VERSION=8.0 -FROM mcr.microsoft.com/dotnet/sdk:${SDK_VERSION} AS build +ARG SDK_VERSION=9.0 +FROM mcr.microsoft.com/dotnet/sdk:8.0.414@sha256:3cef19377b2ef2a0171e930a24627677447c3e41b5f2eab84ff4895f1b15d254 AS dotnet-sdk-8.0 +FROM mcr.microsoft.com/dotnet/sdk:9.0.305@sha256:bb42ae2c058609d1746baf24fe6864ecab0686711dfca1f4b7a99e367ab17162 AS dotnet-sdk-9.0 + +FROM dotnet-sdk-${SDK_VERSION} AS build ARG PUBLISH_CONFIGURATION=Release -ARG PUBLISH_FRAMEWORK=net8.0 +ARG PUBLISH_FRAMEWORK=net9.0 WORKDIR /app COPY . ./ RUN dotnet publish ./examples/MicroserviceExample/WebApi -c "${PUBLISH_CONFIGURATION}" -f "${PUBLISH_FRAMEWORK}" -o /out -p:IntegrationBuild=true -FROM mcr.microsoft.com/dotnet/aspnet:${SDK_VERSION} AS runtime +FROM mcr.microsoft.com/dotnet/aspnet:8.0.20@sha256:e88f90b6d9fd7e9e0d8e231d068fccdbebd3c91892441a85ef35066aea9a4e1e AS dotnet-aspnet-8.0 +FROM mcr.microsoft.com/dotnet/aspnet:9.0.9@sha256:1af4114db9ba87542a3f23dbb5cd9072cad7fcc8505f6e9131d1feb580286a6f AS dotnet-aspnet-9.0 + +FROM dotnet-aspnet-${SDK_VERSION} AS runtime WORKDIR /app COPY --from=build /out ./ ENTRYPOINT ["dotnet", "WebApi.dll"] diff --git a/examples/MicroserviceExample/WebApi/Program.cs b/examples/MicroserviceExample/WebApi/Program.cs index 9ed5a0b9c01..2dbc341f6d4 100644 --- a/examples/MicroserviceExample/WebApi/Program.cs +++ b/examples/MicroserviceExample/WebApi/Program.cs @@ -1,19 +1,36 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -namespace WebApi; +using OpenTelemetry.Trace; +using Utils.Messaging; -public class Program +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); + +builder.Services.AddSingleton(); + +builder.Services.AddOpenTelemetry() + .WithTracing(b => b + .AddAspNetCoreInstrumentation() + .AddSource(nameof(MessageSender)) + .AddZipkinExporter(o => + { + var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; + o.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); + })); + +builder.WebHost.UseUrls("http://*:5000"); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseUrls("http://*:5000").UseStartup(); - }); + app.UseDeveloperExceptionPage(); } + +app.UseRouting(); + +app.MapControllers(); + +app.Run(); diff --git a/examples/MicroserviceExample/WebApi/Startup.cs b/examples/MicroserviceExample/WebApi/Startup.cs deleted file mode 100644 index b77ea597c73..00000000000 --- a/examples/MicroserviceExample/WebApi/Startup.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using OpenTelemetry.Trace; -using Utils.Messaging; - -namespace WebApi; - -public class Startup -{ - public Startup(IConfiguration configuration) - { - this.Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - - services.AddSingleton(); - - services.AddOpenTelemetry() - .WithTracing(builder => builder - .AddAspNetCoreInstrumentation() - .AddSource(nameof(MessageSender)) - .AddZipkinExporter(b => - { - var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; - b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); - })); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - } -} diff --git a/examples/MicroserviceExample/WebApi/WebApi.csproj b/examples/MicroserviceExample/WebApi/WebApi.csproj index 2d5846e580f..9c7ae7cf936 100644 --- a/examples/MicroserviceExample/WebApi/WebApi.csproj +++ b/examples/MicroserviceExample/WebApi/WebApi.csproj @@ -1,11 +1,11 @@ - + $(DefaultTargetFrameworkForExampleApps) + $(NoWarn);CA1515 - diff --git a/examples/MicroserviceExample/WorkerService/Dockerfile b/examples/MicroserviceExample/WorkerService/Dockerfile index dafc0049b46..07dde87f8b9 100644 --- a/examples/MicroserviceExample/WorkerService/Dockerfile +++ b/examples/MicroserviceExample/WorkerService/Dockerfile @@ -1,12 +1,19 @@ -ARG SDK_VERSION=8.0 -FROM mcr.microsoft.com/dotnet/sdk:${SDK_VERSION} AS build +ARG SDK_VERSION=9.0 + +FROM mcr.microsoft.com/dotnet/sdk:8.0.414@sha256:3cef19377b2ef2a0171e930a24627677447c3e41b5f2eab84ff4895f1b15d254 AS dotnet-sdk-8.0 +FROM mcr.microsoft.com/dotnet/sdk:9.0.305@sha256:bb42ae2c058609d1746baf24fe6864ecab0686711dfca1f4b7a99e367ab17162 AS dotnet-sdk-9.0 + +FROM dotnet-sdk-${SDK_VERSION} AS build ARG PUBLISH_CONFIGURATION=Release -ARG PUBLISH_FRAMEWORK=net8.0 +ARG PUBLISH_FRAMEWORK=net9.0 WORKDIR /app COPY . ./ RUN dotnet publish ./examples/MicroserviceExample/WorkerService -c "${PUBLISH_CONFIGURATION}" -f "${PUBLISH_FRAMEWORK}" -o /out -p:IntegrationBuild=true -FROM mcr.microsoft.com/dotnet/aspnet:${SDK_VERSION} AS runtime +FROM mcr.microsoft.com/dotnet/aspnet:8.0.20@sha256:e88f90b6d9fd7e9e0d8e231d068fccdbebd3c91892441a85ef35066aea9a4e1e AS dotnet-aspnet-8.0 +FROM mcr.microsoft.com/dotnet/aspnet:9.0.9@sha256:1af4114db9ba87542a3f23dbb5cd9072cad7fcc8505f6e9131d1feb580286a6f AS dotnet-aspnet-9.0 + +FROM dotnet-aspnet-${SDK_VERSION} AS runtime WORKDIR /app COPY --from=build /out ./ ENTRYPOINT ["dotnet", "WorkerService.dll"] diff --git a/examples/MicroserviceExample/WorkerService/Program.cs b/examples/MicroserviceExample/WorkerService/Program.cs index 24e03322c12..118484d827b 100644 --- a/examples/MicroserviceExample/WorkerService/Program.cs +++ b/examples/MicroserviceExample/WorkerService/Program.cs @@ -3,31 +3,22 @@ using OpenTelemetry.Trace; using Utils.Messaging; +using WorkerService; -namespace WorkerService; +var builder = Host.CreateApplicationBuilder(args); -public class Program -{ - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } +builder.Services.AddHostedService(); +builder.Services.AddSingleton(); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureServices((hostContext, services) => - { - services.AddHostedService(); +builder.Services.AddOpenTelemetry() + .WithTracing(b => b + .AddSource(nameof(MessageReceiver)) + .AddZipkinExporter(o => + { + var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; + o.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); + })); - services.AddSingleton(); +var app = builder.Build(); - services.AddOpenTelemetry() - .WithTracing(builder => builder - .AddSource(nameof(MessageReceiver)) - .AddZipkinExporter(b => - { - var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; - b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); - })); - }); -} +app.Run(); diff --git a/examples/MicroserviceExample/WorkerService/Worker.cs b/examples/MicroserviceExample/WorkerService/Worker.cs index 9b3fa484d19..1292076399b 100644 --- a/examples/MicroserviceExample/WorkerService/Worker.cs +++ b/examples/MicroserviceExample/WorkerService/Worker.cs @@ -5,7 +5,7 @@ namespace WorkerService; -public partial class Worker : BackgroundService +internal sealed class Worker : BackgroundService { private readonly MessageReceiver messageReceiver; @@ -14,22 +14,10 @@ public Worker(MessageReceiver messageReceiver) this.messageReceiver = messageReceiver; } - public override Task StartAsync(CancellationToken cancellationToken) - { - return base.StartAsync(cancellationToken); - } - - public override async Task StopAsync(CancellationToken cancellationToken) - { - await base.StopAsync(cancellationToken).ConfigureAwait(false); - } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) { stoppingToken.ThrowIfCancellationRequested(); - this.messageReceiver.StartConsumer(); - - await Task.CompletedTask.ConfigureAwait(false); + await this.messageReceiver.StartConsumerAsync().ConfigureAwait(false); } } diff --git a/examples/MicroserviceExample/WorkerService/WorkerService.csproj b/examples/MicroserviceExample/WorkerService/WorkerService.csproj index b9b1a680772..a1dc2dae9f4 100644 --- a/examples/MicroserviceExample/WorkerService/WorkerService.csproj +++ b/examples/MicroserviceExample/WorkerService/WorkerService.csproj @@ -1,15 +1,14 @@ $(DefaultTargetFrameworkForExampleApps) + $(NoWarn);CA1812 - - diff --git a/examples/MicroserviceExample/docker-compose.yml b/examples/MicroserviceExample/docker-compose.yml index 35385fae591..ebbe2bbf345 100644 --- a/examples/MicroserviceExample/docker-compose.yml +++ b/examples/MicroserviceExample/docker-compose.yml @@ -1,13 +1,11 @@ -version: '3.8' - services: zipkin: - image: openzipkin/zipkin + image: openzipkin/zipkin:3.5.1@sha256:bb570eb45c2994eaf32da783cc098b3d51d1095b73ec92919863d73d0a9eaafb ports: - 9411:9411 rabbitmq: - image: rabbitmq:3-management-alpine + image: rabbitmq:4-management-alpine@sha256:603089229e6060279f1b5db7fb5d844d1f259dd7a73fca8e3b15bb93713ad27c ports: - 5672:5672 - 15672:15672 diff --git a/global.json b/global.json index 271c2d4e2c8..8c14264b61d 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { "rollForward": "latestFeature", - "version": "9.0.102" + "version": "9.0.305" } } diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 93113cdc8e2..dfe83531769 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,3 +1,4 @@ + diff --git a/src/OpenTelemetry.Api.ProviderBuilderExtensions/AssemblyInfo.cs b/src/OpenTelemetry.Api.ProviderBuilderExtensions/AssemblyInfo.cs deleted file mode 100644 index 0c163966efc..00000000000 --- a/src/OpenTelemetry.Api.ProviderBuilderExtensions/AssemblyInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("OpenTelemetry" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Api.ProviderBuilderExtensions.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Console" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Tests" + AssemblyInfo.PublicKey)] - -#if SIGNED -file static class AssemblyInfo -{ - public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; -} -#else -file static class AssemblyInfo -{ - public const string PublicKey = ""; -} -#endif diff --git a/src/OpenTelemetry.Api.ProviderBuilderExtensions/CHANGELOG.md b/src/OpenTelemetry.Api.ProviderBuilderExtensions/CHANGELOG.md index 3b751248ee4..4cad7a9486d 100644 --- a/src/OpenTelemetry.Api.ProviderBuilderExtensions/CHANGELOG.md +++ b/src/OpenTelemetry.Api.ProviderBuilderExtensions/CHANGELOG.md @@ -7,6 +7,18 @@ Notes](../../RELEASENOTES.md). ## Unreleased +## 1.13.0 + +Released 2025-Oct-01 + +## 1.12.0 + +Released 2025-Apr-29 + +## 1.11.2 + +Released 2025-Mar-04 + ## 1.11.1 Released 2025-Jan-22 diff --git a/src/OpenTelemetry.Api.ProviderBuilderExtensions/Logs/OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.cs b/src/OpenTelemetry.Api.ProviderBuilderExtensions/Logs/OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.cs index 4a96f1b0818..51a1af5c5c5 100644 --- a/src/OpenTelemetry.Api.ProviderBuilderExtensions/Logs/OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Api.ProviderBuilderExtensions/Logs/OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.cs @@ -36,7 +36,7 @@ public static LoggerProviderBuilder AddInstrumentation< loggerProviderBuilder.ConfigureBuilder((sp, builder) => { - builder.AddInstrumentation(() => sp.GetRequiredService()); + builder.AddInstrumentation(sp.GetRequiredService); }); return loggerProviderBuilder; diff --git a/src/OpenTelemetry.Api.ProviderBuilderExtensions/Metrics/OpenTelemetryDependencyInjectionMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Api.ProviderBuilderExtensions/Metrics/OpenTelemetryDependencyInjectionMeterProviderBuilderExtensions.cs index fea2275e9a3..ff52ee37cb3 100644 --- a/src/OpenTelemetry.Api.ProviderBuilderExtensions/Metrics/OpenTelemetryDependencyInjectionMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Api.ProviderBuilderExtensions/Metrics/OpenTelemetryDependencyInjectionMeterProviderBuilderExtensions.cs @@ -36,7 +36,7 @@ public static MeterProviderBuilder AddInstrumentation< meterProviderBuilder.ConfigureBuilder((sp, builder) => { - builder.AddInstrumentation(() => sp.GetRequiredService()); + builder.AddInstrumentation(sp.GetRequiredService); }); return meterProviderBuilder; diff --git a/src/OpenTelemetry.Api.ProviderBuilderExtensions/OpenTelemetry.Api.ProviderBuilderExtensions.csproj b/src/OpenTelemetry.Api.ProviderBuilderExtensions/OpenTelemetry.Api.ProviderBuilderExtensions.csproj index d057d595f43..8c28b1e5c44 100644 --- a/src/OpenTelemetry.Api.ProviderBuilderExtensions/OpenTelemetry.Api.ProviderBuilderExtensions.csproj +++ b/src/OpenTelemetry.Api.ProviderBuilderExtensions/OpenTelemetry.Api.ProviderBuilderExtensions.csproj @@ -5,7 +5,6 @@ Contains extensions to register OpenTelemetry in applications using Microsoft.Extensions.DependencyInjection OpenTelemetry core- - latest-all @@ -16,4 +15,14 @@ + + + + + + + + + + diff --git a/src/OpenTelemetry.Api.ProviderBuilderExtensions/Trace/OpenTelemetryDependencyInjectionTracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Api.ProviderBuilderExtensions/Trace/OpenTelemetryDependencyInjectionTracerProviderBuilderExtensions.cs index 2e6ab37628d..939fd34b366 100644 --- a/src/OpenTelemetry.Api.ProviderBuilderExtensions/Trace/OpenTelemetryDependencyInjectionTracerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Api.ProviderBuilderExtensions/Trace/OpenTelemetryDependencyInjectionTracerProviderBuilderExtensions.cs @@ -36,7 +36,7 @@ public static TracerProviderBuilder AddInstrumentation< tracerProviderBuilder.ConfigureBuilder((sp, builder) => { - builder.AddInstrumentation(() => sp.GetRequiredService()); + builder.AddInstrumentation(sp.GetRequiredService); }); return tracerProviderBuilder; diff --git a/src/OpenTelemetry.Api/.publicApi/Experimental/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Api/.publicApi/Experimental/PublicAPI.Unshipped.txt index 4cb12fd2969..e4f616a9dca 100644 --- a/src/OpenTelemetry.Api/.publicApi/Experimental/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Api/.publicApi/Experimental/PublicAPI.Unshipped.txt @@ -1,73 +1,75 @@ -abstract OpenTelemetry.Logs.Logger.EmitLog(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributeList attributes) -> void -OpenTelemetry.Logs.Logger -OpenTelemetry.Logs.Logger.EmitLog(in OpenTelemetry.Logs.LogRecordData data) -> void -OpenTelemetry.Logs.Logger.Logger(string? name) -> void -OpenTelemetry.Logs.Logger.Name.get -> string! -OpenTelemetry.Logs.Logger.Version.get -> string? -OpenTelemetry.Logs.LoggerProvider.GetLogger() -> OpenTelemetry.Logs.Logger! -OpenTelemetry.Logs.LoggerProvider.GetLogger(string? name) -> OpenTelemetry.Logs.Logger! -OpenTelemetry.Logs.LoggerProvider.GetLogger(string? name, string? version) -> OpenTelemetry.Logs.Logger! -OpenTelemetry.Logs.LogRecordAttributeList -OpenTelemetry.Logs.LogRecordAttributeList.Add(string! key, object? value) -> void -OpenTelemetry.Logs.LogRecordAttributeList.Add(System.Collections.Generic.KeyValuePair attribute) -> void -OpenTelemetry.Logs.LogRecordAttributeList.Clear() -> void -OpenTelemetry.Logs.LogRecordAttributeList.Count.get -> int -OpenTelemetry.Logs.LogRecordAttributeList.Enumerator -OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair -OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Dispose() -> void -OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Enumerator() -> void -OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.MoveNext() -> bool -OpenTelemetry.Logs.LogRecordAttributeList.GetEnumerator() -> OpenTelemetry.Logs.LogRecordAttributeList.Enumerator -OpenTelemetry.Logs.LogRecordAttributeList.LogRecordAttributeList() -> void -OpenTelemetry.Logs.LogRecordAttributeList.RecordException(System.Exception! exception) -> void -OpenTelemetry.Logs.LogRecordAttributeList.this[int index].get -> System.Collections.Generic.KeyValuePair -OpenTelemetry.Logs.LogRecordAttributeList.this[int index].set -> void -OpenTelemetry.Logs.LogRecordAttributeList.this[string! key].set -> void -OpenTelemetry.Logs.LogRecordData -OpenTelemetry.Logs.LogRecordData.Body.get -> string? -OpenTelemetry.Logs.LogRecordData.Body.set -> void -OpenTelemetry.Logs.LogRecordData.LogRecordData() -> void -OpenTelemetry.Logs.LogRecordData.LogRecordData(in System.Diagnostics.ActivityContext activityContext) -> void -OpenTelemetry.Logs.LogRecordData.LogRecordData(System.Diagnostics.Activity? activity) -> void -OpenTelemetry.Logs.LogRecordData.Severity.get -> OpenTelemetry.Logs.LogRecordSeverity? -OpenTelemetry.Logs.LogRecordData.Severity.set -> void -OpenTelemetry.Logs.LogRecordData.SeverityText.get -> string? -OpenTelemetry.Logs.LogRecordData.SeverityText.set -> void -OpenTelemetry.Logs.LogRecordData.SpanId.get -> System.Diagnostics.ActivitySpanId -OpenTelemetry.Logs.LogRecordData.SpanId.set -> void -OpenTelemetry.Logs.LogRecordData.Timestamp.get -> System.DateTime -OpenTelemetry.Logs.LogRecordData.Timestamp.set -> void -OpenTelemetry.Logs.LogRecordData.TraceFlags.get -> System.Diagnostics.ActivityTraceFlags -OpenTelemetry.Logs.LogRecordData.TraceFlags.set -> void -OpenTelemetry.Logs.LogRecordData.TraceId.get -> System.Diagnostics.ActivityTraceId -OpenTelemetry.Logs.LogRecordData.TraceId.set -> void -OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Debug = 5 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Debug2 = 6 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Debug3 = 7 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Debug4 = 8 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Error = 17 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Error2 = 18 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Error3 = 19 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Error4 = 20 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Fatal = 21 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Fatal2 = 22 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Fatal3 = 23 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Fatal4 = 24 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Info = 9 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Info2 = 10 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Info3 = 11 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Info4 = 12 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Trace = 1 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Trace2 = 2 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Trace3 = 3 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Trace4 = 4 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Unspecified = 0 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Warn = 13 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Warn2 = 14 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Warn3 = 15 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverity.Warn4 = 16 -> OpenTelemetry.Logs.LogRecordSeverity -OpenTelemetry.Logs.LogRecordSeverityExtensions -static OpenTelemetry.Logs.LogRecordAttributeList.CreateFromEnumerable(System.Collections.Generic.IEnumerable>! attributes) -> OpenTelemetry.Logs.LogRecordAttributeList -static OpenTelemetry.Logs.LogRecordSeverityExtensions.ToShortName(this OpenTelemetry.Logs.LogRecordSeverity logRecordSeverity) -> string! -virtual OpenTelemetry.Logs.LoggerProvider.TryCreateLogger(string? name, out OpenTelemetry.Logs.Logger? logger) -> bool +[OTEL1001]abstract OpenTelemetry.Logs.Logger.EmitLog(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributeList attributes) -> void +[OTEL1001]OpenTelemetry.Logs.Logger +[OTEL1001]OpenTelemetry.Logs.Logger.EmitLog(in OpenTelemetry.Logs.LogRecordData data) -> void +[OTEL1001]OpenTelemetry.Logs.Logger.Logger(string? name) -> void +[OTEL1001]OpenTelemetry.Logs.Logger.Name.get -> string! +[OTEL1001]OpenTelemetry.Logs.Logger.Version.get -> string? +[OTEL1001]OpenTelemetry.Logs.LoggerProvider.GetLogger() -> OpenTelemetry.Logs.Logger! +[OTEL1001]OpenTelemetry.Logs.LoggerProvider.GetLogger(string? name) -> OpenTelemetry.Logs.Logger! +[OTEL1001]OpenTelemetry.Logs.LoggerProvider.GetLogger(string? name, string? version) -> OpenTelemetry.Logs.Logger! +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList.Add(string! key, object? value) -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList.Add(System.Collections.Generic.KeyValuePair attribute) -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList.Clear() -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList.Count.get -> int +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList.Enumerator +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Dispose() -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Enumerator() -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.MoveNext() -> bool +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList.GetEnumerator() -> OpenTelemetry.Logs.LogRecordAttributeList.Enumerator +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList.LogRecordAttributeList() -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList.RecordException(System.Exception! exception) -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList.this[int index].get -> System.Collections.Generic.KeyValuePair +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList.this[int index].set -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordAttributeList.this[string! key].set -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordData +[OTEL1001]OpenTelemetry.Logs.LogRecordData.Body.get -> string? +[OTEL1001]OpenTelemetry.Logs.LogRecordData.Body.set -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordData.LogRecordData() -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordData.LogRecordData(in System.Diagnostics.ActivityContext activityContext) -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordData.LogRecordData(System.Diagnostics.Activity? activity) -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordData.Severity.get -> OpenTelemetry.Logs.LogRecordSeverity? +[OTEL1001]OpenTelemetry.Logs.LogRecordData.Severity.set -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordData.SeverityText.get -> string? +[OTEL1001]OpenTelemetry.Logs.LogRecordData.SeverityText.set -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordData.SpanId.get -> System.Diagnostics.ActivitySpanId +[OTEL1001]OpenTelemetry.Logs.LogRecordData.SpanId.set -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordData.Timestamp.get -> System.DateTime +[OTEL1001]OpenTelemetry.Logs.LogRecordData.Timestamp.set -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordData.TraceFlags.get -> System.Diagnostics.ActivityTraceFlags +[OTEL1001]OpenTelemetry.Logs.LogRecordData.TraceFlags.set -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordData.TraceId.get -> System.Diagnostics.ActivityTraceId +[OTEL1001]OpenTelemetry.Logs.LogRecordData.TraceId.set -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordData.EventName.get -> string? +[OTEL1001]OpenTelemetry.Logs.LogRecordData.EventName.set -> void +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Debug = 5 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Debug2 = 6 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Debug3 = 7 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Debug4 = 8 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Error = 17 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Error2 = 18 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Error3 = 19 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Error4 = 20 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Fatal = 21 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Fatal2 = 22 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Fatal3 = 23 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Fatal4 = 24 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Info = 9 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Info2 = 10 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Info3 = 11 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Info4 = 12 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Trace = 1 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Trace2 = 2 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Trace3 = 3 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Trace4 = 4 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Unspecified = 0 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Warn = 13 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Warn2 = 14 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Warn3 = 15 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverity.Warn4 = 16 -> OpenTelemetry.Logs.LogRecordSeverity +[OTEL1001]OpenTelemetry.Logs.LogRecordSeverityExtensions +[OTEL1001]static OpenTelemetry.Logs.LogRecordAttributeList.CreateFromEnumerable(System.Collections.Generic.IEnumerable>! attributes) -> OpenTelemetry.Logs.LogRecordAttributeList +[OTEL1001]static OpenTelemetry.Logs.LogRecordSeverityExtensions.ToShortName(this OpenTelemetry.Logs.LogRecordSeverity logRecordSeverity) -> string! +[OTEL1001]virtual OpenTelemetry.Logs.LoggerProvider.TryCreateLogger(string? name, out OpenTelemetry.Logs.Logger? logger) -> bool diff --git a/src/OpenTelemetry.Api/.publicApi/Stable/PublicAPI.Shipped.txt b/src/OpenTelemetry.Api/.publicApi/Stable/PublicAPI.Shipped.txt index 73362dee3c1..a0742cbc241 100644 --- a/src/OpenTelemetry.Api/.publicApi/Stable/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry.Api/.publicApi/Stable/PublicAPI.Shipped.txt @@ -126,6 +126,8 @@ OpenTelemetry.Trace.TelemetrySpan.AddEvent(string! name, OpenTelemetry.Trace.Spa OpenTelemetry.Trace.TelemetrySpan.AddEvent(string! name, System.DateTimeOffset timestamp, OpenTelemetry.Trace.SpanAttributes? attributes) -> OpenTelemetry.Trace.TelemetrySpan! OpenTelemetry.Trace.TelemetrySpan.AddEvent(string! name, System.DateTimeOffset timestamp) -> OpenTelemetry.Trace.TelemetrySpan! OpenTelemetry.Trace.TelemetrySpan.AddEvent(string! name) -> OpenTelemetry.Trace.TelemetrySpan! +OpenTelemetry.Trace.TelemetrySpan.AddLink(OpenTelemetry.Trace.SpanContext spanContext, OpenTelemetry.Trace.SpanAttributes? attributes) -> OpenTelemetry.Trace.TelemetrySpan! +OpenTelemetry.Trace.TelemetrySpan.AddLink(OpenTelemetry.Trace.SpanContext spanContext) -> OpenTelemetry.Trace.TelemetrySpan! OpenTelemetry.Trace.TelemetrySpan.Context.get -> OpenTelemetry.Trace.SpanContext OpenTelemetry.Trace.TelemetrySpan.Dispose() -> void OpenTelemetry.Trace.TelemetrySpan.End() -> void @@ -151,7 +153,8 @@ OpenTelemetry.Trace.Tracer.StartRootSpan(string! name, OpenTelemetry.Trace.SpanK OpenTelemetry.Trace.Tracer.StartSpan(string! name, OpenTelemetry.Trace.SpanKind kind = OpenTelemetry.Trace.SpanKind.Internal, in OpenTelemetry.Trace.SpanContext parentContext = default(OpenTelemetry.Trace.SpanContext), OpenTelemetry.Trace.SpanAttributes? initialAttributes = null, System.Collections.Generic.IEnumerable? links = null, System.DateTimeOffset startTime = default(System.DateTimeOffset)) -> OpenTelemetry.Trace.TelemetrySpan! OpenTelemetry.Trace.Tracer.StartSpan(string! name, OpenTelemetry.Trace.SpanKind kind, in OpenTelemetry.Trace.TelemetrySpan? parentSpan, OpenTelemetry.Trace.SpanAttributes? initialAttributes = null, System.Collections.Generic.IEnumerable? links = null, System.DateTimeOffset startTime = default(System.DateTimeOffset)) -> OpenTelemetry.Trace.TelemetrySpan! OpenTelemetry.Trace.TracerProvider -OpenTelemetry.Trace.TracerProvider.GetTracer(string! name, string? version = null) -> OpenTelemetry.Trace.Tracer! +OpenTelemetry.Trace.TracerProvider.GetTracer(string! name, string? version = null, System.Collections.Generic.IEnumerable>? tags = null) -> OpenTelemetry.Trace.Tracer! +OpenTelemetry.Trace.TracerProvider.GetTracer(string! name, string? version) -> OpenTelemetry.Trace.Tracer! OpenTelemetry.Trace.TracerProvider.TracerProvider() -> void OpenTelemetry.Trace.TracerProviderBuilder OpenTelemetry.Trace.TracerProviderBuilder.TracerProviderBuilder() -> void diff --git a/src/OpenTelemetry.Api/AssemblyInfo.cs b/src/OpenTelemetry.Api/AssemblyInfo.cs deleted file mode 100644 index b1e95ff67e6..00000000000 --- a/src/OpenTelemetry.Api/AssemblyInfo.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("OpenTelemetry" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Api.ProviderBuilderExtensions" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Api.ProviderBuilderExtensions.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Api.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Console" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.InMemory" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Shims.OpenTracing.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Tests" + AssemblyInfo.PublicKey)] - -#if SIGNED -file static class AssemblyInfo -{ - public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; -} -#else -file static class AssemblyInfo -{ - public const string PublicKey = ""; -} -#endif diff --git a/src/OpenTelemetry.Api/Baggage.cs b/src/OpenTelemetry.Api/Baggage.cs index 93b972f4f17..d69e0170ca4 100644 --- a/src/OpenTelemetry.Api/Baggage.cs +++ b/src/OpenTelemetry.Api/Baggage.cs @@ -16,7 +16,7 @@ namespace OpenTelemetry; public readonly struct Baggage : IEquatable { private static readonly RuntimeContextSlot RuntimeContextSlot = RuntimeContext.RegisterSlot("otel.baggage"); - private static readonly Dictionary EmptyBaggage = new(); + private static readonly Dictionary EmptyBaggage = []; private readonly Dictionary baggage; @@ -305,7 +305,9 @@ public Baggage RemoveBaggage(string name) /// Returns a new with all the key/value pairs removed. /// /// New with all the key/value pairs removed. +#pragma warning disable CA1822 // Mark members as static public Baggage ClearBaggage() +#pragma warning restore CA1822 // Mark members as static => default; /// @@ -343,7 +345,11 @@ public override int GetHashCode() unchecked { hash = (hash * 23) + baggage.Comparer.GetHashCode(item.Key); +#if NET + hash = (hash * 23) + item.Value.GetHashCode(StringComparison.Ordinal); +#else hash = (hash * 23) + item.Value.GetHashCode(); +#endif } } diff --git a/src/OpenTelemetry.Api/CHANGELOG.md b/src/OpenTelemetry.Api/CHANGELOG.md index a726bf04cc5..961b11de5b0 100644 --- a/src/OpenTelemetry.Api/CHANGELOG.md +++ b/src/OpenTelemetry.Api/CHANGELOG.md @@ -6,6 +6,35 @@ Notes](../../RELEASENOTES.md). ## Unreleased +## 1.13.0 + +Released 2025-Oct-01 + +* Added `AddLink(SpanContext, SpanAttributes?)` to `TelemetrySpan` to support + linking spans and associating optional attributes for advanced trace relationships. + ([#6305](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6305)) + +* Experimental (only in pre-release versions): Added the `EventName` property + to `LogRecordData` + ([#6306](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6306)) + +## 1.12.0 + +Released 2025-Apr-29 + +* Added a new overload for `TracerProvider.GetTracer` which accepts an optional + `IEnumerable>? tags` parameter, allowing + additional attributes to be associated with the `Tracer`. + ([#6137](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6137)) + +## 1.11.2 + +Released 2025-Mar-04 + +* Revert optimize performance of `TraceContextPropagator.Extract` introduced + in #5749 to resolve [GHSA-8785-wc3w-h8q6](https://github.com/open-telemetry/opentelemetry-dotnet/security/advisories/GHSA-8785-wc3w-h8q6). + ([#6161](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6161)) + ## 1.11.1 Released 2025-Jan-22 diff --git a/src/OpenTelemetry.Api/Context/Propagation/B3Propagator.cs b/src/OpenTelemetry.Api/Context/Propagation/B3Propagator.cs index 18276172106..56c353f4a28 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/B3Propagator.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/B3Propagator.cs @@ -27,6 +27,7 @@ public sealed class B3Propagator : TextMapPropagator internal const string UpperTraceId = "0000000000000000"; // Sampled values via the X_B3_SAMPLED header. + internal const char SampledValueChar = '1'; internal const string SampledValue = "1"; // Some old zipkin implementations may send true/false for the sampled header. Only use this for checking incoming values. @@ -35,7 +36,7 @@ public sealed class B3Propagator : TextMapPropagator // "Debug" sampled value. internal const string FlagsValue = "1"; - private static readonly HashSet AllFields = new() { XB3TraceId, XB3SpanId, XB3ParentSpanId, XB3Sampled, XB3Flags }; + private static readonly HashSet AllFields = [XB3TraceId, XB3SpanId, XB3ParentSpanId, XB3Sampled, XB3Flags]; private static readonly HashSet SampledValues = new(StringComparer.Ordinal) { SampledValue, LegacySampledValue }; @@ -130,7 +131,7 @@ public override void Inject(PropagationContext context, T carrier, Action public override ISet Fields => new HashSet { BaggageHeaderName }; @@ -52,7 +52,7 @@ public override PropagationContext Extract(PropagationContext context, T carr var baggageCollection = getter(carrier, BaggageHeaderName); if (baggageCollection?.Any() ?? false) { - if (TryExtractBaggage(baggageCollection.ToArray(), out var baggage)) + if (TryExtractBaggage([.. baggageCollection], out var baggage)) { return new PropagationContext(context.ActivityContext, new Baggage(baggage!)); } @@ -138,7 +138,11 @@ internal static bool TryExtractBaggage( break; } +#if NET + if (pair.IndexOf('=', StringComparison.Ordinal) < 0) +#else if (pair.IndexOf('=') < 0) +#endif { continue; } @@ -157,10 +161,7 @@ internal static bool TryExtractBaggage( continue; } - if (baggageDictionary == null) - { - baggageDictionary = new Dictionary(); - } + baggageDictionary ??= []; baggageDictionary[key] = value; } diff --git a/src/OpenTelemetry.Api/Context/Propagation/CompositeTextMapPropagator.cs b/src/OpenTelemetry.Api/Context/Propagation/CompositeTextMapPropagator.cs index e160bc8ac78..d0375aadc71 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/CompositeTextMapPropagator.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/CompositeTextMapPropagator.cs @@ -11,7 +11,7 @@ namespace OpenTelemetry.Context.Propagation; /// public class CompositeTextMapPropagator : TextMapPropagator { - private readonly IReadOnlyList propagators; + private readonly List propagators; private readonly ISet allFields; /// diff --git a/src/OpenTelemetry.Api/Context/Propagation/NoopTextMapPropagator.cs b/src/OpenTelemetry.Api/Context/Propagation/NoopTextMapPropagator.cs index a722a4f087a..0721ad52883 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/NoopTextMapPropagator.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/NoopTextMapPropagator.cs @@ -5,7 +5,9 @@ namespace OpenTelemetry.Context.Propagation; internal sealed class NoopTextMapPropagator : TextMapPropagator { +#pragma warning disable CA1805 // Do not initialize unnecessarily private static readonly PropagationContext DefaultPropagationContext = default; +#pragma warning restore CA1805 // Do not initialize unnecessarily public override ISet? Fields => null; diff --git a/src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs b/src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs index ee67aaaaafb..a00f8e8beb1 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs @@ -1,9 +1,9 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Text; using OpenTelemetry.Internal; namespace OpenTelemetry.Context.Propagation; @@ -16,6 +16,12 @@ public class TraceContextPropagator : TextMapPropagator private const string TraceParent = "traceparent"; private const string TraceState = "tracestate"; + // The following length limits are from Trace Context v1 https://www.w3.org/TR/trace-context-1/#key + private const int TraceStateKeyMaxLength = 256; + private const int TraceStateKeyTenantMaxLength = 241; + private const int TraceStateKeyVendorMaxLength = 14; + private const int TraceStateValueMaxLength = 256; + private static readonly int VersionPrefixIdLength = "00-".Length; private static readonly int TraceIdLength = "0af7651916cd43dd8448eb211c80319c".Length; private static readonly int VersionAndTraceIdLength = "00-0af7651916cd43dd8448eb211c80319c-".Length; @@ -24,12 +30,6 @@ public class TraceContextPropagator : TextMapPropagator private static readonly int OptionsLength = "00".Length; private static readonly int TraceparentLengthV0 = "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-00".Length; - // The following length limits are from Trace Context v1 https://www.w3.org/TR/trace-context-1/#key - private static readonly int TraceStateKeyMaxLength = 256; - private static readonly int TraceStateKeyTenantMaxLength = 241; - private static readonly int TraceStateKeyVendorMaxLength = 14; - private static readonly int TraceStateValueMaxLength = 256; - /// public override ISet Fields => new HashSet { TraceState, TraceParent }; @@ -76,7 +76,7 @@ public override PropagationContext Extract(PropagationContext context, T carr var tracestateCollection = getter(carrier, TraceState); if (tracestateCollection?.Any() ?? false) { - TryExtractTracestate(tracestateCollection, out tracestate); + TryExtractTracestate([.. tracestateCollection], out tracestate); } return new PropagationContext( @@ -173,7 +173,7 @@ internal static bool TryExtractTraceparent(string traceparent, out ActivityTrace try { - traceId = ActivityTraceId.CreateFromString(traceparent.AsSpan().Slice(VersionPrefixIdLength, TraceIdLength)); + traceId = ActivityTraceId.CreateFromString(traceparent.AsSpan(VersionPrefixIdLength, TraceIdLength)); } catch (ArgumentOutOfRangeException) { @@ -189,7 +189,7 @@ internal static bool TryExtractTraceparent(string traceparent, out ActivityTrace byte optionsLowByte; try { - spanId = ActivitySpanId.CreateFromString(traceparent.AsSpan().Slice(VersionAndTraceIdLength, SpanIdLength)); + spanId = ActivitySpanId.CreateFromString(traceparent.AsSpan(VersionAndTraceIdLength, SpanIdLength)); _ = HexCharToByte(traceparent[VersionAndTraceIdAndSpanIdLength]); // to verify if there is no bad chars on options position optionsLowByte = HexCharToByte(traceparent[VersionAndTraceIdAndSpanIdLength + 1]); } @@ -220,37 +220,31 @@ internal static bool TryExtractTraceparent(string traceparent, out ActivityTrace return true; } - internal static bool TryExtractTracestate(IEnumerable tracestateCollection, out string tracestateResult) + internal static bool TryExtractTracestate(string[] tracestateCollection, out string tracestateResult) { tracestateResult = string.Empty; - char[]? rentedArray = null; - Span traceStateBuffer = stackalloc char[128]; // 256B - Span keyLookupBuffer = stackalloc char[96]; // 192B (3x32 keys) - int keys = 0; - int charsWritten = 0; - - try + if (tracestateCollection != null) { - foreach (var tracestateItem in tracestateCollection) + var keySet = new HashSet(); + var result = new StringBuilder(); + for (int i = 0; i < tracestateCollection.Length; ++i) { - var tracestate = tracestateItem.AsSpan(); - int position = 0; - - while (position < tracestate.Length) + var tracestate = tracestateCollection[i].AsSpan(); + int begin = 0; + while (begin < tracestate.Length) { - int length = tracestate.Slice(position).IndexOf(','); + int length = tracestate.Slice(begin).IndexOf(','); ReadOnlySpan listMember; - if (length != -1) { - listMember = tracestate.Slice(position, length).Trim(); - position += length + 1; + listMember = tracestate.Slice(begin, length).Trim(); + begin += length + 1; } else { - listMember = tracestate.Slice(position).Trim(); - position = tracestate.Length; + listMember = tracestate.Slice(begin).Trim(); + begin = tracestate.Length; } // https://github.com/w3c/trace-context/blob/master/spec/20-http_request_header_format.md#tracestate-header-field-values @@ -261,7 +255,7 @@ internal static bool TryExtractTracestate(IEnumerable tracestateCollecti continue; } - if (keys >= 32) + if (keySet.Count >= 32) { // https://github.com/w3c/trace-context/blob/master/spec/20-http_request_header_format.md#list // test_tracestate_member_count_limit @@ -292,107 +286,29 @@ internal static bool TryExtractTracestate(IEnumerable tracestateCollecti } // ValidateKey() call above has ensured the key does not contain upper case letters. - - var duplicationCheckLength = Math.Min(key.Length, 3); - - if (keys > 0) - { - // Fast path check of first three chars for potential duplicated keys - var potentialMatchingKeyPosition = 1; - var found = false; - for (int i = 0; i < keys * 3; i += 3) - { - if (keyLookupBuffer.Slice(i, duplicationCheckLength).SequenceEqual(key.Slice(0, duplicationCheckLength))) - { - found = true; - break; - } - - potentialMatchingKeyPosition++; - } - - // If the fast check has found a possible duplicate, we need to do a full check - if (found) - { - var bufferToCompare = traceStateBuffer.Slice(0, charsWritten); - - // We know which key is the first possible duplicate, so skip to that key - // by slicing to the position after the appropriate comma. - for (int i = 1; i < potentialMatchingKeyPosition; i++) - { - var commaIndex = bufferToCompare.IndexOf(','); - - if (commaIndex > -1) - { - bufferToCompare.Slice(commaIndex); - } - } - - int existingIndex = -1; - while ((existingIndex = bufferToCompare.IndexOf(key)) > -1) - { - if ((existingIndex > 0 && bufferToCompare[existingIndex - 1] != ',') || bufferToCompare[existingIndex + key.Length] != '=') - { - continue; // this is not a key - } - - return false; // test_tracestate_duplicated_keys - } - } - } - - // Store up to the first three characters of the key for use in the duplicate lookup fast path - var startKeyLookupIndex = keys > 0 ? keys * 3 : 0; - key.Slice(0, duplicationCheckLength).CopyTo(keyLookupBuffer.Slice(startKeyLookupIndex)); - - // Check we have capacity to write the key and value - var requiredCapacity = charsWritten > 0 ? listMember.Length + 1 : listMember.Length; - - while (charsWritten + requiredCapacity > traceStateBuffer.Length) + if (!keySet.Add(key.ToString())) { - GrowBuffer(ref rentedArray, ref traceStateBuffer); + // test_tracestate_duplicated_keys + return false; } - if (charsWritten > 0) + if (result.Length > 0) { - traceStateBuffer[charsWritten++] = ','; + result.Append(','); } - listMember.CopyTo(traceStateBuffer.Slice(charsWritten)); - charsWritten += listMember.Length; - - keys++; +#if NET + result.Append(listMember); +#else + result.Append(listMember.ToString()); +#endif } } - tracestateResult = traceStateBuffer.Slice(0, charsWritten).ToString(); - - return true; - } - finally - { - if (rentedArray is not null) - { - ArrayPool.Shared.Return(rentedArray); - rentedArray = null; - } + tracestateResult = result.ToString(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void GrowBuffer(ref char[]? array, ref Span buffer) - { - var newBuffer = ArrayPool.Shared.Rent(buffer.Length * 2); - - buffer.CopyTo(newBuffer.AsSpan()); - - if (array is not null) - { - ArrayPool.Shared.Return(array); - } - - array = newBuffer; - buffer = array.AsSpan(); - } + return true; } private static byte HexCharToByte(char c) diff --git a/src/OpenTelemetry.Api/Context/Propagation/TraceStateUtilsNew.cs b/src/OpenTelemetry.Api/Context/Propagation/TraceStateUtils.cs similarity index 96% rename from src/OpenTelemetry.Api/Context/Propagation/TraceStateUtilsNew.cs rename to src/OpenTelemetry.Api/Context/Propagation/TraceStateUtils.cs index eef28f8ad97..d81291b5052 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/TraceStateUtilsNew.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/TraceStateUtils.cs @@ -10,7 +10,7 @@ namespace OpenTelemetry.Context.Propagation; /// /// Extension methods to extract TraceState from string. /// -internal static class TraceStateUtilsNew +internal static class TraceStateUtils { private const int KeyMaxSize = 256; private const int ValueMaxSize = 256; @@ -98,6 +98,7 @@ internal static bool AppendTraceState(string traceStateString, List>? traceState) { +#pragma warning disable CA1851 // Possible multiple enumerations of 'IEnumerable' collection if (traceState == null || !traceState.Any()) { return string.Empty; @@ -125,6 +126,7 @@ internal static string GetString(IEnumerable>? trac .Append(','); } } +#pragma warning restore CA1851 // Possible multiple enumerations of 'IEnumerable' collection return sb.Remove(sb.Length - 1, 1).ToString(); } @@ -153,7 +155,7 @@ private static bool TryParseKeyValue(ReadOnlySpan pair, out ReadOnlySpan /// The value retrieved from the context slot. +#pragma warning disable CA1716 // Identifiers should not match keywords public abstract T? Get(); +#pragma warning restore CA1716 // Identifiers should not match keywords /// /// Set the value to the context slot. /// /// The value to be set. +#pragma warning disable CA1716 // Identifiers should not match keywords public abstract void Set(T value); +#pragma warning restore CA1716 // Identifiers should not match keywords /// public void Dispose() diff --git a/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs b/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs index 5ac56d362f3..c2e9cf9dfb2 100644 --- a/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs +++ b/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs @@ -12,7 +12,7 @@ namespace OpenTelemetry.Internal; [EventSource(Name = "OpenTelemetry-Api")] internal sealed class OpenTelemetryApiEventSource : EventSource { - public static OpenTelemetryApiEventSource Log = new(); + public static readonly OpenTelemetryApiEventSource Log = new(); [NonEvent] public void ActivityContextExtractException(string format, Exception ex) diff --git a/src/OpenTelemetry.Api/Logs/LogRecordAttributeList.cs b/src/OpenTelemetry.Api/Logs/LogRecordAttributeList.cs index 5f6568a8a54..96621ee27ed 100644 --- a/src/OpenTelemetry.Api/Logs/LogRecordAttributeList.cs +++ b/src/OpenTelemetry.Api/Logs/LogRecordAttributeList.cs @@ -4,7 +4,7 @@ using System.Collections; using System.ComponentModel; using System.Diagnostics; -#if NET && EXPOSE_EXPERIMENTAL_FEATURES +#if EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; #endif using OpenTelemetry.Internal; @@ -17,9 +17,7 @@ namespace OpenTelemetry.Logs; /// Stores attributes to be added to a log message. /// /// -#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else /// @@ -27,12 +25,14 @@ namespace OpenTelemetry.Logs; /// internal #endif +#pragma warning disable CA1815 // Override equals and operator equals on value types struct LogRecordAttributeList : IReadOnlyList> +#pragma warning restore CA1815 // Override equals and operator equals on value types { internal const int OverflowMaxCount = 8; internal const int OverflowAdditionalCapacity = 16; internal List>? OverflowAttributes; - private static readonly IReadOnlyList> Empty = Array.Empty>(); + private static readonly IReadOnlyList> Empty = []; private KeyValuePair attribute1; private KeyValuePair attribute2; private KeyValuePair attribute3; @@ -113,7 +113,9 @@ readonly get /// Attribute name. /// Attribute value. [EditorBrowsable(EditorBrowsableState.Never)] +#pragma warning disable CA1044 // Properties should not be write only public object? this[string key] +#pragma warning restore CA1044 // Properties should not be write only { // Note: This only exists to enable collection initializer syntax // like { ["key"] = value }. @@ -130,7 +132,7 @@ public static LogRecordAttributeList CreateFromEnumerable(IEnumerable> Export(ref List>? attributeStorage) { - int count = this.count; - if (count <= 0) + int readonlyCount = this.count; + if (readonlyCount <= 0) { return Empty; } @@ -223,49 +225,49 @@ public readonly Enumerator GetEnumerator() return overflowAttributes; } - Debug.Assert(count <= 8, "Invalid size detected."); + Debug.Assert(readonlyCount <= 8, "Invalid size detected."); attributeStorage ??= new List>(OverflowAdditionalCapacity); // TODO: Perf test this, adjust as needed. attributeStorage.Add(this.attribute1); - if (count == 1) + if (readonlyCount == 1) { return attributeStorage; } attributeStorage.Add(this.attribute2); - if (count == 2) + if (readonlyCount == 2) { return attributeStorage; } attributeStorage.Add(this.attribute3); - if (count == 3) + if (readonlyCount == 3) { return attributeStorage; } attributeStorage.Add(this.attribute4); - if (count == 4) + if (readonlyCount == 4) { return attributeStorage; } attributeStorage.Add(this.attribute5); - if (count == 5) + if (readonlyCount == 5) { return attributeStorage; } attributeStorage.Add(this.attribute6); - if (count == 6) + if (readonlyCount == 6) { return attributeStorage; } attributeStorage.Add(this.attribute7); - if (count == 7) + if (readonlyCount == 7) { return attributeStorage; } diff --git a/src/OpenTelemetry.Api/Logs/LogRecordData.cs b/src/OpenTelemetry.Api/Logs/LogRecordData.cs index 20d8d5f264c..374b95d736a 100644 --- a/src/OpenTelemetry.Api/Logs/LogRecordData.cs +++ b/src/OpenTelemetry.Api/Logs/LogRecordData.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; -#if NET && EXPOSE_EXPERIMENTAL_FEATURES +#if EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Internal; #endif @@ -14,9 +14,7 @@ namespace OpenTelemetry.Logs; /// Stores details about a log message. /// /// -#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else /// @@ -24,7 +22,9 @@ namespace OpenTelemetry.Logs; /// internal #endif +#pragma warning disable CA1815 // Override equals and operator equals on value types struct LogRecordData +#pragma warning restore CA1815 // Override equals and operator equals on value types { internal DateTime TimestampBacking = DateTime.UtcNow; @@ -123,6 +123,11 @@ public DateTime Timestamp /// public string? Body { get; set; } = null; + /// + /// Gets or sets the name of the event associated with the log. + /// + public string? EventName { get; set; } = null; + internal static void SetActivityContext(ref LogRecordData data, Activity? activity) { if (activity != null) diff --git a/src/OpenTelemetry.Api/Logs/LogRecordSeverity.cs b/src/OpenTelemetry.Api/Logs/LogRecordSeverity.cs index b6a5b3e000a..73d98881d0a 100644 --- a/src/OpenTelemetry.Api/Logs/LogRecordSeverity.cs +++ b/src/OpenTelemetry.Api/Logs/LogRecordSeverity.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NET && EXPOSE_EXPERIMENTAL_FEATURES +#if EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Internal; #endif @@ -13,9 +13,7 @@ namespace OpenTelemetry.Logs; /// Describes the severity level of a log record. /// /// -#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else /// diff --git a/src/OpenTelemetry.Api/Logs/LogRecordSeverityExtensions.cs b/src/OpenTelemetry.Api/Logs/LogRecordSeverityExtensions.cs index 81453d0302f..14e6e846cdd 100644 --- a/src/OpenTelemetry.Api/Logs/LogRecordSeverityExtensions.cs +++ b/src/OpenTelemetry.Api/Logs/LogRecordSeverityExtensions.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NET && EXPOSE_EXPERIMENTAL_FEATURES +#if EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Internal; #endif @@ -13,9 +13,7 @@ namespace OpenTelemetry.Logs; /// Contains extension methods for the enum. /// /// -#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else /// @@ -57,8 +55,8 @@ static class LogRecordSeverityExtensions internal const string Fatal3ShortName = FatalShortName + "3"; internal const string Fatal4ShortName = FatalShortName + "4"; - private static readonly string[] LogRecordSeverityShortNames = new string[] - { + private static readonly string[] LogRecordSeverityShortNames = + [ UnspecifiedShortName, TraceShortName, @@ -89,8 +87,8 @@ static class LogRecordSeverityExtensions FatalShortName, Fatal2ShortName, Fatal3ShortName, - Fatal4ShortName, - }; + Fatal4ShortName + ]; /// /// Returns the OpenTelemetry Specification short name for the /// WARNING: This is an experimental API which might change or be removed in the future. Use at your own risk. -#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else /// diff --git a/src/OpenTelemetry.Api/Logs/LoggerProvider.cs b/src/OpenTelemetry.Api/Logs/LoggerProvider.cs index 71cc40e2325..c01a9ffd923 100644 --- a/src/OpenTelemetry.Api/Logs/LoggerProvider.cs +++ b/src/OpenTelemetry.Api/Logs/LoggerProvider.cs @@ -1,10 +1,10 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NETSTANDARD2_1_OR_GREATER || NET +#if NETSTANDARD2_1_OR_GREATER || NET || EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; #endif -#if EXPOSE_EXPERIMENTAL_FEATURES && NET +#if EXPOSE_EXPERIMENTAL_FEATURES using OpenTelemetry.Internal; #endif @@ -30,9 +30,7 @@ protected LoggerProvider() /// /// /// instance. -#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else internal @@ -47,9 +45,7 @@ Logger GetLogger() /// /// Optional name identifying the instrumentation library. /// instance. -#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else internal @@ -65,9 +61,7 @@ Logger GetLogger(string? name) /// Optional name identifying the instrumentation library. /// Optional version of the instrumentation library. /// instance. -#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else internal @@ -92,9 +86,7 @@ Logger GetLogger(string? name, string? version) /// Optional name identifying the instrumentation library. /// . /// if the logger was created. -#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif protected #else internal diff --git a/src/OpenTelemetry.Api/OpenTelemetry.Api.csproj b/src/OpenTelemetry.Api/OpenTelemetry.Api.csproj index 0e6d270b408..a3a2895ee3e 100644 --- a/src/OpenTelemetry.Api/OpenTelemetry.Api.csproj +++ b/src/OpenTelemetry.Api/OpenTelemetry.Api.csproj @@ -16,8 +16,25 @@ + + + + + + + + + + + + + + + + + diff --git a/src/OpenTelemetry.Api/README.md b/src/OpenTelemetry.Api/README.md index 09978db05a4..fcaea931073 100644 --- a/src/OpenTelemetry.Api/README.md +++ b/src/OpenTelemetry.Api/README.md @@ -249,7 +249,7 @@ following sections describes more features. ### Activity creation options Basic usage example above showed how `StartActivity` method can be used to start -an `Activity`. The started activity will automatically becomes the `Current` +an `Activity`. The started activity automatically becomes the `Current` activity. It is important to note that the `StartActivity` returns `null`, if no listeners are interested in the activity to be created. This happens when the final application does not enable OpenTelemetry, or when OpenTelemetry samplers diff --git a/src/OpenTelemetry.Api/Trace/SpanContext.cs b/src/OpenTelemetry.Api/Trace/SpanContext.cs index 825ad62bb41..9ebab851122 100644 --- a/src/OpenTelemetry.Api/Trace/SpanContext.cs +++ b/src/OpenTelemetry.Api/Trace/SpanContext.cs @@ -34,7 +34,7 @@ public SpanContext( bool isRemote = false, IEnumerable>? traceState = null) { - this.ActivityContext = new ActivityContext(traceId, spanId, traceFlags, TraceStateUtilsNew.GetString(traceState), isRemote); + this.ActivityContext = new ActivityContext(traceId, spanId, traceFlags, TraceStateUtils.GetString(traceState), isRemote); } /// @@ -86,11 +86,11 @@ public IEnumerable> TraceState var traceState = this.ActivityContext.TraceState; if (string.IsNullOrEmpty(traceState)) { - return Enumerable.Empty>(); + return []; } var traceStateResult = new List>(); - TraceStateUtilsNew.AppendTraceState(traceState!, traceStateResult); + TraceStateUtils.AppendTraceState(traceState!, traceStateResult); return traceStateResult; } } @@ -99,7 +99,9 @@ public IEnumerable> TraceState /// Converts a into an . /// /// source. +#pragma warning disable CA2225 // Operator overloads have named alternates public static implicit operator ActivityContext(SpanContext spanContext) +#pragma warning restore CA2225 // Operator overloads have named alternates => spanContext.ActivityContext; /// diff --git a/src/OpenTelemetry.Api/Trace/SpanKind.cs b/src/OpenTelemetry.Api/Trace/SpanKind.cs index 07358f7b0e8..65a6df7af9c 100644 --- a/src/OpenTelemetry.Api/Trace/SpanKind.cs +++ b/src/OpenTelemetry.Api/Trace/SpanKind.cs @@ -6,7 +6,9 @@ namespace OpenTelemetry.Trace; /// /// Span kind. /// +#pragma warning disable CA1008 // Enums should have zero value public enum SpanKind +#pragma warning restore CA1008 // Enums should have zero value { /// /// Span kind was not specified. diff --git a/src/OpenTelemetry.Api/Trace/Status.cs b/src/OpenTelemetry.Api/Trace/Status.cs index a39ee8dd754..679d663f4ef 100644 --- a/src/OpenTelemetry.Api/Trace/Status.cs +++ b/src/OpenTelemetry.Api/Trace/Status.cs @@ -86,7 +86,11 @@ public override int GetHashCode() unchecked { hash = (31 * hash) + this.StatusCode.GetHashCode(); +#if NET + hash = (31 * hash) + (this.Description?.GetHashCode(StringComparison.Ordinal) ?? 0); +#else hash = (31 * hash) + (this.Description?.GetHashCode() ?? 0); +#endif } return hash; diff --git a/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs b/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs index 47e67c6095a..0b5ce40141f 100644 --- a/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs +++ b/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs @@ -45,9 +45,9 @@ public ActivitySpanId ParentSpanId /// Status to be set. public void SetStatus(Status value) { -#pragma warning disable +#pragma warning disable CS0618 // Type or member is obsolete this.Activity.SetStatus(value); -#pragma warning restore +#pragma warning restore CS0618 // Type or member is obsolete } /// @@ -226,6 +226,31 @@ public TelemetrySpan AddEvent(string name, DateTimeOffset timestamp, SpanAttribu return this; } + /// + /// Adds a link to another span. + /// + /// Span context to be linked. + /// The instance for chaining. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TelemetrySpan AddLink(SpanContext spanContext) + { + this.AddLinkInternal(spanContext.ActivityContext); + return this; + } + + /// + /// Adds a link to another span. + /// + /// Span context to be linked. + /// Attributes for the link. + /// The instance for chaining. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TelemetrySpan AddLink(SpanContext spanContext, SpanAttributes? attributes) + { + this.AddLinkInternal(spanContext.ActivityContext, attributes?.Attributes); + return this; + } + /// /// End the span. /// @@ -342,4 +367,12 @@ private void AddEventInternal(string name, DateTimeOffset timestamp = default, A this.Activity!.AddEvent(new ActivityEvent(name, timestamp, tags)); } } + + private void AddLinkInternal(ActivityContext context, ActivityTagsCollection? tags = null) + { + if (this.IsRecording) + { + this.Activity!.AddLink(new ActivityLink(context, tags)); + } + } } diff --git a/src/OpenTelemetry.Api/Trace/TracerProvider.cs b/src/OpenTelemetry.Api/Trace/TracerProvider.cs index 21197d0abdb..3246b2e4e7b 100644 --- a/src/OpenTelemetry.Api/Trace/TracerProvider.cs +++ b/src/OpenTelemetry.Api/Trace/TracerProvider.cs @@ -33,12 +33,29 @@ protected TracerProvider() /// Name identifying the instrumentation library. /// Version of the instrumentation library. /// Tracer instance. + // 1.11.1 BACKCOMPAT OVERLOAD -- DO NOT TOUCH public Tracer GetTracer( #if NET [AllowNull] #endif string name, - string? version = null) + string? version) => + this.GetTracer(name, version, null); + + /// + /// Gets a tracer with given name, version and tags. + /// + /// Name identifying the instrumentation library. + /// Version of the instrumentation library. + /// Tags associated with the tracer. + /// Tracer instance. + public Tracer GetTracer( +#if NET + [AllowNull] +#endif + string name, + string? version = null, + IEnumerable>? tags = null) { var tracers = this.Tracers; if (tracers == null) @@ -47,7 +64,7 @@ public Tracer GetTracer( return new(activitySource: null); } - var key = new TracerKey(name, version); + var key = new TracerKey(name, version, tags); if (!tracers.TryGetValue(key, out var tracer)) { @@ -60,12 +77,10 @@ public Tracer GetTracer( return new(activitySource: null); } - tracer = new(new(key.Name, key.Version)); -#if DEBUG + tracer = new(new(key.Name, key.Version, key.Tags)); bool result = tracers.TryAdd(key, tracer); +#if DEBUG System.Diagnostics.Debug.Assert(result, "Write into tracers cache failed"); -#else - tracers.TryAdd(key, tracer); #endif } } @@ -78,7 +93,7 @@ protected override void Dispose(bool disposing) { if (disposing) { - var tracers = Interlocked.CompareExchange(ref this.Tracers, null, this.Tracers); + var tracers = Interlocked.Exchange(ref this.Tracers, null); if (tracers != null) { lock (tracers) @@ -103,11 +118,154 @@ internal readonly record struct TracerKey { public readonly string Name; public readonly string? Version; + public readonly KeyValuePair[]? Tags; - public TracerKey(string? name, string? version) + public TracerKey(string? name, string? version, IEnumerable>? tags) { this.Name = name ?? string.Empty; this.Version = version; + this.Tags = GetOrderedTags(tags); + } + + public bool Equals(TracerKey other) + { + if (!string.Equals(this.Name, other.Name, StringComparison.Ordinal) || + !string.Equals(this.Version, other.Version, StringComparison.Ordinal)) + { + return false; + } + + return AreTagsEqual(this.Tags, other.Tags); + } + + public override int GetHashCode() + { + unchecked + { + var hash = 17; +#if NET + hash = (hash * 31) + (this.Name?.GetHashCode(StringComparison.Ordinal) ?? 0); + hash = (hash * 31) + (this.Version?.GetHashCode(StringComparison.Ordinal) ?? 0); +#else + hash = (hash * 31) + (this.Name?.GetHashCode() ?? 0); + hash = (hash * 31) + (this.Version?.GetHashCode() ?? 0); +#endif + + hash = (hash * 31) + GetTagsHashCode(this.Tags); + return hash; + } + } + + private static bool AreTagsEqual( + KeyValuePair[]? tags1, + KeyValuePair[]? tags2) + { + if (tags1 == null && tags2 == null) + { + return true; + } + + if (tags1 == null || tags2 == null || tags1.Length != tags2.Length) + { + return false; + } + + for (int i = 0; i < tags1.Length; i++) + { + var kvp1 = tags1[i]; + var kvp2 = tags2[i]; + + if (!string.Equals(kvp1.Key, kvp2.Key, StringComparison.Ordinal)) + { + return false; + } + + // Compare values + if (kvp1.Value is null) + { + if (kvp2.Value is not null) + { + return false; + } + } + else + { + if (!kvp1.Value.Equals(kvp2.Value)) + { + return false; + } + } + } + + return true; + } + + private static int GetTagsHashCode( + IEnumerable>? tags) + { + if (tags is null) + { + return 0; + } + + var hash = 0; + unchecked + { + foreach (var kvp in tags) + { +#if NET + hash = (hash * 31) + kvp.Key.GetHashCode(StringComparison.Ordinal); +#else + hash = (hash * 31) + kvp.Key.GetHashCode(); +#endif + if (kvp.Value != null) + { + hash = (hash * 31) + kvp.Value.GetHashCode()!; + } + } + } + + return hash; + } + + private static KeyValuePair[]? GetOrderedTags( + IEnumerable>? tags) + { + if (tags is null) + { + return null; + } + + var orderedTagList = new List>(tags); + orderedTagList.Sort((left, right) => + { + // First compare by key + int keyComparison = string.Compare(left.Key, right.Key, StringComparison.Ordinal); + if (keyComparison != 0) + { + return keyComparison; + } + + // If keys are equal, compare by value + if (left.Value == null && right.Value == null) + { + return 0; + } + + if (left.Value == null) + { + return -1; + } + + if (right.Value == null) + { + return 1; + } + + // Both values are non-null, compare as strings + return string.Compare(left.Value.ToString(), right.Value.ToString(), StringComparison.Ordinal); + }); + return [.. orderedTagList]; } } } diff --git a/src/OpenTelemetry.Exporter.Console/CHANGELOG.md b/src/OpenTelemetry.Exporter.Console/CHANGELOG.md index 8bf439e1846..8e2ee12d281 100644 --- a/src/OpenTelemetry.Exporter.Console/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Console/CHANGELOG.md @@ -6,6 +6,18 @@ Notes](../../RELEASENOTES.md). ## Unreleased +## 1.13.0 + +Released 2025-Oct-01 + +## 1.12.0 + +Released 2025-Apr-29 + +## 1.11.2 + +Released 2025-Mar-04 + ## 1.11.1 Released 2025-Jan-22 diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleExporterLoggingExtensions.cs b/src/OpenTelemetry.Exporter.Console/ConsoleExporterLoggingExtensions.cs index 498aa3a926f..d8d04913496 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleExporterLoggingExtensions.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleExporterLoggingExtensions.cs @@ -32,7 +32,9 @@ public static OpenTelemetryLoggerOptions AddConsoleExporter(this OpenTelemetryLo var options = new ConsoleExporterOptions(); configure?.Invoke(options); +#pragma warning disable CA2000 // Dispose objects before losing scope return loggerOptions.AddProcessor(new SimpleLogRecordExportProcessor(new ConsoleLogRecordExporter(options))); +#pragma warning restore CA2000 // Dispose objects before losing scope } /// diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricsExtensions.cs b/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricsExtensions.cs index 78ee90829d9..3d9bf9560f5 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricsExtensions.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricsExtensions.cs @@ -104,11 +104,13 @@ public static MeterProviderBuilder AddConsoleExporter( }); } - private static MetricReader BuildConsoleExporterMetricReader( + private static PeriodicExportingMetricReader BuildConsoleExporterMetricReader( ConsoleExporterOptions exporterOptions, MetricReaderOptions metricReaderOptions) { +#pragma warning disable CA2000 // Dispose objects before losing scope var metricExporter = new ConsoleMetricExporter(exporterOptions); +#pragma warning restore CA2000 // Dispose objects before losing scope return PeriodicExportingMetricReaderHelper.CreatePeriodicExportingMetricReader( metricExporter, diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs index 0dfe396b30e..84fd449532b 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs @@ -38,7 +38,7 @@ public override ExportResult Export(in Batch batch) this.WriteLine("The console exporter is still being invoked after it has been disposed. This could be due to the application's incorrect lifecycle management of the LoggerFactory/OpenTelemetry .NET SDK."); this.WriteLine(Environment.StackTrace); - this.WriteLine(Environment.NewLine + "Dispose was called on the following stack trace:"); + this.WriteLine($"{Environment.NewLine}Dispose was called on the following stack trace:"); this.WriteLine(this.disposedStackTrace!); } @@ -89,7 +89,7 @@ public override ExportResult Export(in Batch batch) // Special casing {OriginalFormat} // See https://github.com/open-telemetry/opentelemetry-dotnet/pull/3182 // for explanation. - var valueToTransform = logRecord.Attributes[i].Key.Equals("{OriginalFormat}") + var valueToTransform = logRecord.Attributes[i].Key.Equals("{OriginalFormat}", StringComparison.Ordinal) ? new KeyValuePair("OriginalFormat (a.k.a Body)", logRecord.Attributes[i].Value) : logRecord.Attributes[i]; @@ -137,7 +137,7 @@ void ProcessScope(LogRecordScope scope, ConsoleLogRecordExporter exporter) var resource = this.ParentProvider.GetResource(); if (resource != Resource.Empty) { - this.WriteLine("\nResource associated with LogRecord:"); + this.WriteLine($"{Environment.NewLine}Resource associated with LogRecord:"); foreach (var resourceAttribute in resource.Attributes) { if (this.TagWriter.TryTransformTag(resourceAttribute.Key, resourceAttribute.Value, out var result)) diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs index 97ba3bbe25c..c47823b3ad6 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs @@ -17,22 +17,74 @@ public ConsoleMetricExporter(ConsoleExporterOptions options) public override ExportResult Export(in Batch batch) { + // Print Resource information once at the beginning of the batch + var resource = this.ParentProvider.GetResource(); + if (resource != Resource.Empty) + { + this.WriteLine("Resource associated with Metrics:"); + foreach (var resourceAttribute in resource.Attributes) + { + if (this.TagWriter.TryTransformTag(resourceAttribute.Key, resourceAttribute.Value, out var result)) + { + this.WriteLine($"\t{result.Key}: {result.Value}"); + } + } + } + foreach (var metric in batch) { - var msg = new StringBuilder($"\n"); + var msg = new StringBuilder(Environment.NewLine); +#if NET + msg.Append(CultureInfo.InvariantCulture, $"Metric Name: {metric.Name}"); +#else msg.Append($"Metric Name: {metric.Name}"); - if (metric.Description != string.Empty) +#endif + if (!string.IsNullOrEmpty(metric.Description)) { +#if NET + msg.Append(CultureInfo.InvariantCulture, $", Description: {metric.Description}"); +#else msg.Append($", Description: {metric.Description}"); +#endif } - if (metric.Unit != string.Empty) + if (!string.IsNullOrEmpty(metric.Unit)) { +#if NET + msg.Append(CultureInfo.InvariantCulture, $", Unit: {metric.Unit}"); +#else msg.Append($", Unit: {metric.Unit}"); +#endif } +#if NET + msg.Append(CultureInfo.InvariantCulture, $", Metric Type: {metric.MetricType}"); +#else + msg.Append($", Metric Type: {metric.MetricType}"); +#endif + this.WriteLine(msg.ToString()); + // Print Instrumentation scope (Meter) information once per metric + this.WriteLine("Instrumentation scope (Meter):"); + this.WriteLine($"\tName: {metric.MeterName}"); + if (!string.IsNullOrEmpty(metric.MeterVersion)) + { + this.WriteLine($"\tVersion: {metric.MeterVersion}"); + } + + if (metric.MeterTags?.Any() == true) + { + this.WriteLine("\tTags:"); + foreach (var meterTag in metric.MeterTags) + { + if (this.TagWriter.TryTransformTag(meterTag, out var result)) + { + this.WriteLine($"\t\t{result.Key}: {result.Value}"); + } + } + } + foreach (ref readonly var metricPoint in metric.GetMetricPoints()) { string valueDisplay = string.Empty; @@ -41,7 +93,11 @@ public override ExportResult Export(in Batch batch) { if (this.TagWriter.TryTransformTag(tag, out var result)) { +#if NET + tagsBuilder.Append(CultureInfo.InvariantCulture, $"{result.Key}: {result.Value}"); +#else tagsBuilder.Append($"{result.Key}: {result.Value}"); +#endif tagsBuilder.Append(' '); } } @@ -55,10 +111,18 @@ public override ExportResult Export(in Batch batch) var bucketsBuilder = new StringBuilder(); var sum = metricPoint.GetHistogramSum(); var count = metricPoint.GetHistogramCount(); +#if NET + bucketsBuilder.Append(CultureInfo.InvariantCulture, $"Sum: {sum} Count: {count} "); +#else bucketsBuilder.Append($"Sum: {sum} Count: {count} "); +#endif if (metricPoint.TryGetHistogramMinMaxValues(out double min, out double max)) { +#if NET + bucketsBuilder.Append(CultureInfo.InvariantCulture, $"Min: {min} Max: {max} "); +#else bucketsBuilder.Append($"Min: {min} Max: {max} "); +#endif } bucketsBuilder.AppendLine(); @@ -109,7 +173,11 @@ public override ExportResult Export(in Batch batch) if (exponentialHistogramData.ZeroCount != 0) { +#if NET + bucketsBuilder.AppendLine(CultureInfo.InvariantCulture, $"Zero Bucket:{exponentialHistogramData.ZeroCount}"); +#else bucketsBuilder.AppendLine($"Zero Bucket:{exponentialHistogramData.ZeroCount}"); +#endif } var offset = exponentialHistogramData.PositiveBuckets.Offset; @@ -117,7 +185,11 @@ public override ExportResult Export(in Batch batch) { var lowerBound = Base2ExponentialBucketHistogramHelper.CalculateLowerBoundary(offset, scale).ToString(CultureInfo.InvariantCulture); var upperBound = Base2ExponentialBucketHistogramHelper.CalculateLowerBoundary(++offset, scale).ToString(CultureInfo.InvariantCulture); +#if NET + bucketsBuilder.AppendLine(CultureInfo.InvariantCulture, $"({lowerBound}, {upperBound}]:{bucketCount}"); +#else bucketsBuilder.AppendLine($"({lowerBound}, {upperBound}]:{bucketCount}"); +#endif } } @@ -125,25 +197,11 @@ public override ExportResult Export(in Batch batch) } else if (metricType.IsDouble()) { - if (metricType.IsSum()) - { - valueDisplay = metricPoint.GetSumDouble().ToString(CultureInfo.InvariantCulture); - } - else - { - valueDisplay = metricPoint.GetGaugeLastValueDouble().ToString(CultureInfo.InvariantCulture); - } + valueDisplay = metricType.IsSum() ? metricPoint.GetSumDouble().ToString(CultureInfo.InvariantCulture) : metricPoint.GetGaugeLastValueDouble().ToString(CultureInfo.InvariantCulture); } else if (metricType.IsLong()) { - if (metricType.IsSum()) - { - valueDisplay = metricPoint.GetSumLong().ToString(CultureInfo.InvariantCulture); - } - else - { - valueDisplay = metricPoint.GetGaugeLastValueLong().ToString(CultureInfo.InvariantCulture); - } + valueDisplay = metricType.IsSum() ? metricPoint.GetSumLong().ToString(CultureInfo.InvariantCulture) : metricPoint.GetGaugeLastValueLong().ToString(CultureInfo.InvariantCulture); } var exemplarString = new StringBuilder(); @@ -183,7 +241,11 @@ public override ExportResult Export(in Batch batch) appendedTagString = true; } +#if NET + exemplarString.Append(CultureInfo.InvariantCulture, $"{result.Key}: {result.Value}"); +#else exemplarString.Append($"{result.Key}: {result.Value}"); +#endif exemplarString.Append(' '); } } @@ -199,55 +261,26 @@ public override ExportResult Export(in Batch batch) msg.Append(metricPoint.EndTime.ToString("yyyy-MM-ddTHH:mm:ss.fffffffZ", CultureInfo.InvariantCulture)); msg.Append("] "); msg.Append(tags); - if (tags != string.Empty) + if (string.IsNullOrEmpty(tags)) { msg.Append(' '); } - msg.Append(metric.MetricType); msg.AppendLine(); +#if NET + msg.Append(CultureInfo.InvariantCulture, $"Value: {valueDisplay}"); +#else msg.Append($"Value: {valueDisplay}"); +#endif if (exemplarString.Length > 0) { msg.AppendLine(); msg.AppendLine("Exemplars"); - msg.Append(exemplarString.ToString()); + msg.Append(exemplarString); } this.WriteLine(msg.ToString()); - - this.WriteLine("Instrumentation scope (Meter):"); - this.WriteLine($"\tName: {metric.MeterName}"); - if (!string.IsNullOrEmpty(metric.MeterVersion)) - { - this.WriteLine($"\tVersion: {metric.MeterVersion}"); - } - - if (metric.MeterTags?.Any() == true) - { - this.WriteLine("\tTags:"); - foreach (var meterTag in metric.MeterTags) - { - if (this.TagWriter.TryTransformTag(meterTag, out var result)) - { - this.WriteLine($"\t\t{result.Key}: {result.Value}"); - } - } - } - - var resource = this.ParentProvider.GetResource(); - if (resource != Resource.Empty) - { - this.WriteLine("Resource associated with Metric:"); - foreach (var resourceAttribute in resource.Attributes) - { - if (this.TagWriter.TryTransformTag(resourceAttribute.Key, resourceAttribute.Value, out var result)) - { - this.WriteLine($"\t{result.Key}: {result.Value}"); - } - } - } } } diff --git a/src/OpenTelemetry.Exporter.Console/Implementation/ConsoleTagWriter.cs b/src/OpenTelemetry.Exporter.Console/Implementation/ConsoleTagWriter.cs index fcaf9cdb43c..4950bd100e5 100644 --- a/src/OpenTelemetry.Exporter.Console/Implementation/ConsoleTagWriter.cs +++ b/src/OpenTelemetry.Exporter.Console/Implementation/ConsoleTagWriter.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; +using System.Globalization; using System.Text; using OpenTelemetry.Internal; @@ -39,13 +40,13 @@ public bool TryTransformTag(string key, object? value, out KeyValuePair - $(NoWarn),1591 + $(NoWarn),CS1591 diff --git a/src/OpenTelemetry.Exporter.Console/README.md b/src/OpenTelemetry.Exporter.Console/README.md index 07a51a29e52..ef7985bf7ef 100644 --- a/src/OpenTelemetry.Exporter.Console/README.md +++ b/src/OpenTelemetry.Exporter.Console/README.md @@ -7,8 +7,11 @@ The console exporter prints data to the Console window. ConsoleExporter supports exporting logs, metrics and traces. > [!WARNING] -> This component is intended to be used while learning how telemetry data is - created and exported. It is not recommended for any production environment. +> This exporter is intended for debugging and learning purposes. It is not + recommended for production use. The output format is not standardized and can + change at any time. + If a standardized format for exporting telemetry to stdout is desired, upvote on + [this feature request](https://github.com/open-telemetry/opentelemetry-dotnet/issues/5920). ## Installation diff --git a/src/OpenTelemetry.Exporter.InMemory/AssemblyInfo.cs b/src/OpenTelemetry.Exporter.InMemory/AssemblyInfo.cs deleted file mode 100644 index f7775c1b9b8..00000000000 --- a/src/OpenTelemetry.Exporter.InMemory/AssemblyInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -#if !EXPOSE_EXPERIMENTAL_FEATURES -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests" + AssemblyInfo.PublicKey)] -#endif - -#if SIGNED -file static class AssemblyInfo -{ - public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; -} -#else -file static class AssemblyInfo -{ - public const string PublicKey = ""; -} -#endif diff --git a/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md b/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md index 6075e746600..4521a32b130 100644 --- a/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md @@ -6,6 +6,18 @@ Notes](../../RELEASENOTES.md). ## Unreleased +## 1.13.0 + +Released 2025-Oct-01 + +## 1.12.0 + +Released 2025-Apr-29 + +## 1.11.2 + +Released 2025-Mar-04 + ## 1.11.1 Released 2025-Jan-22 diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterHelperExtensions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterHelperExtensions.cs index 3940a846bc3..e8af683c695 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterHelperExtensions.cs @@ -20,6 +20,8 @@ public static TracerProviderBuilder AddInMemoryExporter(this TracerProviderBuild Guard.ThrowIfNull(builder); Guard.ThrowIfNull(exportedItems); +#pragma warning disable CA2000 // Dispose objects before losing scope return builder.AddProcessor(new SimpleActivityExportProcessor(new InMemoryExporter(exportedItems))); +#pragma warning restore CA2000 // Dispose objects before losing scope } } diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs index 19b2079921e..2b665f7a293 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs @@ -6,7 +6,9 @@ namespace OpenTelemetry.Logs; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public static class InMemoryExporterLoggingExtensions +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { /// /// Adds InMemory exporter to the OpenTelemetryLoggerOptions. @@ -22,10 +24,12 @@ public static OpenTelemetryLoggerOptions AddInMemoryExporter( Guard.ThrowIfNull(loggerOptions); Guard.ThrowIfNull(exportedItems); +#pragma warning disable CA2000 // Dispose objects before losing scope var logExporter = BuildExporter(exportedItems); return loggerOptions.AddProcessor( new SimpleLogRecordExportProcessor(logExporter)); +#pragma warning restore CA2000 // Dispose objects before losing scope } /// @@ -41,10 +45,12 @@ public static LoggerProviderBuilder AddInMemoryExporter( Guard.ThrowIfNull(loggerProviderBuilder); Guard.ThrowIfNull(exportedItems); +#pragma warning disable CA2000 // Dispose objects before losing scope var logExporter = BuildExporter(exportedItems); return loggerProviderBuilder.AddProcessor( new SimpleLogRecordExportProcessor(logExporter)); +#pragma warning restore CA2000 // Dispose objects before losing scope } private static InMemoryExporter BuildExporter(ICollection exportedItems) diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs index da5ecda24d9..a9cc18eb029 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs @@ -147,11 +147,13 @@ public static MeterProviderBuilder AddInMemoryExporter( }); } - private static MetricReader BuildInMemoryExporterMetricReader( + private static PeriodicExportingMetricReader BuildInMemoryExporterMetricReader( ICollection exportedItems, MetricReaderOptions metricReaderOptions) { +#pragma warning disable CA2000 // Dispose objects before losing scope var metricExporter = new InMemoryExporter(exportedItems); +#pragma warning restore CA2000 // Dispose objects before losing scope return PeriodicExportingMetricReaderHelper.CreatePeriodicExportingMetricReader( metricExporter, @@ -160,12 +162,14 @@ private static MetricReader BuildInMemoryExporterMetricReader( DefaultExportTimeoutMilliseconds); } - private static MetricReader BuildInMemoryExporterMetricReader( + private static PeriodicExportingMetricReader BuildInMemoryExporterMetricReader( ICollection exportedItems, MetricReaderOptions metricReaderOptions) { +#pragma warning disable CA2000 // Dispose objects before losing scope var metricExporter = new InMemoryExporter( exportFunc: (in Batch metricBatch) => ExportMetricSnapshot(in metricBatch, exportedItems)); +#pragma warning restore CA2000 // Dispose objects before losing scope return PeriodicExportingMetricReaderHelper.CreatePeriodicExportingMetricReader( metricExporter, diff --git a/src/OpenTelemetry.Exporter.InMemory/MetricSnapshot.cs b/src/OpenTelemetry.Exporter.InMemory/MetricSnapshot.cs index 983cf7bb2b8..fb4bcc5c155 100644 --- a/src/OpenTelemetry.Exporter.InMemory/MetricSnapshot.cs +++ b/src/OpenTelemetry.Exporter.InMemory/MetricSnapshot.cs @@ -1,6 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using OpenTelemetry.Internal; + namespace OpenTelemetry.Metrics; /// @@ -14,10 +16,11 @@ public class MetricSnapshot public MetricSnapshot(Metric metric) { + Guard.ThrowIfNull(metric); this.instrumentIdentity = metric.InstrumentIdentity; this.MetricType = metric.MetricType; - List metricPoints = new(); + List metricPoints = []; foreach (ref readonly var metricPoint in metric.GetMetricPoints()) { metricPoints.Add(metricPoint.Copy()); diff --git a/src/OpenTelemetry.Exporter.InMemory/OpenTelemetry.Exporter.InMemory.csproj b/src/OpenTelemetry.Exporter.InMemory/OpenTelemetry.Exporter.InMemory.csproj index 8ca97988f64..5d6f89a7391 100644 --- a/src/OpenTelemetry.Exporter.InMemory/OpenTelemetry.Exporter.InMemory.csproj +++ b/src/OpenTelemetry.Exporter.InMemory/OpenTelemetry.Exporter.InMemory.csproj @@ -1,4 +1,4 @@ - + $(TargetFrameworksForLibraries) @@ -8,7 +8,7 @@ - $(NoWarn),1591 + $(NoWarn),CS1591 @@ -19,4 +19,8 @@ + + + + diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/AssemblyInfo.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/AssemblyInfo.cs deleted file mode 100644 index 3f125304deb..00000000000 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/AssemblyInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Runtime.CompilerServices; - -#if SIGNED -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -[assembly: InternalsVisibleTo("Benchmarks, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -[assembly: InternalsVisibleTo("MockOpenTelemetryCollector, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -#else -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests")] -[assembly: InternalsVisibleTo("Benchmarks")] -[assembly: InternalsVisibleTo("MockOpenTelemetryCollector")] -#endif diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OpenTelemetryBuilderOtlpExporterExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OpenTelemetryBuilderOtlpExporterExtensions.cs index e0e999b3e72..baaceb07165 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OpenTelemetryBuilderOtlpExporterExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OpenTelemetryBuilderOtlpExporterExtensions.cs @@ -36,7 +36,9 @@ public static class OpenTelemetryBuilderOtlpExporterExtensions /// Supplied for chaining calls. public static IOpenTelemetryBuilder UseOtlpExporter( this IOpenTelemetryBuilder builder) +#pragma warning disable CA1062 // Validate arguments of public methods => UseOtlpExporter(builder, name: null, configuration: null, configure: null); +#pragma warning restore CA1062 // Validate arguments of public methods /// /// @@ -56,7 +58,9 @@ public static IOpenTelemetryBuilder UseOtlpExporter( { Guard.ThrowIfNull(baseUrl); +#pragma warning disable CA1062 // Validate arguments of public methods return UseOtlpExporter(builder, name: null, configuration: null, configure: otlpBuilder => +#pragma warning restore CA1062 // Validate arguments of public methods { otlpBuilder.ConfigureDefaultExporterOptions(o => { diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index a3f0013d6ed..79d6d1f3b10 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -7,13 +7,68 @@ Notes](../../RELEASENOTES.md). ## Unreleased +## 1.13.0 + +Released 2025-Oct-01 + +* Fixed an issue in .NET Framework where OTLP export of traces, logs, and + metrics using `OtlpExportProtocol.Grpc` did not correctly set the initial + write position, resulting in gRPC protocol errors. + ([#6280](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6280)) + +* If `EventName` is specified either through `ILogger` or the experimental + log bridge API, it is exported as `EventName` by default instead of + `logrecord.event.name` which was previously behind the + `OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES` feature flag. + Note that exporting `logrecord.event.id` is still behind that same feature + flag. ([#6306](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6306)) + +* gRPC calls to export traces, logs, and metrics using `OtlpExportProtocol.Grpc` + now set the `TE=trailers` HTTP request header to improve interoperability. + ([#6449](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6449)) + +* Improved performance exporting `byte[]` attributes as native binary format + instead of arrays. + ([#6534](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6534)) + +## 1.12.0 + +Released 2025-Apr-29 + +* **Breaking Change**: .NET Framework and .NET Standard builds now default to + exporting over OTLP/HTTP instead of OTLP/gRPC. **This change could result in a + failure to export telemetry unless appropriate measures are taken.** + Additionally, if you explicitly configure the exporter to use OTLP/gRPC it may + result in a `NotSupportedException` without further configuration. Please + carefully review issue + ([#6209](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6209)) + for additional information and workarounds. + ([#6229](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6229)) + +## 1.11.2 + +Released 2025-Mar-04 + +* Fixed a bug in .NET Framework gRPC export client where the default success + export response was incorrectly marked as false, now changed to true, ensuring + exports are correctly marked as successful. + ([#6099](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6099)) + +* Fixed an issues causing trace exports to fail when + `Activity.StatusDescription` exceeds 127 bytes. + ([#6119](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6119)) + +* Fixed incorrect log serialization of attributes with null values, causing + some backends to reject logs. + ([#6149](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6149)) + ## 1.11.1 Released 2025-Jan-22 * Fixed an issue where the OTLP gRPC exporter did not export logs, metrics, or traces in .NET Framework projects. - ([#6067](https://github.com/open-telemetry/opentelemetry-dotnet/issues/6067)) + ([#6083](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6083)) ## 1.11.0 diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExperimentalOptions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExperimentalOptions.cs index 25b345ac96e..0470b2d8a23 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExperimentalOptions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExperimentalOptions.cs @@ -9,8 +9,6 @@ internal sealed class ExperimentalOptions { public const string LogRecordEventIdAttribute = "logrecord.event.id"; - public const string LogRecordEventNameAttribute = "logrecord.event.name"; - public const string EmitLogEventEnvVar = "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES"; public const string OtlpRetryEnvVar = "OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY"; @@ -29,7 +27,7 @@ public ExperimentalOptions(IConfiguration configuration) this.EmitLogEventAttributes = emitLogEventAttributes; } - if (configuration.TryGetStringValue(OtlpRetryEnvVar, out var retryPolicy) && retryPolicy != null) + if (configuration.TryGetStringValue(OtlpRetryEnvVar, out var retryPolicy)) { if (retryPolicy.Equals("in_memory", StringComparison.OrdinalIgnoreCase)) { @@ -38,7 +36,7 @@ public ExperimentalOptions(IConfiguration configuration) else if (retryPolicy.Equals("disk", StringComparison.OrdinalIgnoreCase)) { this.EnableDiskRetry = true; - if (configuration.TryGetStringValue(OtlpDiskRetryDirectoryPathEnvVar, out var path) && path != null) + if (configuration.TryGetStringValue(OtlpDiskRetryDirectoryPathEnvVar, out var path)) { this.DiskRetryDirectoryPath = path; } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/.editorconfig b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/.editorconfig new file mode 100644 index 00000000000..c895abfaeb0 --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/.editorconfig @@ -0,0 +1,2 @@ +[*.cs] +generated_code = true diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/GrpcProtocolHelpers.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/GrpcProtocolHelpers.cs index 29a0a1b4d39..599bfc4a091 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/GrpcProtocolHelpers.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/GrpcProtocolHelpers.cs @@ -1,6 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +#nullable enable + // Copyright 2019 The gRPC Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/GrpcStatusDeserializer.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/GrpcStatusDeserializer.cs index 57efec46b89..3518a0f658d 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/GrpcStatusDeserializer.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/GrpcStatusDeserializer.cs @@ -1,6 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +#nullable enable + using System.Text; namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc; @@ -199,8 +201,8 @@ private static long DecodeVarint(Stream stream) throw new EndOfStreamException(); } - result |= (long)(b & 0x7F) << shift; - if ((b & 0x80) == 0) + result |= (long)(b & 0b_0111_1111) << shift; + if ((b & 0b_1000_0000) == 0) { return result; } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/Status.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/Status.cs index 89445891970..3dc5c3a7f2e 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/Status.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/Status.cs @@ -1,6 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +#nullable enable + // Copyright 2015 gRPC authors. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/StatusCode.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/StatusCode.cs index 4c64491c30e..4ab8fefa232 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/StatusCode.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/StatusCode.cs @@ -1,6 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +#nullable enable + // Copyright 2015 gRPC authors. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/TrailingHeadersHelpers.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/TrailingHeadersHelpers.cs index 6d18a8fe386..a154014d17b 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/TrailingHeadersHelpers.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/Grpc/TrailingHeadersHelpers.cs @@ -1,6 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +#nullable enable + // Copyright 2019 The gRPC Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/GrpcExportClient.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/GrpcExportClient.cs deleted file mode 100644 index 6803890f75a..00000000000 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/GrpcExportClient.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -#if NET462_OR_GREATER || NETSTANDARD2_0 -using Grpc.Core; -using OpenTelemetry.Internal; - -using InternalStatus = OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc.Status; -using InternalStatusCode = OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc.StatusCode; -using Status = Grpc.Core.Status; -using StatusCode = Grpc.Core.StatusCode; - -namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; - -internal sealed class GrpcExportClient : IExportClient -{ - private static readonly ExportClientGrpcResponse SuccessExportResponse = new( - success: false, - deadlineUtc: default, - exception: null, - status: null, - grpcStatusDetailsHeader: null); - - private static readonly Marshaller ByteArrayMarshaller = Marshallers.Create( - serializer: static input => input, - deserializer: static data => data); - - private readonly Method exportMethod; - - private readonly CallInvoker callInvoker; - - public GrpcExportClient(OtlpExporterOptions options, string signalPath) - { - Guard.ThrowIfNull(options); - Guard.ThrowIfInvalidTimeout(options.TimeoutMilliseconds); - Guard.ThrowIfNull(signalPath); - - var exporterEndpoint = options.Endpoint.AppendPathIfNotPresent(signalPath); - this.Endpoint = new UriBuilder(exporterEndpoint).Uri; - this.Channel = options.CreateChannel(); - this.Headers = options.GetMetadataFromHeaders(); - - var serviceAndMethod = signalPath.Split('/'); - this.exportMethod = new Method(MethodType.Unary, serviceAndMethod[0], serviceAndMethod[1], ByteArrayMarshaller, ByteArrayMarshaller); - this.callInvoker = this.Channel.CreateCallInvoker(); - } - - internal Channel Channel { get; } - - internal Uri Endpoint { get; } - - internal Metadata Headers { get; } - - public ExportClientResponse SendExportRequest(byte[] buffer, int contentLength, DateTime deadlineUtc, CancellationToken cancellationToken = default) - { - try - { - var contentSpan = buffer.AsSpan(0, contentLength); - this.callInvoker?.BlockingUnaryCall(this.exportMethod, null, new CallOptions(this.Headers, deadlineUtc, cancellationToken), contentSpan.ToArray()); - return SuccessExportResponse; - } - catch (RpcException rpcException) - { - OpenTelemetryProtocolExporterEventSource.Log.FailedToReachCollector(this.Endpoint, rpcException); - return new ExportClientGrpcResponse(success: false, deadlineUtc: deadlineUtc, exception: rpcException, ConvertGrpcStatusToStatus(rpcException.Status), rpcException.Trailers.ToString()); - } - } - - public bool Shutdown(int timeoutMilliseconds) - { - if (this.Channel == null) - { - return true; - } - - if (timeoutMilliseconds == -1) - { - this.Channel.ShutdownAsync().Wait(); - return true; - } - else - { - return Task.WaitAny([this.Channel.ShutdownAsync(), Task.Delay(timeoutMilliseconds)]) == 0; - } - } - - private static InternalStatus ConvertGrpcStatusToStatus(Status grpcStatus) => grpcStatus.StatusCode switch - { - StatusCode.OK => new InternalStatus(InternalStatusCode.OK, grpcStatus.Detail), - StatusCode.Cancelled => new InternalStatus(InternalStatusCode.Cancelled, grpcStatus.Detail), - StatusCode.Unknown => new InternalStatus(InternalStatusCode.Unknown, grpcStatus.Detail), - StatusCode.InvalidArgument => new InternalStatus(InternalStatusCode.InvalidArgument, grpcStatus.Detail), - StatusCode.DeadlineExceeded => new InternalStatus(InternalStatusCode.DeadlineExceeded, grpcStatus.Detail), - StatusCode.NotFound => new InternalStatus(InternalStatusCode.NotFound, grpcStatus.Detail), - StatusCode.AlreadyExists => new InternalStatus(InternalStatusCode.AlreadyExists, grpcStatus.Detail), - StatusCode.PermissionDenied => new InternalStatus(InternalStatusCode.PermissionDenied, grpcStatus.Detail), - StatusCode.Unauthenticated => new InternalStatus(InternalStatusCode.Unauthenticated, grpcStatus.Detail), - StatusCode.ResourceExhausted => new InternalStatus(InternalStatusCode.ResourceExhausted, grpcStatus.Detail), - StatusCode.FailedPrecondition => new InternalStatus(InternalStatusCode.FailedPrecondition, grpcStatus.Detail), - StatusCode.Aborted => new InternalStatus(InternalStatusCode.Aborted, grpcStatus.Detail), - StatusCode.OutOfRange => new InternalStatus(InternalStatusCode.OutOfRange, grpcStatus.Detail), - StatusCode.Unimplemented => new InternalStatus(InternalStatusCode.Unimplemented, grpcStatus.Detail), - StatusCode.Internal => new InternalStatus(InternalStatusCode.Internal, grpcStatus.Detail), - StatusCode.Unavailable => new InternalStatus(InternalStatusCode.Unavailable, grpcStatus.Detail), - StatusCode.DataLoss => new InternalStatus(InternalStatusCode.DataLoss, grpcStatus.Detail), - _ => new InternalStatus(InternalStatusCode.Unknown, grpcStatus.Detail), - }; -} -#endif diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpExportClient.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpExportClient.cs index 7347c69b271..24fc1551cc9 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpExportClient.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpExportClient.cs @@ -14,19 +14,12 @@ internal abstract class OtlpExportClient : IExportClient private static readonly Version Http2RequestVersion = new(2, 0); #if NET - private static readonly bool SynchronousSendSupportedByCurrentPlatform; - - static OtlpExportClient() - { -#if NET - // See: https://github.com/dotnet/runtime/blob/280f2a0c60ce0378b8db49adc0eecc463d00fe5d/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.AnyMobile.cs#L767 - SynchronousSendSupportedByCurrentPlatform = !OperatingSystem.IsAndroid() + // See: https://github.com/dotnet/runtime/blob/280f2a0c60ce0378b8db49adc0eecc463d00fe5d/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.AnyMobile.cs#L767 + private static readonly bool SynchronousSendSupportedByCurrentPlatform = !OperatingSystem.IsAndroid() && !OperatingSystem.IsIOS() && !OperatingSystem.IsTvOS() && !OperatingSystem.IsBrowser(); #endif - } -#endif protected OtlpExportClient(OtlpExporterOptions options, HttpClient httpClient, string signalPath) { @@ -35,7 +28,9 @@ protected OtlpExportClient(OtlpExporterOptions options, HttpClient httpClient, s Guard.ThrowIfNull(signalPath); Uri exporterEndpoint; +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning if (options.Protocol == OtlpExportProtocol.Grpc) +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning { exporterEndpoint = options.Endpoint.AppendPathIfNotPresent(signalPath); } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpGrpcExportClient.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpGrpcExportClient.cs index 1e645c2559f..31b7d0e9bb2 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpGrpcExportClient.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpGrpcExportClient.cs @@ -39,6 +39,11 @@ public override ExportClientResponse SendExportRequest(byte[] buffer, int conten try { using var httpRequest = this.CreateHttpRequest(buffer, contentLength); + + // TE is required by some servers, e.g. C Core. + // A missing TE header results in servers aborting the gRPC call. + httpRequest.Headers.TryAddWithoutValidation("TE", "trailers"); + using var httpResponse = this.SendHttpRequest(httpRequest, cancellationToken); httpResponse.EnsureSuccessStatusCode(); @@ -46,7 +51,7 @@ public override ExportClientResponse SendExportRequest(byte[] buffer, int conten var trailingHeaders = httpResponse.TrailingHeaders(); Status status = GrpcProtocolHelpers.GetResponseStatus(httpResponse, trailingHeaders); - if (status.Detail.Equals(Status.NoReplyDetailMessage)) + if (status.Detail.Equals(Status.NoReplyDetailMessage, StringComparison.Ordinal)) { #if NET using var responseStream = httpResponse.Content.ReadAsStream(cancellationToken); @@ -93,7 +98,7 @@ public override ExportClientResponse SendExportRequest(byte[] buffer, int conten grpcStatusDetailsHeader = GrpcProtocolHelpers.GetHeaderValue(trailingHeaders, GrpcStatusDetailsHeader); } - OpenTelemetryProtocolExporterEventSource.Log.ExportFailure(this.Endpoint.ToString(), "Export failed due to unexpected status code."); + OpenTelemetryProtocolExporterEventSource.Log.ExportFailure(this.Endpoint, "Export failed due to unexpected status code.", status); return new ExportClientGrpcResponse( success: false, @@ -158,6 +163,7 @@ private static bool IsTransientNetworkError(HttpRequestException ex) return ex.InnerException is System.Net.Sockets.SocketException socketEx && (socketEx.SocketErrorCode == System.Net.Sockets.SocketError.TimedOut || socketEx.SocketErrorCode == System.Net.Sockets.SocketError.ConnectionReset - || socketEx.SocketErrorCode == System.Net.Sockets.SocketError.HostUnreachable); + || socketEx.SocketErrorCode == System.Net.Sockets.SocketError.HostUnreachable + || socketEx.SocketErrorCode == System.Net.Sockets.SocketError.ConnectionRefused); } } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpRetry.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpRetry.cs index 89afecb4e9d..b4617a1fd42 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpRetry.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpRetry.cs @@ -3,6 +3,9 @@ using System.Net; using System.Net.Http.Headers; +#if NET +using System.Security.Cryptography; +#endif using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc; namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; @@ -40,13 +43,12 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClie /// internal static class OtlpRetry { - public const string GrpcStatusDetailsHeader = "grpc-status-details-bin"; public const int InitialBackoffMilliseconds = 1000; private const int MaxBackoffMilliseconds = 5000; private const double BackoffMultiplier = 1.5; #if !NET - private static readonly Random Random = new Random(); + private static readonly Random Random = new(); #endif public static bool TryGetHttpRetryResult(ExportClientHttpResponse response, int retryDelayInMilliSeconds, out RetryResult retryResult) @@ -156,9 +158,7 @@ private static bool TryGetRetryResult(TStatusCode statusC return false; } - var delayDuration = throttleDelay.HasValue - ? throttleDelay.Value - : TimeSpan.FromMilliseconds(GetRandomNumber(0, nextRetryDelayMilliseconds)); + var delayDuration = throttleDelay ?? TimeSpan.FromMilliseconds(GetRandomNumber(0, nextRetryDelayMilliseconds)); if (deadline.HasValue && IsDeadlineExceeded(deadline + delayDuration)) { @@ -246,13 +246,15 @@ private static bool IsHttpStatusCodeRetryable(HttpStatusCode statusCode, bool ha private static int GetRandomNumber(int min, int max) { #if NET - return Random.Shared.Next(min, max); + return RandomNumberGenerator.GetInt32(min, max); #else // TODO: Implement this better to minimize lock contention. // Consider pulling in Random.Shared implementation. lock (Random) { +#pragma warning disable CA5394 // Do not use insecure randomness return Random.Next(min, max); +#pragma warning restore CA5394 // Do not use insecure randomness } #endif } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OpenTelemetryProtocolExporterEventSource.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OpenTelemetryProtocolExporterEventSource.cs index 3596b79fa28..c4d21d92370 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OpenTelemetryProtocolExporterEventSource.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OpenTelemetryProtocolExporterEventSource.cs @@ -3,6 +3,7 @@ using System.Diagnostics.Tracing; using Microsoft.Extensions.Configuration; +using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc; using OpenTelemetry.Internal; namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; @@ -94,6 +95,15 @@ public void GrpcRetryDelayParsingFailed(string? grpcStatusDetailsHeader, Excepti } } + [NonEvent] + public void ExportFailure(Uri endpoint, string message, Status status) + { + if (Log.IsEnabled(EventLevel.Error, EventKeywords.All)) + { + this.ExportFailure(endpoint.ToString(), message, status.ToString()); + } + } + [Event(2, Message = "Exporter failed send data to collector to {0} endpoint. Data will not be sent. Exception: {1}", Level = EventLevel.Error)] public void FailedToReachCollector(string rawCollectorUri, string ex) { @@ -208,10 +218,10 @@ public void GrpcStatusWarning(string endpoint, string statusCode) this.WriteEvent(22, endpoint, statusCode); } - [Event(23, Message = "Export failed for {0}. Message: {1}", Level = EventLevel.Error)] - public void ExportFailure(string endpoint, string message) + [Event(23, Message = "Export failed for {0}. Message: {1}. {2}.", Level = EventLevel.Error)] + public void ExportFailure(string endpoint, string message, string statusString) { - this.WriteEvent(23, endpoint, message); + this.WriteEvent(23, endpoint, message, statusString); } [Event(24, Message = "Failed to parse gRPC retry delay from header grpcStatusDetailsHeader: '{0}'. Exception: {1}", Level = EventLevel.Warning)] diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpLogFieldNumberConstants.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpLogFieldNumberConstants.cs index f7c97f6e222..c5238b2fa6b 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpLogFieldNumberConstants.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpLogFieldNumberConstants.cs @@ -36,6 +36,7 @@ internal static class ProtobufOtlpLogFieldNumberConstants internal const int LogRecord_Flags = 8; internal const int LogRecord_Trace_Id = 9; internal const int LogRecord_Span_Id = 10; + internal const int LogRecord_Event_Name = 12; // SeverityNumber internal const int Severity_Number_Unspecified = 0; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpLogSerializer.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpLogSerializer.cs index e95ef92812c..c6304b2324e 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpLogSerializer.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpLogSerializer.cs @@ -56,30 +56,38 @@ internal static int WriteLogsData(ref byte[] buffer, int writePosition, SdkLimit internal static int TryWriteResourceLogs(ref byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, ExperimentalOptions experimentalOptions, Resources.Resource? resource, Dictionary> scopeLogs) { - int entryWritePosition = writePosition; - - try + while (true) { - writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, ProtobufOtlpLogFieldNumberConstants.LogsData_Resource_Logs, ProtobufWireType.LEN); - int logsDataLengthPosition = writePosition; - writePosition += ReserveSizeForLength; - - writePosition = WriteResourceLogs(buffer, writePosition, sdkLimitOptions, experimentalOptions, resource, scopeLogs); + int entryWritePosition = writePosition; - ProtobufSerializer.WriteReservedLength(buffer, logsDataLengthPosition, writePosition - (logsDataLengthPosition + ReserveSizeForLength)); - } - catch (Exception ex) when (ex is IndexOutOfRangeException || ex is ArgumentException) - { - writePosition = entryWritePosition; - if (!ProtobufSerializer.IncreaseBufferSize(ref buffer, OtlpSignalType.Logs)) + try { - throw; + writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, ProtobufOtlpLogFieldNumberConstants.LogsData_Resource_Logs, ProtobufWireType.LEN); + int logsDataLengthPosition = writePosition; + writePosition += ReserveSizeForLength; + + writePosition = WriteResourceLogs(buffer, writePosition, sdkLimitOptions, experimentalOptions, resource, scopeLogs); + + ProtobufSerializer.WriteReservedLength(buffer, logsDataLengthPosition, writePosition - (logsDataLengthPosition + ReserveSizeForLength)); + + // Serialization succeeded, return the final write position + return writePosition; } + catch (Exception ex) when (ex is IndexOutOfRangeException || ex is ArgumentException) + { + // Reset write position and attempt to increase the buffer size + writePosition = entryWritePosition; - return TryWriteResourceLogs(ref buffer, writePosition, sdkLimitOptions, experimentalOptions, resource, scopeLogs); - } + if (!ProtobufSerializer.IncreaseBufferSize(ref buffer, OtlpSignalType.Logs)) + { + throw; + } - return writePosition; + // Continue the loop to retry serialization with the larger buffer + // The loop is limited by the buffer size expansion logic in IncreaseBufferSize, + // which stops at a maximum of 100 MB, ensuring this doesn't become an infinite loop + } + } } internal static void ReturnLogRecordListToPool() @@ -192,11 +200,6 @@ internal static int WriteLogRecord(byte[] buffer, int writePosition, SdkLimitOpt { AddLogAttribute(state, ExperimentalOptions.LogRecordEventIdAttribute, logRecord.EventId.Id); } - - if (!string.IsNullOrEmpty(logRecord.EventId.Name)) - { - AddLogAttribute(state, ExperimentalOptions.LogRecordEventNameAttribute, logRecord.EventId.Name!); - } } if (logRecord.Exception != null) @@ -223,7 +226,7 @@ internal static int WriteLogRecord(byte[] buffer, int writePosition, SdkLimitOpt // Special casing {OriginalFormat} // See https://github.com/open-telemetry/opentelemetry-dotnet/pull/3182 // for explanation. - if (attribute.Key.Equals("{OriginalFormat}") && !bodyPopulatedFromFormattedMessage) + if (attribute.Key.Equals("{OriginalFormat}", StringComparison.Ordinal) && !bodyPopulatedFromFormattedMessage) { otlpTagWriterState.WritePosition = WriteLogRecordBody(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, (attribute.Value as string).AsSpan()); isLogRecordBodySet = true; @@ -254,6 +257,11 @@ internal static int WriteLogRecord(byte[] buffer, int writePosition, SdkLimitOpt otlpTagWriterState.WritePosition = ProtobufSerializer.WriteFixed32WithTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpLogFieldNumberConstants.LogRecord_Flags, (uint)logRecord.TraceFlags); } + if (logRecord.EventId.Name != null) + { + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteStringWithTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpLogFieldNumberConstants.LogRecord_Event_Name, logRecord.EventId.Name!); + } + logRecord.ForEachScope(ProcessScope, state); if (otlpTagWriterState.DroppedTagCount > 0) @@ -270,7 +278,7 @@ static void ProcessScope(LogRecordScope scope, SerializationState state) { foreach (var scopeItem in scope) { - if (scopeItem.Key.Equals("{OriginalFormat}") || string.IsNullOrEmpty(scopeItem.Key)) + if (scopeItem.Key.Equals("{OriginalFormat}", StringComparison.Ordinal) || string.IsNullOrEmpty(scopeItem.Key)) { // Ignore if the scope key is empty. // Ignore if the scope key is {OriginalFormat} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpMetricSerializer.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpMetricSerializer.cs index 5064e9cd122..47873bc4c83 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpMetricSerializer.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpMetricSerializer.cs @@ -29,7 +29,7 @@ internal static int WriteMetricsData(ref byte[] buffer, int writePosition, Resou var metricName = metric.MeterName; if (!scopeMetricsList.TryGetValue(metricName, out var metrics)) { - metrics = metricListPool.Count > 0 ? metricListPool.Pop() : new List(); + metrics = metricListPool.Count > 0 ? metricListPool.Pop() : []; scopeMetricsList[metricName] = metrics; } @@ -44,30 +44,34 @@ internal static int WriteMetricsData(ref byte[] buffer, int writePosition, Resou internal static int TryWriteResourceMetrics(ref byte[] buffer, int writePosition, Resources.Resource? resource, Dictionary> scopeMetrics) { - int entryWritePosition = writePosition; - - try + while (true) { - writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, ProtobufOtlpMetricFieldNumberConstants.MetricsData_Resource_Metrics, ProtobufWireType.LEN); - int mericsDataLengthPosition = writePosition; - writePosition += ReserveSizeForLength; - - writePosition = WriteResourceMetrics(buffer, writePosition, resource, scopeMetrics); + int entryWritePosition = writePosition; - ProtobufSerializer.WriteReservedLength(buffer, mericsDataLengthPosition, writePosition - (mericsDataLengthPosition + ReserveSizeForLength)); - } - catch (Exception ex) when (ex is IndexOutOfRangeException || ex is ArgumentException) - { - writePosition = entryWritePosition; - if (!ProtobufSerializer.IncreaseBufferSize(ref buffer, OtlpSignalType.Metrics)) + try { - throw; + writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, ProtobufOtlpMetricFieldNumberConstants.MetricsData_Resource_Metrics, ProtobufWireType.LEN); + int mericsDataLengthPosition = writePosition; + writePosition += ReserveSizeForLength; + + writePosition = WriteResourceMetrics(buffer, writePosition, resource, scopeMetrics); + + ProtobufSerializer.WriteReservedLength(buffer, mericsDataLengthPosition, writePosition - (mericsDataLengthPosition + ReserveSizeForLength)); + + // Serialization succeeded, return the final write position + return writePosition; } + catch (Exception ex) when (ex is IndexOutOfRangeException || ex is ArgumentException) + { + // Reset write position and attempt to increase the buffer size + writePosition = entryWritePosition; - return TryWriteResourceMetrics(ref buffer, writePosition, resource, scopeMetrics); + if (!ProtobufSerializer.IncreaseBufferSize(ref buffer, OtlpSignalType.Metrics)) + { + throw; + } + } } - - return writePosition; } private static void ReturnMetricListToPool() @@ -117,7 +121,7 @@ private static int WriteScopeMetric(byte[] buffer, int writePosition, string met int instrumentationScopeLengthPosition = writePosition; writePosition += ReserveSizeForLength; - Debug.Assert(metrics.Any(), "Metrics collection is not expected to be empty."); + Debug.Assert(metrics.Count > 0, "Metrics collection is not expected to be empty."); var meterVersion = metrics[0].MeterVersion; var meterTags = metrics[0].MeterTags; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTagWriter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTagWriter.cs index 1002f90e7c7..04e60fa0db7 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTagWriter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTagWriter.cs @@ -81,6 +81,27 @@ protected override void OnUnsupportedTagDropped( tagValueTypeFullName, tagKey); + protected override bool TryWriteEmptyTag(ref OtlpTagWriterState state, string key, object? value) + { + state.WritePosition = ProtobufSerializer.WriteStringWithTag(state.Buffer, state.WritePosition, ProtobufOtlpCommonFieldNumberConstants.KeyValue_Key, key); + state.WritePosition = ProtobufSerializer.WriteTagAndLength(state.Buffer, state.WritePosition, 0, ProtobufOtlpCommonFieldNumberConstants.KeyValue_Value, ProtobufWireType.LEN); + return true; + } + + protected override bool TryWriteByteArrayTag(ref OtlpTagWriterState state, string key, ReadOnlySpan value) + { + // Write KeyValue tag + state.WritePosition = ProtobufSerializer.WriteStringWithTag(state.Buffer, state.WritePosition, ProtobufOtlpCommonFieldNumberConstants.KeyValue_Key, key); + + var serializedLengthSize = ProtobufSerializer.ComputeVarInt64Size((ulong)value.Length); + + // length = value.Length + tagSize + length field size. + state.WritePosition = ProtobufSerializer.WriteTagAndLength(state.Buffer, state.WritePosition, value.Length + 1 + serializedLengthSize, ProtobufOtlpCommonFieldNumberConstants.KeyValue_Value, ProtobufWireType.LEN); + state.WritePosition = ProtobufSerializer.WriteByteArrayWithTag(state.Buffer, state.WritePosition, ProtobufOtlpCommonFieldNumberConstants.AnyValue_Bytes_Value, value); + + return true; + } + internal struct OtlpTagWriterState { public byte[] Buffer; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTraceSerializer.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTraceSerializer.cs index a8e0609f8d0..7fb7eb2b5ad 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTraceSerializer.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTraceSerializer.cs @@ -45,36 +45,38 @@ internal static int WriteTraceData(ref byte[] buffer, int writePosition, SdkLimi internal static int TryWriteResourceSpans(ref byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, Resources.Resource? resource) { - int entryWritePosition = writePosition; - - try + while (true) { - writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.TracesData_Resource_Spans, ProtobufWireType.LEN); - int resourceSpansScopeSpansLengthPosition = writePosition; - writePosition += ReserveSizeForLength; + int entryWritePosition = writePosition; - writePosition = WriteResourceSpans(buffer, writePosition, sdkLimitOptions, resource); + try + { + writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.TracesData_Resource_Spans, ProtobufWireType.LEN); + int resourceSpansScopeSpansLengthPosition = writePosition; + writePosition += ReserveSizeForLength; - ProtobufSerializer.WriteReservedLength(buffer, resourceSpansScopeSpansLengthPosition, writePosition - (resourceSpansScopeSpansLengthPosition + ReserveSizeForLength)); - } - catch (Exception ex) when (ex is IndexOutOfRangeException || ex is ArgumentException) - { - writePosition = entryWritePosition; + writePosition = WriteResourceSpans(buffer, writePosition, sdkLimitOptions, resource); - // Attempt to increase the buffer size - if (!ProtobufSerializer.IncreaseBufferSize(ref buffer, OtlpSignalType.Traces)) - { - throw; + ProtobufSerializer.WriteReservedLength(buffer, resourceSpansScopeSpansLengthPosition, writePosition - (resourceSpansScopeSpansLengthPosition + ReserveSizeForLength)); + + // Serialization succeeded, return the final write position + return writePosition; } + catch (Exception ex) when (ex is IndexOutOfRangeException || ex is ArgumentException) + { + // Reset write position and attempt to increase the buffer size + writePosition = entryWritePosition; - // Retry serialization after increasing the buffer size. - // The recursion depth is limited to a maximum of 7 calls, as the buffer size starts at ~732 KB - // and doubles until it reaches the maximum size of 100 MB. This ensures the recursion remains safe - // and avoids stack overflow. - return TryWriteResourceSpans(ref buffer, writePosition, sdkLimitOptions, resource); - } + if (!ProtobufSerializer.IncreaseBufferSize(ref buffer, OtlpSignalType.Traces)) + { + throw; + } - return writePosition; + // Continue the loop to retry serialization with the larger buffer + // The loop is limited by the buffer size expansion logic in IncreaseBufferSize, + // which stops at a maximum of 100 MB, ensuring this doesn't become an infinite loop + } + } } internal static void ReturnActivityListToPool() @@ -507,7 +509,10 @@ internal static int WriteSpanStatus(byte[] buffer, int position, Activity activi { var descriptionSpan = description.AsSpan(); var numberOfUtf8CharsInString = ProtobufSerializer.GetNumberOfUtf8CharsInString(descriptionSpan); - position = ProtobufSerializer.WriteTagAndLength(buffer, position, numberOfUtf8CharsInString + 4, ProtobufOtlpTraceFieldNumberConstants.Span_Status, ProtobufWireType.LEN); + var serializedLengthSize = ProtobufSerializer.ComputeVarInt64Size((ulong)numberOfUtf8CharsInString); + + // length = numberOfUtf8CharsInString + Status_Message tag size + serializedLengthSize field size + Span_Status tag size + Span_Status length size. + position = ProtobufSerializer.WriteTagAndLength(buffer, position, numberOfUtf8CharsInString + 1 + serializedLengthSize + 2, ProtobufOtlpTraceFieldNumberConstants.Span_Status, ProtobufWireType.LEN); position = ProtobufSerializer.WriteStringWithTag(buffer, position, ProtobufOtlpTraceFieldNumberConstants.Status_Message, numberOfUtf8CharsInString, descriptionSpan); } else diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufSerializer.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufSerializer.cs index a74ec097299..deee79e77db 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufSerializer.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufSerializer.cs @@ -18,6 +18,8 @@ internal static class ProtobufSerializer private const ulong ULong128 = 0x80; private const int Fixed32Size = 4; private const int Fixed64Size = 8; + private const int MaskBitsLow = 0b_0111_1111; + private const int MaskBitHigh = 0b_1000_0000; private static readonly Encoding Utf8Encoding = Encoding.UTF8; @@ -42,63 +44,11 @@ internal static int WriteTagAndLength(byte[] buffer, int writePosition, int cont [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void WriteReservedLength(byte[] buffer, int writePosition, int length) { - int byteLength = 0; - int? firstByte = null; - int? secondByte = null; - int? thirdByte = null; - int? fourthByte = null; - - do - { - switch (byteLength) - { - case 0: - firstByte = length & 0x7F; - break; - case 1: - secondByte = length & 0x7F; - break; - case 2: - thirdByte = length & 0x7F; - break; - case 3: - fourthByte = length & 0x7F; - break; - } - - length >>= 7; - byteLength++; - } - while (length > 0); - - if (fourthByte.HasValue) - { - buffer[writePosition++] = (byte)(firstByte!.Value | 0x80); - buffer[writePosition++] = (byte)(secondByte!.Value | 0x80); - buffer[writePosition++] = (byte)(thirdByte!.Value | 0x80); - buffer[writePosition++] = (byte)fourthByte!.Value; - } - else if (thirdByte.HasValue) - { - buffer[writePosition++] = (byte)(firstByte!.Value | 0x80); - buffer[writePosition++] = (byte)(secondByte!.Value | 0x80); - buffer[writePosition++] = (byte)(thirdByte!.Value | 0x80); - buffer[writePosition++] = 0; - } - else if (secondByte.HasValue) - { - buffer[writePosition++] = (byte)(firstByte!.Value | 0x80); - buffer[writePosition++] = (byte)(secondByte!.Value | 0x80); - buffer[writePosition++] = 0x80; - buffer[writePosition++] = 0; - } - else - { - buffer[writePosition++] = (byte)(firstByte!.Value | 0x80); - buffer[writePosition++] = 0x80; - buffer[writePosition++] = 0x80; - buffer[writePosition++] = 0; - } + var slice = buffer.AsSpan(writePosition, 4); + slice[0] = (byte)((length & MaskBitsLow) | MaskBitHigh); + slice[1] = (byte)(((length >> 7) & MaskBitsLow) | MaskBitHigh); + slice[2] = (byte)(((length >> 14) & MaskBitsLow) | MaskBitHigh); + slice[3] = (byte)((length >> 21) & MaskBitsLow); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -171,7 +121,7 @@ internal static int WriteVarInt32(byte[] buffer, int writePosition, uint value) { while (value >= UInt128) { - buffer[writePosition++] = (byte)(0x80 | (value & 0x7F)); + buffer[writePosition++] = (byte)(MaskBitHigh | (value & MaskBitsLow)); value >>= 7; } @@ -184,7 +134,7 @@ internal static int WriteVarInt64(byte[] buffer, int writePosition, ulong value) { while (value >= ULong128) { - buffer[writePosition++] = (byte)(0x80 | (value & 0x7F)); + buffer[writePosition++] = (byte)(MaskBitHigh | (value & MaskBitsLow)); value >>= 7; } @@ -281,6 +231,17 @@ internal static int ComputeVarInt64Size(ulong value) return 10; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteByteArrayWithTag(byte[] buffer, int writePosition, int fieldNumber, ReadOnlySpan value) + { + writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.LEN); + writePosition = WriteLength(buffer, writePosition, value.Length); + value.CopyTo(buffer.AsSpan(writePosition)); + + writePosition += value.Length; + return writePosition; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static int WriteStringWithTag(byte[] buffer, int writePosition, int fieldNumber, string value) { @@ -325,7 +286,9 @@ internal static int WriteStringWithTag(byte[] buffer, int writePosition, int fie { // Note: Validate there is enough space in the buffer to hold the // string otherwise throw to trigger a resize of the buffer. +#pragma warning disable CA2201 // Do not raise reserved exception types throw new IndexOutOfRangeException(); +#pragma warning restore CA2201 // Do not raise reserved exception types } unsafe @@ -340,7 +303,7 @@ internal static int WriteStringWithTag(byte[] buffer, int writePosition, int fie } } #else - var bytesWritten = Utf8Encoding.GetBytes(value, buffer.AsSpan().Slice(writePosition)); + var bytesWritten = Utf8Encoding.GetBytes(value, buffer.AsSpan(writePosition)); Debug.Assert(bytesWritten == numberOfUtf8CharsInString, "bytesWritten did not match numberOfUtf8CharsInString"); #endif diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterPersistentStorageTransmissionHandler.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterPersistentStorageTransmissionHandler.cs index c40d7577908..066ecb02d02 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterPersistentStorageTransmissionHandler.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterPersistentStorageTransmissionHandler.cs @@ -19,7 +19,9 @@ internal sealed class OtlpExporterPersistentStorageTransmissionHandler : OtlpExp private bool disposed; public OtlpExporterPersistentStorageTransmissionHandler(IExportClient exportClient, double timeoutMilliseconds, string storagePath) +#pragma warning disable CA2000 // Dispose objects before losing scope : this(new FileBlobProvider(storagePath), exportClient, timeoutMilliseconds) +#pragma warning restore CA2000 // Dispose objects before losing scope { } @@ -93,6 +95,8 @@ protected override void Dispose(bool disposing) this.disposed = true; } + + base.Dispose(disposing); } private void RetryStoredRequests() diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj index a29ba23649e..8470c6df958 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj @@ -23,10 +23,6 @@ - - - - @@ -37,4 +33,10 @@ + + + + + + diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExportProtocol.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExportProtocol.cs index 10375e1dfc6..0a01afbd6ef 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExportProtocol.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExportProtocol.cs @@ -6,11 +6,16 @@ namespace OpenTelemetry.Exporter; /// /// Supported by OTLP exporter protocol types according to the specification https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md. /// +#pragma warning disable CA1028 // Enum storage should be Int32 public enum OtlpExportProtocol : byte +#pragma warning restore CA1028 // Enum storage should be Int32 { /// /// OTLP over gRPC (corresponds to 'grpc' Protocol configuration option). Used as default. /// +#if NET462_OR_GREATER || NETSTANDARD2_0 + [Obsolete("CAUTION: OTLP/gRPC is no longer supported for .NET Framework or .NET Standard targets without supplying a properly configured HttpClientFactory. It is strongly encouraged that you migrate to using OTLP/HTTPPROTOBUF.")] +#endif Grpc = 0, /// diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExportProtocolParser.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExportProtocolParser.cs index e5e788c5617..563c6b8a6b8 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExportProtocolParser.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExportProtocolParser.cs @@ -10,7 +10,9 @@ public static bool TryParse(string value, out OtlpExportProtocol result) switch (value?.Trim()) { case "grpc": +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning result = OtlpExportProtocol.Grpc; +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning return true; case "http/protobuf": result = OtlpExportProtocol.HttpProtobuf; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs index 355ab376743..91ebfdbd3e1 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs @@ -26,7 +26,11 @@ public class OtlpExporterOptions : IOtlpExporterOptions { internal const string DefaultGrpcEndpoint = "http://localhost:4317"; internal const string DefaultHttpEndpoint = "http://localhost:4318"; +#if NET462_OR_GREATER || NETSTANDARD2_0 + internal const OtlpExportProtocol DefaultOtlpExportProtocol = OtlpExportProtocol.HttpProtobuf; +#else internal const OtlpExportProtocol DefaultOtlpExportProtocol = OtlpExportProtocol.Grpc; +#endif internal static readonly KeyValuePair[] StandardHeaders = new KeyValuePair[] { @@ -84,7 +88,9 @@ public Uri Endpoint { if (this.endpoint == null) { +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning return this.Protocol == OtlpExportProtocol.Grpc +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning ? new Uri(DefaultGrpcEndpoint) : new Uri(DefaultHttpEndpoint); } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs index c4bea6931e3..218b4721caa 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs @@ -9,9 +9,6 @@ using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Transmission; -#if NET462_OR_GREATER || NETSTANDARD2_0 -using Grpc.Core; -#endif namespace OpenTelemetry.Exporter; @@ -25,30 +22,6 @@ internal static class OtlpExporterOptionsExtensions private const string MetricsHttpServicePath = "v1/metrics"; private const string LogsHttpServicePath = "v1/logs"; -#if NET462_OR_GREATER || NETSTANDARD2_0 - public static Channel CreateChannel(this OtlpExporterOptions options) - { - if (options.Endpoint.Scheme != Uri.UriSchemeHttp && options.Endpoint.Scheme != Uri.UriSchemeHttps) - { - throw new NotSupportedException($"Endpoint URI scheme ({options.Endpoint.Scheme}) is not supported. Currently only \"http\" and \"https\" are supported."); - } - - ChannelCredentials channelCredentials; - if (options.Endpoint.Scheme == Uri.UriSchemeHttps) - { - channelCredentials = new SslCredentials(); - } - else - { - channelCredentials = ChannelCredentials.Insecure; - } - - return new Channel(options.Endpoint.Authority, channelCredentials); - } - - public static Metadata GetMetadataFromHeaders(this OtlpExporterOptions options) => options.GetHeaders((m, k, v) => m.Add(k, v)); -#endif - public static THeaders GetHeaders(this OtlpExporterOptions options, Action addHeader) where THeaders : new() { @@ -58,23 +31,33 @@ public static THeaders GetHeaders(this OtlpExporterOptions options, Ac { // According to the specification, URL-encoded headers must be supported. optionHeaders = Uri.UnescapeDataString(optionHeaders); + ReadOnlySpan headersSpan = optionHeaders.AsSpan(); + + while (!headersSpan.IsEmpty) + { + int commaIndex = headersSpan.IndexOf(','); + ReadOnlySpan pair; + if (commaIndex == -1) + { + pair = headersSpan; + headersSpan = []; + } + else + { + pair = headersSpan.Slice(0, commaIndex); + headersSpan = headersSpan.Slice(commaIndex + 1); + } - Array.ForEach( - optionHeaders.Split(','), - (pair) => + int equalIndex = pair.IndexOf('='); + if (equalIndex == -1) { - // Specify the maximum number of substrings to return to 2 - // This treats everything that follows the first `=` in the string as the value to be added for the metadata key - var keyValueData = pair.Split(new char[] { '=' }, 2); - if (keyValueData.Length != 2) - { - throw new ArgumentException("Headers provided in an invalid format."); - } + throw new ArgumentException("Headers provided in an invalid format."); + } - var key = keyValueData[0].Trim(); - var value = keyValueData[1].Trim(); - addHeader(headers, key, value); - }); + var key = pair.Slice(0, equalIndex).Trim().ToString(); + var value = pair.Slice(equalIndex + 1).Trim().ToString(); + addHeader(headers, key, value); + } } foreach (var header in OtlpExporterOptions.StandardHeaders) @@ -119,25 +102,12 @@ public static IExportClient GetExportClient(this OtlpExporterOptions options, Ot { var httpClient = options.HttpClientFactory?.Invoke() ?? throw new InvalidOperationException("OtlpExporterOptions was missing HttpClientFactory or it returned null."); +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning if (options.Protocol != OtlpExportProtocol.Grpc && options.Protocol != OtlpExportProtocol.HttpProtobuf) { throw new NotSupportedException($"Protocol {options.Protocol} is not supported."); } -#if NET462_OR_GREATER || NETSTANDARD2_0 - if (options.Protocol == OtlpExportProtocol.Grpc) - { - var servicePath = otlpSignalType switch - { - OtlpSignalType.Traces => TraceGrpcServicePath, - OtlpSignalType.Metrics => MetricsGrpcServicePath, - OtlpSignalType.Logs => LogsGrpcServicePath, - _ => throw new NotSupportedException($"OtlpSignalType {otlpSignalType} is not supported."), - }; - return new GrpcExportClient(options, servicePath); - } -#endif - return otlpSignalType switch { OtlpSignalType.Traces => options.Protocol == OtlpExportProtocol.Grpc @@ -154,6 +124,7 @@ public static IExportClient GetExportClient(this OtlpExporterOptions options, Ot _ => throw new NotSupportedException($"OtlpSignalType {otlpSignalType} is not supported."), }; +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning } public static void TryEnableIHttpClientFactoryIntegration(this OtlpExporterOptions options, IServiceProvider serviceProvider, string httpClientName) @@ -174,11 +145,11 @@ public static void TryEnableIHttpClientFactoryIntegration(this OtlpExporterOptio "CreateClient", BindingFlags.Public | BindingFlags.Instance, binder: null, - new Type[] { typeof(string) }, + [typeof(string)], modifiers: null); if (createClientMethod != null) { - HttpClient? client = (HttpClient?)createClientMethod.Invoke(httpClientFactory, new object[] { httpClientName }); + HttpClient? client = (HttpClient?)createClientMethod.Invoke(httpClientFactory, [httpClientName]); if (client != null) { @@ -200,7 +171,11 @@ internal static Uri AppendPathIfNotPresent(this Uri uri, string path) var absoluteUri = uri.AbsoluteUri; var separator = string.Empty; - if (absoluteUri.EndsWith("/")) +#if NET || NETSTANDARD2_1_OR_GREATER + if (absoluteUri.EndsWith('/')) +#else + if (absoluteUri.EndsWith("/", StringComparison.Ordinal)) +#endif { // Endpoint already ends with 'path/' if (absoluteUri.EndsWith(string.Concat(path, "/"), StringComparison.OrdinalIgnoreCase)) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs index a87c9ad4d96..dbec08c771b 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs @@ -58,18 +58,18 @@ internal OtlpLogExporter( this.experimentalOptions = experimentalOptions!; this.sdkLimitOptions = sdkLimitOptions!; -#if NET462_OR_GREATER || NETSTANDARD2_0 - this.startWritePosition = 0; -#else +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning this.startWritePosition = exporterOptions!.Protocol == OtlpExportProtocol.Grpc ? GrpcStartWritePosition : 0; -#endif +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning this.transmissionHandler = transmissionHandler ?? exporterOptions!.GetExportTransmissionHandler(experimentalOptions!, OtlpSignalType.Logs); } internal Resource Resource => this.resource ??= this.ParentProvider.GetResource(); /// +#pragma warning disable CA1725 // Parameter names should match base declaration public override ExportResult Export(in Batch logRecordBatch) +#pragma warning restore CA1725 // Parameter names should match base declaration { // Prevents the exporter's gRPC and HTTP operations from being instrumented. using var scope = SuppressInstrumentationScope.Begin(); diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporterHelperExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporterHelperExtensions.cs index 42faf425356..851a7729286 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporterHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporterHelperExtensions.cs @@ -56,7 +56,9 @@ public static OpenTelemetryLoggerOptions AddOtlpExporter( var finalOptionsName = name ?? Options.DefaultName; +#pragma warning disable CA1062 // Validate arguments of public methods return loggerOptions.AddProcessor(sp => +#pragma warning restore CA1062 // Validate arguments of public methods { var exporterOptions = GetOptions(sp, name, finalOptionsName, OtlpExporterOptions.CreateOtlpExporterOptions); @@ -102,7 +104,9 @@ public static OpenTelemetryLoggerOptions AddOtlpExporter( var finalOptionsName = name ?? Options.DefaultName; +#pragma warning disable CA1062 // Validate arguments of public methods return loggerOptions.AddProcessor(sp => +#pragma warning restore CA1062 // Validate arguments of public methods { var exporterOptions = GetOptions(sp, name, finalOptionsName, OtlpExporterOptions.CreateOtlpExporterOptions); @@ -284,6 +288,17 @@ internal static BaseProcessor BuildOtlpLogExporter( Debug.Assert(sdkLimitOptions != null, "sdkLimitOptions was null"); Debug.Assert(experimentalOptions != null, "experimentalOptions was null"); +#if NET462_OR_GREATER || NETSTANDARD2_0 +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning + if (exporterOptions!.Protocol == OtlpExportProtocol.Grpc && + ReferenceEquals(exporterOptions.HttpClientFactory, exporterOptions.DefaultHttpClientFactory)) +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning + { + throw new NotSupportedException("OtlpExportProtocol.Grpc with the default HTTP client factory is not supported on .NET Framework or .NET Standard 2.0." + + "Please switch to OtlpExportProtocol.HttpProtobuf or provide a custom HttpClientFactory."); + } +#endif + if (!skipUseOtlpExporterRegistrationCheck) { serviceProvider!.EnsureNoUseOtlpExporterRegistrations(); @@ -305,30 +320,40 @@ internal static BaseProcessor BuildOtlpLogExporter( * "OtlpLogExporter"); */ +#pragma warning disable CA2000 // Dispose objects before losing scope BaseExporter otlpExporter = new OtlpLogExporter( exporterOptions!, sdkLimitOptions!, experimentalOptions!); +#pragma warning restore CA2000 // Dispose objects before losing scope - if (configureExporterInstance != null) + try { - otlpExporter = configureExporterInstance(otlpExporter); - } + if (configureExporterInstance != null) + { + otlpExporter = configureExporterInstance(otlpExporter); + } - if (processorOptions!.ExportProcessorType == ExportProcessorType.Simple) - { - return new SimpleLogRecordExportProcessor(otlpExporter); + if (processorOptions!.ExportProcessorType == ExportProcessorType.Simple) + { + return new SimpleLogRecordExportProcessor(otlpExporter); + } + else + { + var batchOptions = processorOptions.BatchExportProcessorOptions; + + return new BatchLogRecordExportProcessor( + otlpExporter, + batchOptions.MaxQueueSize, + batchOptions.ScheduledDelayMilliseconds, + batchOptions.ExporterTimeoutMilliseconds, + batchOptions.MaxExportBatchSize); + } } - else + catch { - var batchOptions = processorOptions.BatchExportProcessorOptions; - - return new BatchLogRecordExportProcessor( - otlpExporter, - batchOptions.MaxQueueSize, - batchOptions.ScheduledDelayMilliseconds, - batchOptions.ExporterTimeoutMilliseconds, - batchOptions.MaxExportBatchSize); + otlpExporter.Dispose(); + throw; } } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporter.cs index 8c596e9c61c..1ff7dcc5ae5 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporter.cs @@ -51,18 +51,18 @@ internal OtlpMetricExporter( Debug.Assert(exporterOptions != null, "exporterOptions was null"); Debug.Assert(experimentalOptions != null, "experimentalOptions was null"); -#if NET462_OR_GREATER || NETSTANDARD2_0 - this.startWritePosition = 0; -#else +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning this.startWritePosition = exporterOptions!.Protocol == OtlpExportProtocol.Grpc ? GrpcStartWritePosition : 0; -#endif +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning this.transmissionHandler = transmissionHandler ?? exporterOptions!.GetExportTransmissionHandler(experimentalOptions!, OtlpSignalType.Metrics); } internal Resource Resource => this.resource ??= this.ParentProvider.GetResource(); /// +#pragma warning disable CA1725 // Parameter names should match base declaration public override ExportResult Export(in Batch metrics) +#pragma warning restore CA1725 // Parameter names should match base declaration { // Prevents the exporter's gRPC and HTTP operations from being instrumented. using var scope = SuppressInstrumentationScope.Begin(); diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs index 1a8ef2f1b42..4efbc2c8b18 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs @@ -168,6 +168,17 @@ internal static MetricReader BuildOtlpExporterMetricReader( Debug.Assert(metricReaderOptions != null, "metricReaderOptions was null"); Debug.Assert(experimentalOptions != null, "experimentalOptions was null"); +#if NET462_OR_GREATER || NETSTANDARD2_0 +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning + if (exporterOptions!.Protocol == OtlpExportProtocol.Grpc && + ReferenceEquals(exporterOptions.HttpClientFactory, exporterOptions.DefaultHttpClientFactory)) +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning + { + throw new NotSupportedException("OtlpExportProtocol.Grpc with the default HTTP client factory is not supported on .NET Framework or .NET Standard 2.0." + + "Please switch to OtlpExportProtocol.HttpProtobuf or provide a custom HttpClientFactory."); + } +#endif + if (!skipUseOtlpExporterRegistrationCheck) { serviceProvider!.EnsureNoUseOtlpExporterRegistrations(); @@ -175,7 +186,9 @@ internal static MetricReader BuildOtlpExporterMetricReader( exporterOptions!.TryEnableIHttpClientFactoryIntegration(serviceProvider!, "OtlpMetricExporter"); +#pragma warning disable CA2000 // Dispose objects before losing scope BaseExporter metricExporter = new OtlpMetricExporter(exporterOptions!, experimentalOptions!); +#pragma warning restore CA2000 // Dispose objects before losing scope if (configureExporterInstance != null) { diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporter.cs index d30abeeece0..6f10f51e7b2 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporter.cs @@ -54,18 +54,18 @@ internal OtlpTraceExporter( Debug.Assert(sdkLimitOptions != null, "sdkLimitOptions was null"); this.sdkLimitOptions = sdkLimitOptions!; -#if NET462_OR_GREATER || NETSTANDARD2_0 - this.startWritePosition = 0; -#else +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning this.startWritePosition = exporterOptions!.Protocol == OtlpExportProtocol.Grpc ? GrpcStartWritePosition : 0; -#endif +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning this.transmissionHandler = transmissionHandler ?? exporterOptions!.GetExportTransmissionHandler(experimentalOptions, OtlpSignalType.Traces); } internal Resource Resource => this.resource ??= this.ParentProvider.GetResource(); /// +#pragma warning disable CA1725 // Parameter names should match base declaration public override ExportResult Export(in Batch activityBatch) +#pragma warning restore CA1725 // Parameter names should match base declaration { // Prevents the exporter's gRPC and HTTP operations from being instrumented. using var scope = SuppressInstrumentationScope.Begin(); diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporterHelperExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporterHelperExtensions.cs index 3a18b3da423..831a1380bb6 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporterHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporterHelperExtensions.cs @@ -129,6 +129,17 @@ internal static BaseProcessor BuildOtlpExporterProcessor( Debug.Assert(experimentalOptions != null, "experimentalOptions was null"); Debug.Assert(batchExportProcessorOptions != null, "batchExportProcessorOptions was null"); +#if NET462_OR_GREATER || NETSTANDARD2_0 +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning + if (exporterOptions!.Protocol == OtlpExportProtocol.Grpc && + ReferenceEquals(exporterOptions.HttpClientFactory, exporterOptions.DefaultHttpClientFactory)) +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning + { + throw new NotSupportedException("OtlpExportProtocol.Grpc with the default HTTP client factory is not supported on .NET Framework or .NET Standard 2.0." + + "Please switch to OtlpExportProtocol.HttpProtobuf or provide a custom HttpClientFactory."); + } +#endif + if (!skipUseOtlpExporterRegistrationCheck) { serviceProvider!.EnsureNoUseOtlpExporterRegistrations(); @@ -136,25 +147,35 @@ internal static BaseProcessor BuildOtlpExporterProcessor( exporterOptions!.TryEnableIHttpClientFactoryIntegration(serviceProvider!, "OtlpTraceExporter"); +#pragma warning disable CA2000 // Dispose objects before losing scope BaseExporter otlpExporter = new OtlpTraceExporter(exporterOptions!, sdkLimitOptions!, experimentalOptions!); +#pragma warning restore CA2000 // Dispose objects before losing scope - if (configureExporterInstance != null) + try { - otlpExporter = configureExporterInstance(otlpExporter); - } + if (configureExporterInstance != null) + { + otlpExporter = configureExporterInstance(otlpExporter); + } - if (exportProcessorType == ExportProcessorType.Simple) - { - return new SimpleActivityExportProcessor(otlpExporter); + if (exportProcessorType == ExportProcessorType.Simple) + { + return new SimpleActivityExportProcessor(otlpExporter); + } + else + { + return new BatchActivityExportProcessor( + otlpExporter, + batchExportProcessorOptions!.MaxQueueSize, + batchExportProcessorOptions.ScheduledDelayMilliseconds, + batchExportProcessorOptions.ExporterTimeoutMilliseconds, + batchExportProcessorOptions.MaxExportBatchSize); + } } - else + catch { - return new BatchActivityExportProcessor( - otlpExporter, - batchExportProcessorOptions!.MaxQueueSize, - batchExportProcessorOptions.ScheduledDelayMilliseconds, - batchExportProcessorOptions.ExporterTimeoutMilliseconds, - batchExportProcessorOptions.MaxExportBatchSize); + otlpExporter.Dispose(); + throw; } } } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/DirectorySizeTracker.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/DirectorySizeTracker.cs index 2bbc352817d..d251885f81d 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/DirectorySizeTracker.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/DirectorySizeTracker.cs @@ -56,16 +56,16 @@ internal static long CalculateFolderSize(string path) long directorySize = 0; try { - foreach (string file in Directory.EnumerateFiles(path)) + foreach (var file in Directory.EnumerateFiles(path)) { if (File.Exists(file)) { - FileInfo fileInfo = new FileInfo(file); + var fileInfo = new FileInfo(file); directorySize += fileInfo.Length; } } - foreach (string dir in Directory.GetDirectories(path)) + foreach (var dir in Directory.GetDirectories(path)) { directorySize += CalculateFolderSize(dir); } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlob.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlob.cs index cfb1c395d08..72f121b9508 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlob.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlob.cs @@ -58,7 +58,7 @@ protected override bool OnTryWrite(byte[] buffer, int leasePeriodMilliseconds = { Guard.ThrowIfNull(buffer); - string path = this.FullPath + ".tmp"; + var path = this.FullPath + ".tmp"; try { diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlobProvider.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlobProvider.cs index 4d790469958..9e79fb28736 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlobProvider.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlobProvider.cs @@ -5,7 +5,6 @@ using System.Timers; using OpenTelemetry.Internal; using OpenTelemetry.PersistentStorage.Abstractions; -using Timer = System.Timers.Timer; namespace OpenTelemetry.PersistentStorage.FileSystem; @@ -24,7 +23,7 @@ public class FileBlobProvider : PersistentBlobProvider, IDisposable private readonly DirectorySizeTracker directorySizeTracker; private readonly long retentionPeriodInMilliseconds; private readonly int writeTimeoutInMilliseconds; - private readonly Timer maintenanceTimer; + private readonly System.Timers.Timer maintenanceTimer; private bool disposedValue; /// @@ -61,7 +60,7 @@ public class FileBlobProvider : PersistentBlobProvider, IDisposable /// path exceeds system defined maximum length. /// /// - /// insufficient priviledges for provided path. + /// insufficient privileges for provided path. /// /// /// path contains a colon character (:) that is not part of a drive label ("C:\"). @@ -87,7 +86,7 @@ public FileBlobProvider( this.retentionPeriodInMilliseconds = retentionPeriodInMilliseconds; this.writeTimeoutInMilliseconds = writeTimeoutInMilliseconds; - this.maintenanceTimer = new Timer(maintenancePeriodInMilliseconds); + this.maintenanceTimer = new System.Timers.Timer(maintenancePeriodInMilliseconds); this.maintenanceTimer.Elapsed += this.OnMaintenanceEvent; this.maintenanceTimer.AutoReset = true; this.maintenanceTimer.Enabled = true; @@ -105,7 +104,7 @@ protected override IEnumerable OnGetBlobs() foreach (var file in Directory.EnumerateFiles(this.DirectoryPath, "*.blob", SearchOption.TopDirectoryOnly).OrderByDescending(f => f)) { - DateTime fileDateTime = PersistentStorageHelper.GetDateTimeFromBlobName(file); + var fileDateTime = PersistentStorageHelper.GetDateTimeFromBlobName(file); if (fileDateTime > retentionDeadline) { yield return new FileBlob(file, this.directorySizeTracker); @@ -174,7 +173,7 @@ private void OnMaintenanceEvent(object? source, ElapsedEventArgs e) private bool CheckStorageSize() { - if (!this.directorySizeTracker.IsSpaceAvailable(out long size)) + if (!this.directorySizeTracker.IsSpaceAvailable(out var size)) { // TODO: check accuracy of size reporting. PersistentStorageEventSource.Log.PersistentStorageWarning( @@ -186,7 +185,7 @@ private bool CheckStorageSize() return true; } - private PersistentBlob? CreateFileBlob(byte[] buffer, int leasePeriodMilliseconds = 0) + private FileBlob? CreateFileBlob(byte[] buffer, int leasePeriodMilliseconds = 0) { if (!this.CheckStorageSize()) { @@ -198,14 +197,7 @@ private bool CheckStorageSize() var blobFilePath = Path.Combine(this.DirectoryPath, PersistentStorageHelper.GetUniqueFileName(".blob")); var blob = new FileBlob(blobFilePath, this.directorySizeTracker); - if (blob.TryWrite(buffer, leasePeriodMilliseconds)) - { - return blob; - } - else - { - return null; - } + return blob.TryWrite(buffer, leasePeriodMilliseconds) ? blob : null; } catch (Exception ex) { diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentBlobProvider.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentBlobProvider.cs index 29d3dee8a02..465af27746d 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentBlobProvider.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentBlobProvider.cs @@ -102,12 +102,12 @@ public IEnumerable GetBlobs() { try { - return this.OnGetBlobs() ?? Enumerable.Empty(); + return this.OnGetBlobs() ?? []; } catch (Exception ex) { PersistentStorageAbstractionsEventSource.Log.PersistentStorageAbstractionsException(nameof(PersistentBlobProvider), "Failed to get all the blobs", ex); - return Enumerable.Empty(); + return []; } } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentStorageHelper.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentStorageHelper.cs index 63005646fc7..6d2fd821cbb 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentStorageHelper.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentStorageHelper.cs @@ -12,7 +12,7 @@ internal static void RemoveExpiredBlob(DateTime retentionDeadline, string filePa { if (filePath.EndsWith(".blob", StringComparison.OrdinalIgnoreCase)) { - DateTime fileDateTime = GetDateTimeFromBlobName(filePath); + var fileDateTime = GetDateTimeFromBlobName(filePath); if (fileDateTime < retentionDeadline) { try @@ -30,11 +30,11 @@ internal static void RemoveExpiredBlob(DateTime retentionDeadline, string filePa internal static bool RemoveExpiredLease(DateTime leaseDeadline, string filePath) { - bool success = false; + var success = false; if (filePath.EndsWith(".lock", StringComparison.OrdinalIgnoreCase)) { - DateTime fileDateTime = GetDateTimeFromLeaseName(filePath); + var fileDateTime = GetDateTimeFromLeaseName(filePath); if (fileDateTime < leaseDeadline) { var newFilePath = filePath.Substring(0, filePath.LastIndexOf('@')); @@ -55,11 +55,11 @@ internal static bool RemoveExpiredLease(DateTime leaseDeadline, string filePath) internal static bool RemoveTimedOutTmpFiles(DateTime timeoutDeadline, string filePath) { - bool success = false; + var success = false; if (filePath.EndsWith(".tmp", StringComparison.OrdinalIgnoreCase)) { - DateTime fileDateTime = GetDateTimeFromBlobName(filePath); + var fileDateTime = GetDateTimeFromBlobName(filePath); if (fileDateTime < timeoutDeadline) { try @@ -144,7 +144,7 @@ internal static DateTime GetDateTimeFromLeaseName(string filePath) { var fileName = Path.GetFileNameWithoutExtension(filePath); var startIndex = fileName.LastIndexOf('@') + 1; - var time = fileName.Substring(startIndex, fileName.Length - startIndex); + var time = fileName.Substring(startIndex); DateTime.TryParseExact(time, "yyyy-MM-ddTHHmmss.fffffffZ", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTime); return dateTime.ToUniversalTime(); } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/README.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/README.md index 018aef5b4a8..353cd8dffbc 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/README.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/README.md @@ -1,9 +1,9 @@ # Persistent Storage APIs for OTLP Exporter The files in this folder have been copied over from -[OpenTelemetry.PersistentStorage.Abstractions](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/tree/58607b7cdeb15207027a6fa4ca56e7fac897bda4/src/OpenTelemetry.PersistentStorage.Abstractions) +[OpenTelemetry.PersistentStorage.Abstractions](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/tree/1be4157075ad09e13aa54b8e5845d2310bd82673/src/OpenTelemetry.PersistentStorage.Abstractions) and -[OpenTelemetry.PersistentStorage.FileSystem](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/tree/58607b7cdeb15207027a6fa4ca56e7fac897bda4/src/OpenTelemetry.PersistentStorage.FileSystem). +[OpenTelemetry.PersistentStorage.FileSystem](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/tree/1be4157075ad09e13aa54b8e5845d2310bd82673/src/OpenTelemetry.PersistentStorage.FileSystem). Any code changes in this folder MUST go through changes in the original location i.e. in the contrib repo. Here is the sequence of steps to be followed when making changes: diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/AssemblyInfo.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/AssemblyInfo.cs deleted file mode 100644 index 59d05ae59c9..00000000000 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/AssemblyInfo.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Runtime.CompilerServices; - -#if SIGNED -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -#else -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests")] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Tests")] -#endif diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md index 07256d56975..768eb691981 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md @@ -7,6 +7,20 @@ Notes](../../RELEASENOTES.md). ## Unreleased +## 1.12.0-beta.1 + +Released 2025-May-06 + +* Updated OpenTelemetry core component version(s) to `1.12.0`. + ([#6269](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6269)) + +## 1.11.2-beta.1 + +Released 2025-Mar-05 + +* Updated OpenTelemetry core component version(s) to `1.11.2`. + ([#6169](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6169)) + ## 1.11.0-beta.1 Released 2025-Jan-16 diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj index f6a49fe6a64..1979137998b 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj @@ -36,5 +36,10 @@ + + + + + diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterApplicationBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterApplicationBuilderExtensions.cs index 38559e6b201..92bef6f1b59 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterApplicationBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterApplicationBuilderExtensions.cs @@ -99,6 +99,8 @@ public static IApplicationBuilder UseOpenTelemetryPrometheusScrapingEndpoint( Action? configureBranchedPipeline, string? optionsName) { + Guard.ThrowIfNull(app); + // Note: Order is important here. MeterProvider is accessed before // GetOptions so that any changes made to // PrometheusAspNetCoreOptions in deferred AddPrometheusExporter @@ -114,7 +116,7 @@ public static IApplicationBuilder UseOpenTelemetryPrometheusScrapingEndpoint( path = options.ScrapeEndpointPath ?? PrometheusAspNetCoreOptions.DefaultScrapeEndpointPath; } - if (!path.StartsWith("/")) + if (!path.StartsWith('/')) { path = $"/{path}"; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterEndpointRouteBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterEndpointRouteBuilderExtensions.cs index 45639b378fc..6604935ebf0 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterEndpointRouteBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterEndpointRouteBuilderExtensions.cs @@ -69,6 +69,8 @@ public static IEndpointConventionBuilder MapPrometheusScrapingEndpoint( Action? configureBranchedPipeline, string? optionsName) { + Guard.ThrowIfNull(endpoints); + var builder = endpoints.CreateApplicationBuilder(); // Note: Order is important here. MeterProvider is accessed before @@ -84,7 +86,7 @@ public static IEndpointConventionBuilder MapPrometheusScrapingEndpoint( path = options.ScrapeEndpointPath ?? PrometheusAspNetCoreOptions.DefaultScrapeEndpointPath; } - if (!path.StartsWith("/")) + if (!path.StartsWith('/')) { path = $"/{path}"; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs index f73455d3b08..e3567d4ad63 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs @@ -62,9 +62,11 @@ public static MeterProviderBuilder AddPrometheusExporter( }); } - private static MetricReader BuildPrometheusExporterMetricReader(PrometheusAspNetCoreOptions options) + private static BaseExportingMetricReader BuildPrometheusExporterMetricReader(PrometheusAspNetCoreOptions options) { +#pragma warning disable CA2000 // Dispose objects before losing scope var exporter = new PrometheusExporter(options.ExporterOptions); +#pragma warning restore CA2000 // Dispose objects before losing scope return new BaseExportingMetricReader(exporter) { diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs index f91a93f66bb..863695b975f 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs @@ -74,7 +74,7 @@ public async Task InvokeAsync(HttpContext httpContext) ? "application/openmetrics-text; version=1.0.0; charset=utf-8" : "text/plain; charset=utf-8; version=0.0.4"; - await response.Body.WriteAsync(dataView.Array!, 0, dataView.Count).ConfigureAwait(false); + await response.Body.WriteAsync(dataView.Array.AsMemory(0, dataView.Count)).ConfigureAwait(false); } else { diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/AssemblyInfo.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/AssemblyInfo.cs deleted file mode 100644 index 7d6b4b08289..00000000000 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/AssemblyInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Runtime.CompilerServices; - -#if SIGNED -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.HttpListener.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -[assembly: InternalsVisibleTo("Benchmarks, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -#else -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.HttpListener.Tests")] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Tests")] -[assembly: InternalsVisibleTo("Benchmarks")] -#endif diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md index ad0b8e591d1..7ab44c75ca1 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md @@ -7,6 +7,20 @@ Notes](../../RELEASENOTES.md). ## Unreleased +## 1.12.0-beta.1 + +Released 2025-May-06 + +* Updated OpenTelemetry core component version(s) to `1.12.0`. + ([#6269](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6269)) + +## 1.11.2-beta.1 + +Released 2025-Mar-05 + +* Updated OpenTelemetry core component version(s) to `1.11.2`. + ([#6169](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6169)) + ## 1.11.0-beta.1 Released 2025-Jan-16 diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs index 1c74e36bcf8..ce4c5384485 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs @@ -34,8 +34,8 @@ public PrometheusCollectionManager(PrometheusExporter exporter) this.exporter = exporter; this.scrapeResponseCacheDurationMilliseconds = this.exporter.ScrapeResponseCacheDurationMilliseconds; this.onCollectRef = this.OnCollect; - this.metricsCache = new Dictionary(); - this.scopes = new HashSet(); + this.metricsCache = []; + this.scopes = []; } #if NET @@ -68,10 +68,7 @@ public Task EnterCollect(bool openMetricsRequested) // If a collection is already running, return a task to wait on the result. if (this.collectionRunning) { - if (this.collectionTcs == null) - { - this.collectionTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - } + this.collectionTcs ??= new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Interlocked.Increment(ref this.readerCount); this.ExitGlobalLock(); @@ -148,6 +145,22 @@ public void ExitCollect() Interlocked.Decrement(ref this.readerCount); } + private static bool IncreaseBufferSize(ref byte[] buffer) + { + var newBufferSize = buffer.Length * 2; + + if (newBufferSize > 100 * 1024 * 1024) + { + return false; + } + + var newBuffer = new byte[newBufferSize]; + buffer.CopyTo(newBuffer, 0); + buffer = newBuffer; + + return true; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnterGlobalLock() { @@ -230,7 +243,7 @@ private ExportResult OnCollect(in Batch metrics) } catch (IndexOutOfRangeException) { - if (!this.IncreaseBufferSize(ref buffer)) + if (!IncreaseBufferSize(ref buffer)) { // there are two cases we might run into the following condition: // 1. we have many metrics to be exported - in this case we probably want @@ -268,7 +281,7 @@ private ExportResult OnCollect(in Batch metrics) } catch (IndexOutOfRangeException) { - if (!this.IncreaseBufferSize(ref buffer)) + if (!IncreaseBufferSize(ref buffer)) { throw; } @@ -285,7 +298,7 @@ private ExportResult OnCollect(in Batch metrics) } catch (IndexOutOfRangeException) { - if (!this.IncreaseBufferSize(ref buffer)) + if (!IncreaseBufferSize(ref buffer)) { throw; } @@ -307,11 +320,11 @@ private ExportResult OnCollect(in Batch metrics) { if (this.exporter.OpenMetricsRequested) { - this.previousOpenMetricsDataView = new ArraySegment(Array.Empty(), 0, 0); + this.previousOpenMetricsDataView = new ArraySegment([], 0, 0); } else { - this.previousPlainTextDataView = new ArraySegment(Array.Empty(), 0, 0); + this.previousPlainTextDataView = new ArraySegment([], 0, 0); } return ExportResult.Failure; @@ -332,7 +345,7 @@ private int WriteTargetInfo(ref byte[] buffer) } catch (IndexOutOfRangeException) { - if (!this.IncreaseBufferSize(ref buffer)) + if (!IncreaseBufferSize(ref buffer)) { throw; } @@ -343,22 +356,6 @@ private int WriteTargetInfo(ref byte[] buffer) return this.targetInfoBufferLength; } - private bool IncreaseBufferSize(ref byte[] buffer) - { - var newBufferSize = buffer.Length * 2; - - if (newBufferSize > 100 * 1024 * 1024) - { - return false; - } - - var newBuffer = new byte[newBufferSize]; - buffer.CopyTo(newBuffer, 0); - buffer = newBuffer; - - return true; - } - private PrometheusMetric GetPrometheusMetric(Metric metric) { // Optimize writing metrics with bounded cache that has pre-calculated Prometheus names. diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs index c5b0e0e64d3..2d9ea4c27d1 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs @@ -14,8 +14,6 @@ namespace OpenTelemetry.Exporter.Prometheus; [ExportModes(ExportModes.Pull)] internal sealed class PrometheusExporter : BaseExporter, IPullMetricExporter { - private Func? funcCollect; - private ExportFunc? funcExport; private Resource? resource; private bool disposed; @@ -38,17 +36,9 @@ public PrometheusExporter(PrometheusExporterOptions options) /// /// Gets or sets the Collect delegate. /// - public Func? Collect - { - get => this.funcCollect; - set => this.funcCollect = value; - } + public Func? Collect { get; set; } - internal ExportFunc? OnExport - { - get => this.funcExport; - set => this.funcExport = value; - } + internal ExportFunc? OnExport { get; set; } internal Action? OnDispose { get; set; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusMetric.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusMetric.cs index cc9176ea0c5..9f80d6dc5d3 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusMetric.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusMetric.cs @@ -16,10 +16,10 @@ Histogram becomes histogram UpDownCounter becomes gauge * https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/data-model.md#otlp-metric-points-to-prometheus */ - private static readonly PrometheusType[] MetricTypes = new PrometheusType[] - { + private static readonly PrometheusType[] MetricTypes = + [ PrometheusType.Untyped, PrometheusType.Counter, PrometheusType.Gauge, PrometheusType.Summary, PrometheusType.Histogram, PrometheusType.Histogram, PrometheusType.Histogram, PrometheusType.Histogram, PrometheusType.Gauge, - }; + ]; public PrometheusMetric(string name, string unit, PrometheusType type, bool disableTotalNameSuffixForCounters) { @@ -40,7 +40,7 @@ public PrometheusMetric(string name, string unit, PrometheusType type, bool disa // [OpenMetrics UNIT metadata](https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#metricfamily) // and as a suffix to the metric name. The unit suffix comes before any type-specific suffixes. // https://github.com/open-telemetry/opentelemetry-specification/blob/3dfb383fe583e3b74a2365c5a1d90256b273ee76/specification/compatibility/prometheus_and_openmetrics.md#metric-metadata-1 - if (!sanitizedName.EndsWith(sanitizedUnit)) + if (!sanitizedName.EndsWith(sanitizedUnit, StringComparison.Ordinal)) { sanitizedName += $"_{sanitizedUnit}"; openMetricsName += $"_{sanitizedUnit}"; @@ -51,14 +51,14 @@ public PrometheusMetric(string name, string unit, PrometheusType type, bool disa // Exporters SHOULD provide a configuration option to disable the addition of `_total` suffixes. // https://github.com/open-telemetry/opentelemetry-specification/blob/b2f923fb1650dde1f061507908b834035506a796/specification/compatibility/prometheus_and_openmetrics.md#L286 // Note that we no longer append '_ratio' for units that are '1', see: https://github.com/open-telemetry/opentelemetry-specification/issues/4058 - if (type == PrometheusType.Counter && !sanitizedName.EndsWith("_total") && !disableTotalNameSuffixForCounters) + if (type == PrometheusType.Counter && !sanitizedName.EndsWith("_total", StringComparison.Ordinal) && !disableTotalNameSuffixForCounters) { sanitizedName += "_total"; } // For counters requested using OpenMetrics format, the MetricFamily name MUST be suffixed with '_total', regardless of the setting to disable the 'total' suffix. // https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#counter-1 - if (type == PrometheusType.Counter && !openMetricsName.EndsWith("_total")) + if (type == PrometheusType.Counter && !openMetricsName.EndsWith("_total", StringComparison.Ordinal)) { openMetricsName += "_total"; } @@ -127,7 +127,7 @@ internal static string SanitizeMetricName(string metricName) return sb?.ToString() ?? metricName; - static StringBuilder CreateStringBuilder(string name) => new StringBuilder(name.Length); + static StringBuilder CreateStringBuilder(string name) => new(name.Length); } internal static string RemoveAnnotations(string unit) @@ -179,7 +179,7 @@ internal static string RemoveAnnotations(string unit) private static string SanitizeOpenMetricsName(string metricName) { - if (metricName.EndsWith("_total")) + if (metricName.EndsWith("_total", StringComparison.Ordinal)) { return metricName.Substring(0, metricName.Length - 6); } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs index b182b521506..c199a6695c5 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs @@ -17,8 +17,6 @@ internal static partial class PrometheusSerializer { #pragma warning disable SA1310 // Field name should not contain an underscore private const byte ASCII_QUOTATION_MARK = 0x22; // '"' - private const byte ASCII_FULL_STOP = 0x2E; // '.' - private const byte ASCII_HYPHEN_MINUS = 0x2D; // '-' private const byte ASCII_REVERSE_SOLIDUS = 0x5C; // '\\' private const byte ASCII_LINEFEED = 0x0A; // `\n` #pragma warning restore SA1310 // Field name should not contain an underscore @@ -102,16 +100,13 @@ public static int WriteUnicodeNoEscape(byte[] buffer, int cursor, ushort ordinal buffer[cursor++] = unchecked((byte)(0b_1100_0000 | (ordinal >> 6))); buffer[cursor++] = unchecked((byte)(0b_1000_0000 | (ordinal & 0b_0011_1111))); } - else if (ordinal <= 0xFFFF) + else { + // all other <= 0xFFFF which is ushort.MaxValue buffer[cursor++] = unchecked((byte)(0b_1110_0000 | (ordinal >> 12))); buffer[cursor++] = unchecked((byte)(0b_1000_0000 | ((ordinal >> 6) & 0b_0011_1111))); buffer[cursor++] = unchecked((byte)(0b_1000_0000 | (ordinal & 0b_0011_1111))); } - else - { - Debug.Assert(ordinal <= 0xFFFF, ".NET string should not go beyond Unicode BMP."); - } return cursor; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj index 4e087919be2..f69ce360219 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj @@ -23,4 +23,10 @@ + + + + + + diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs index cecda73f7c9..b3bdf286f34 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs @@ -30,12 +30,20 @@ public PrometheusHttpListener(PrometheusExporter exporter, PrometheusHttpListene string path = options.ScrapeEndpointPath ?? PrometheusHttpListenerOptions.DefaultScrapeEndpointPath; - if (!path.StartsWith("/")) +#if NET + if (!path.StartsWith('/')) +#else + if (!path.StartsWith("/", StringComparison.Ordinal)) +#endif { path = $"/{path}"; } - if (!path.EndsWith("/")) +#if NET + if (!path.EndsWith('/')) +#else + if (!path.EndsWith("/", StringComparison.Ordinal)) +#endif { path = $"{path}/"; } @@ -164,7 +172,11 @@ private async Task ProcessRequestAsync(HttpListenerContext context) ? "application/openmetrics-text; version=1.0.0; charset=utf-8" : "text/plain; charset=utf-8; version=0.0.4"; - await context.Response.OutputStream.WriteAsync(dataView.Array!, 0, dataView.Count).ConfigureAwait(false); +#if NET + await context.Response.OutputStream.WriteAsync(dataView.Array.AsMemory(0, dataView.Count)).ConfigureAwait(false); +#else + await context.Response.OutputStream.WriteAsync(dataView.Array, 0, dataView.Count).ConfigureAwait(false); +#endif } else { diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs index 7289432cdcf..94eea0f5742 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs @@ -62,7 +62,7 @@ public static MeterProviderBuilder AddPrometheusHttpListener( }); } - private static MetricReader BuildPrometheusHttpListenerMetricReader( + private static BaseExportingMetricReader BuildPrometheusHttpListenerMetricReader( PrometheusHttpListenerOptions options) { var exporter = new PrometheusExporter(new PrometheusExporterOptions @@ -78,8 +78,10 @@ private static MetricReader BuildPrometheusHttpListenerMetricReader( try { +#pragma warning disable CA2000 // Dispose objects before losing scope var listener = new PrometheusHttpListener(exporter, options); - exporter.OnDispose = () => listener.Dispose(); +#pragma warning restore CA2000 // Dispose objects before losing scope + exporter.OnDispose = listener.Dispose; listener.Start(); } catch (Exception ex) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs index dbe20b726c4..8909a3bd82e 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs @@ -12,7 +12,7 @@ public class PrometheusHttpListenerOptions { internal const string DefaultScrapeEndpointPath = "/metrics"; - private IReadOnlyCollection uriPrefixes = new[] { "http://localhost:9464/" }; + private IReadOnlyCollection uriPrefixes = ["http://localhost:9464/"]; /// /// Gets or sets the path to use for the scraping endpoint. Default value: "/metrics". diff --git a/src/OpenTelemetry.Exporter.Zipkin/AssemblyInfo.cs b/src/OpenTelemetry.Exporter.Zipkin/AssemblyInfo.cs deleted file mode 100644 index 7ce3243549d..00000000000 --- a/src/OpenTelemetry.Exporter.Zipkin/AssemblyInfo.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Runtime.CompilerServices; - -#if SIGNED -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Zipkin.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -[assembly: InternalsVisibleTo("Benchmarks, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -#else -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Zipkin.Tests")] -[assembly: InternalsVisibleTo("Benchmarks")] -#endif diff --git a/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md b/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md index bd3cb442aeb..f22f4de85d9 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md @@ -6,6 +6,27 @@ Notes](../../RELEASENOTES.md). ## Unreleased +## 1.13.0 + +Released 2025-Oct-01 + +* Removed the peer service resolver, which was based on earlier experimental + semantic conventions that are not part of the stable specification. This + change ensures that the exporter no longer modifies or assumes the value of + peer service attributes. + ([#6191](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6191)) + +* Extended remote endpoint calculation to align with the [opentelemetry-specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.40.0/specification/trace/sdk_exporters/zipkin.md#otlp---zipkin). + ([#6191](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6191)) + +## 1.12.0 + +Released 2025-Apr-29 + +## 1.11.2 + +Released 2025-Mar-04 + ## 1.11.1 Released 2025-Jan-22 @@ -209,7 +230,7 @@ Released 2022-June-1 ([#3281](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3281)) * Fix exporting of array-valued attributes on an `Activity`. Previously, each item in the array would result in a new tag on an exported `Activity`. Now, - array-valued attributes are serialzed to a JSON-array representation. + array-valued attributes are serialized to a JSON-array representation. ([#3281](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3281)) ## 1.3.0-beta.2 diff --git a/src/Shared/PooledList.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/PooledList.cs similarity index 100% rename from src/Shared/PooledList.cs rename to src/OpenTelemetry.Exporter.Zipkin/Implementation/PooledList.cs diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs index 2753f51f50a..247cd87463b 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs @@ -20,103 +20,15 @@ internal static class ZipkinActivityConversionExtensions internal static ZipkinSpan ToZipkinSpan(this Activity activity, ZipkinEndpoint localEndpoint, bool useShortTraceIds = false) { var context = activity.Context; + string? parentId = activity.ParentSpanId == default ? null : EncodeSpanId(activity.ParentSpanId); - string? parentId = activity.ParentSpanId == default ? - null - : EncodeSpanId(activity.ParentSpanId); + var tags = PooledList>.Create(); + ExtractActivityTags(activity, ref tags); + ExtractActivityStatus(activity, ref tags); + ExtractActivitySource(activity, ref tags); - var tagState = new TagEnumerationState - { - Tags = PooledList>.Create(), - }; - - tagState.EnumerateTags(activity); - - // When status is set on Activity using the native Status field in activity, - // which was first introduced in System.Diagnostic.DiagnosticSource 6.0.0. - if (activity.Status != ActivityStatusCode.Unset) - { - if (activity.Status == ActivityStatusCode.Ok) - { - PooledList>.Add( - ref tagState.Tags, - new KeyValuePair( - SpanAttributeConstants.StatusCodeKey, - "OK")); - } - - // activity.Status is Error - else - { - PooledList>.Add( - ref tagState.Tags, - new KeyValuePair( - SpanAttributeConstants.StatusCodeKey, - "ERROR")); - - // Error flag rule from https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/zipkin.md#status - PooledList>.Add( - ref tagState.Tags, - new KeyValuePair( - ZipkinErrorFlagTagName, - activity.StatusDescription ?? string.Empty)); - } - } - - // In the case when both activity status and status tag were set, - // activity status takes precedence over status tag. - else if (tagState.StatusCode.HasValue && tagState.StatusCode != StatusCode.Unset) - { - PooledList>.Add( - ref tagState.Tags, - new KeyValuePair( - SpanAttributeConstants.StatusCodeKey, - StatusHelper.GetTagValueForStatusCode(tagState.StatusCode.Value))); - - // Error flag rule from https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/zipkin.md#status - if (tagState.StatusCode == StatusCode.Error) - { - PooledList>.Add( - ref tagState.Tags, - new KeyValuePair( - ZipkinErrorFlagTagName, - tagState.StatusDescription ?? string.Empty)); - } - } - - var activitySource = activity.Source; - if (!string.IsNullOrEmpty(activitySource.Name)) - { - PooledList>.Add(ref tagState.Tags, new KeyValuePair("otel.scope.name", activitySource.Name)); - - // otel.library.name is deprecated, but has to be propagated according to https://github.com/open-telemetry/opentelemetry-specification/blob/v1.31.0/specification/common/mapping-to-non-otlp.md#instrumentationscope - PooledList>.Add(ref tagState.Tags, new KeyValuePair("otel.library.name", activitySource.Name)); - if (!string.IsNullOrEmpty(activitySource.Version)) - { - PooledList>.Add(ref tagState.Tags, new KeyValuePair("otel.scope.version", activitySource.Version)); - - // otel.library.version is deprecated, but has to be propagated according to https://github.com/open-telemetry/opentelemetry-specification/blob/v1.31.0/specification/common/mapping-to-non-otlp.md#instrumentationscope - PooledList>.Add(ref tagState.Tags, new KeyValuePair("otel.library.version", activitySource.Version)); - } - } - - ZipkinEndpoint? remoteEndpoint = null; - if (activity.Kind == ActivityKind.Client || activity.Kind == ActivityKind.Producer) - { - PeerServiceResolver.Resolve(ref tagState, out string? peerServiceName, out bool addAsTag); - - if (peerServiceName != null) - { - remoteEndpoint = RemoteEndpointCache.GetOrAdd((peerServiceName, default), ZipkinEndpoint.Create); - if (addAsTag) - { - PooledList>.Add(ref tagState.Tags, new KeyValuePair(SemanticConventions.AttributePeerService, peerServiceName)); - } - } - } - - EventEnumerationState eventState = default; - eventState.EnumerateEvents(activity); + ZipkinEndpoint? remoteEndpoint = ExtractRemoteEndpoint(activity); + var annotations = ExtractActivityEvents(activity); return new ZipkinSpan( EncodeTraceId(context.TraceId, useShortTraceIds), @@ -128,8 +40,8 @@ internal static ZipkinSpan ToZipkinSpan(this Activity activity, ZipkinEndpoint l duration: activity.Duration.ToEpochMicroseconds(), localEndpoint, remoteEndpoint, - eventState.Annotations, - tagState.Tags, + annotations, + tags, null, null); } @@ -184,89 +96,235 @@ private static string EncodeTraceId(ActivityTraceId traceId, bool useShortTraceI }; } - internal struct TagEnumerationState : PeerServiceResolver.IPeerServiceState + private static string ExtractStatusDescription(Activity activity) { - public PooledList> Tags; - - public string? PeerService { get; set; } - - public int? PeerServicePriority { get; set; } - - public string? HostName { get; set; } - - public string? IpAddress { get; set; } + return activity.StatusDescription + ?? activity.GetTagItem(SpanAttributeConstants.StatusDescriptionKey) as string + ?? activity.GetTagItem(ZipkinErrorFlagTagName) as string + ?? string.Empty; + } - public long Port { get; set; } + private static void ExtractActivityTags(Activity activity, ref PooledList> tags) + { + foreach (ref readonly var tag in activity.EnumerateTagObjects()) + { + if (tag.Key != ZipkinErrorFlagTagName && tag.Key != SpanAttributeConstants.StatusCodeKey) + { + PooledList>.Add(ref tags, tag); + } + } + } - public StatusCode? StatusCode { get; set; } + private static void ExtractActivityStatus(Activity activity, ref PooledList> tags) + { + // When status is set on Activity using the native Status field in activity, + // which was first introduced in System.Diagnostic.DiagnosticSource 6.0.0. + if (activity.Status != ActivityStatusCode.Unset) + { + if (activity.Status == ActivityStatusCode.Ok) + { + PooledList>.Add( + ref tags, + new KeyValuePair( + SpanAttributeConstants.StatusCodeKey, + "OK")); + } - public string StatusDescription { get; set; } + // activity.Status is Error + else + { + string statusDescription = ExtractStatusDescription(activity); + PooledList>.Add( + ref tags, + new KeyValuePair( + SpanAttributeConstants.StatusCodeKey, + "ERROR")); - public void EnumerateTags(Activity activity) + // Error flag rule from https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/zipkin.md#status + PooledList>.Add( + ref tags, + new KeyValuePair( + ZipkinErrorFlagTagName, + statusDescription)); + } + } + else { - foreach (ref readonly var tag in activity.EnumerateTagObjects()) + if (activity.GetTagItem(SpanAttributeConstants.StatusCodeKey) is string status) { - if (tag.Value == null) + if (status == "OK") { - continue; - } - - string key = tag.Key; + activity.SetStatus(ActivityStatusCode.Ok); - if (tag.Value is string strVal) - { - PeerServiceResolver.InspectTag(ref this, key, strVal); - - if (key == SpanAttributeConstants.StatusCodeKey) - { - this.StatusCode = StatusHelper.GetStatusCodeForTagValue(strVal); - continue; - } - else if (key == SpanAttributeConstants.StatusDescriptionKey) - { - // Description is sent as `error` but only if StatusCode is Error. See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/zipkin.md#status - this.StatusDescription = strVal; - continue; - } - else if (key == ZipkinErrorFlagTagName) - { - // Ignore `error` tag if it exists, it will be added based on StatusCode + StatusDescription. - continue; - } + PooledList>.Add( + ref tags, + new KeyValuePair( + SpanAttributeConstants.StatusCodeKey, + "OK")); } - else if (tag.Value is int intVal && tag.Key == SemanticConventions.AttributeNetPeerPort) + else if (status == "ERROR") { - PeerServiceResolver.InspectTag(ref this, key, intVal); - } + string statusDescription = ExtractStatusDescription(activity); + + activity.SetStatus(ActivityStatusCode.Error); + + PooledList>.Add( + ref tags, + new KeyValuePair( + SpanAttributeConstants.StatusCodeKey, + "ERROR")); - PooledList>.Add(ref this.Tags, tag); + PooledList>.Add( + ref tags, + new KeyValuePair( + ZipkinErrorFlagTagName, + statusDescription)); + } } } } - private struct EventEnumerationState + private static void ExtractActivitySource(Activity activity, ref PooledList> tags) { - public bool Created; + var source = activity.Source; + if (!string.IsNullOrEmpty(source.Name)) + { + PooledList>.Add(ref tags, new KeyValuePair("otel.scope.name", source.Name)); + + // otel.library.name is deprecated, but has to be propagated according to https://github.com/open-telemetry/opentelemetry-specification/blob/v1.31.0/specification/common/mapping-to-non-otlp.md#instrumentationscope + PooledList>.Add(ref tags, new KeyValuePair("otel.library.name", source.Name)); + + if (!string.IsNullOrEmpty(source.Version)) + { + PooledList>.Add(ref tags, new KeyValuePair("otel.scope.version", source.Version)); - public PooledList Annotations; + // otel.library.version is deprecated, but has to be propagated according to https://github.com/open-telemetry/opentelemetry-specification/blob/v1.31.0/specification/common/mapping-to-non-otlp.md#instrumentationscope + PooledList>.Add(ref tags, new KeyValuePair("otel.library.version", source.Version)); + } + } + } - public void EnumerateEvents(Activity activity) + private static ZipkinEndpoint? ExtractRemoteEndpoint(Activity activity) + { + if (activity.Kind != ActivityKind.Client && activity.Kind != ActivityKind.Producer) { - var enumerator = activity.EnumerateEvents(); + return null; + } - if (enumerator.MoveNext()) + static ZipkinEndpoint? TryCreateEndpoint(string? remoteEndpoint) + { + if (remoteEndpoint != null) { - this.Annotations = PooledList.Create(); - this.Created = true; + var endpoint = RemoteEndpointCache.GetOrAdd((remoteEndpoint, default), ZipkinEndpoint.Create); + return endpoint; + } - do - { - ref readonly var @event = ref enumerator.Current; + return null; + } - PooledList.Add(ref this.Annotations, new ZipkinAnnotation(@event.Timestamp.ToEpochMicroseconds(), @event.Name)); - } - while (enumerator.MoveNext()); - } + string? remoteEndpoint = activity.GetTagItem(SemanticConventions.AttributePeerService) as string; + var endpoint = TryCreateEndpoint(remoteEndpoint); + if (endpoint != null) + { + return endpoint; } + + remoteEndpoint = activity.GetTagItem(SemanticConventions.AttributeServerAddress) as string; + endpoint = TryCreateEndpoint(remoteEndpoint); + if (endpoint != null) + { + return endpoint; + } + + remoteEndpoint = activity.GetTagItem(SemanticConventions.AttributeNetPeerName) as string; + endpoint = TryCreateEndpoint(remoteEndpoint); + if (endpoint != null) + { + return endpoint; + } + + var peerAddress = activity.GetTagItem(SemanticConventions.AttributeNetworkPeerAddress) as string; + var peerPort = activity.GetTagItem(SemanticConventions.AttributeNetworkPeerPort) as string; + remoteEndpoint = peerPort != null ? $"{peerAddress}:{peerPort}" : peerAddress; + endpoint = TryCreateEndpoint(remoteEndpoint); + if (endpoint != null) + { + return endpoint; + } + + remoteEndpoint = activity.GetTagItem(SemanticConventions.AttributeServerSocketDomain) as string; + endpoint = TryCreateEndpoint(remoteEndpoint); + if (endpoint != null) + { + return endpoint; + } + + var serverAddress = activity.GetTagItem(SemanticConventions.AttributeServerSocketAddress) as string; + var serverPort = activity.GetTagItem(SemanticConventions.AttributeServerSocketPort) as string; + remoteEndpoint = serverPort != null ? $"{serverAddress}:{serverPort}" : serverAddress; + endpoint = TryCreateEndpoint(remoteEndpoint); + if (endpoint != null) + { + return endpoint; + } + + remoteEndpoint = activity.GetTagItem(SemanticConventions.AttributeNetSockPeerName) as string; + endpoint = TryCreateEndpoint(remoteEndpoint); + if (endpoint != null) + { + return endpoint; + } + + var socketAddress = activity.GetTagItem(SemanticConventions.AttributeNetSockPeerAddr) as string; + var socketPort = activity.GetTagItem(SemanticConventions.AttributeNetSockPeerPort) as string; + remoteEndpoint = socketPort != null ? $"{socketAddress}:{socketPort}" : socketAddress; + endpoint = TryCreateEndpoint(remoteEndpoint); + if (endpoint != null) + { + return endpoint; + } + + remoteEndpoint = activity.GetTagItem(SemanticConventions.AttributePeerHostname) as string; + endpoint = TryCreateEndpoint(remoteEndpoint); + if (endpoint != null) + { + return endpoint; + } + + remoteEndpoint = activity.GetTagItem(SemanticConventions.AttributePeerAddress) as string; + endpoint = TryCreateEndpoint(remoteEndpoint); + if (endpoint != null) + { + return endpoint; + } + + remoteEndpoint = activity.GetTagItem(SemanticConventions.AttributeDbName) as string; + endpoint = TryCreateEndpoint(remoteEndpoint); + if (endpoint != null) + { + return endpoint; + } + + return null; + } + + private static PooledList ExtractActivityEvents(Activity activity) + { + var enumerator = activity.EnumerateEvents().GetEnumerator(); + if (!enumerator.MoveNext()) + { + return default; + } + + var annotations = PooledList.Create(); + + do + { + var @event = enumerator.Current; + PooledList.Add(ref annotations, new ZipkinAnnotation(@event.Timestamp.ToEpochMicroseconds(), @event.Name)); + } + while (enumerator.MoveNext()); + + return annotations; } } diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagWriter.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagWriter.cs index 30e1eb112ee..3e1c42c69ec 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagWriter.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagWriter.cs @@ -64,4 +64,6 @@ protected override void OnUnsupportedTagDropped( tagValueTypeFullName, tagKey); } + + protected override bool TryWriteEmptyTag(ref Utf8JsonWriter state, string key, object? value) => false; } diff --git a/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj b/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj index 78d44fd3ee5..a9f9e7df834 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj +++ b/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj @@ -11,13 +11,11 @@ - - @@ -33,4 +31,9 @@ + + + + + diff --git a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporter.cs b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporter.cs index c65f1454284..103cacbcea5 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporter.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporter.cs @@ -225,6 +225,17 @@ protected override bool TryComputeLength(out long length) return false; } + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.writer?.Dispose(); + this.writer = null; + } + + base.Dispose(disposing); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SerializeToStreamInternal(Stream stream) { diff --git a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterHelperExtensions.cs b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterHelperExtensions.cs index b7142b985a5..869202b0f8d 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterHelperExtensions.cs @@ -68,12 +68,11 @@ public static TracerProviderBuilder AddZipkinExporter( { var options = sp.GetRequiredService>().Get(name); - return BuildZipkinExporterProcessor(builder, options, sp); + return BuildZipkinExporterProcessor(options, sp); }); } private static BaseProcessor BuildZipkinExporterProcessor( - TracerProviderBuilder builder, ZipkinExporterOptions options, IServiceProvider serviceProvider) { @@ -91,7 +90,7 @@ private static BaseProcessor BuildZipkinExporterProcessor( "CreateClient", BindingFlags.Public | BindingFlags.Instance, binder: null, - new Type[] { typeof(string) }, + [typeof(string)], modifiers: null); if (createClientMethod != null) { @@ -106,20 +105,17 @@ private static BaseProcessor BuildZipkinExporterProcessor( }; } +#pragma warning disable CA2000 // Dispose objects before losing scope var zipkinExporter = new ZipkinExporter(options); +#pragma warning restore CA2000 // Dispose objects before losing scope - if (options.ExportProcessorType == ExportProcessorType.Simple) - { - return new SimpleActivityExportProcessor(zipkinExporter); - } - else - { - return new BatchActivityExportProcessor( + return options.ExportProcessorType == ExportProcessorType.Simple + ? new SimpleActivityExportProcessor(zipkinExporter) + : new BatchActivityExportProcessor( zipkinExporter, options.BatchExportProcessorOptions.MaxQueueSize, options.BatchExportProcessorOptions.ScheduledDelayMilliseconds, options.BatchExportProcessorOptions.ExporterTimeoutMilliseconds, options.BatchExportProcessorOptions.MaxExportBatchSize); - } } } diff --git a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs index 1f4bcd14684..161ab174b2f 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs @@ -42,7 +42,7 @@ internal ZipkinExporterOptions( if (configuration!.TryGetUriValue(ZipkinExporterEventSource.Log, ZipkinEndpointEnvVar, out var endpoint)) { - this.Endpoint = endpoint!; + this.Endpoint = endpoint; } this.BatchExportProcessorOptions = defaultBatchOptions!; diff --git a/src/OpenTelemetry.Extensions.Hosting/AssemblyInfo.cs b/src/OpenTelemetry.Extensions.Hosting/AssemblyInfo.cs deleted file mode 100644 index d36465c0505..00000000000 --- a/src/OpenTelemetry.Extensions.Hosting/AssemblyInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting.Tests" + AssemblyInfo.PublicKey)] - -#if SIGNED -file static class AssemblyInfo -{ - public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; -} -#else -file static class AssemblyInfo -{ - public const string PublicKey = ""; -} -#endif diff --git a/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md b/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md index 635c5845252..3201a20f9f3 100644 --- a/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md +++ b/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md @@ -6,6 +6,18 @@ Notes](../../RELEASENOTES.md). ## Unreleased +## 1.13.0 + +Released 2025-Oct-01 + +## 1.12.0 + +Released 2025-Apr-29 + +## 1.11.2 + +Released 2025-Mar-04 + ## 1.11.1 Released 2025-Jan-22 diff --git a/src/OpenTelemetry.Extensions.Hosting/Implementation/HostingExtensionsEventSource.cs b/src/OpenTelemetry.Extensions.Hosting/Implementation/HostingExtensionsEventSource.cs index 1d31393b0fa..af4bb57a7f2 100644 --- a/src/OpenTelemetry.Extensions.Hosting/Implementation/HostingExtensionsEventSource.cs +++ b/src/OpenTelemetry.Extensions.Hosting/Implementation/HostingExtensionsEventSource.cs @@ -11,7 +11,7 @@ namespace OpenTelemetry.Extensions.Hosting.Implementation; [EventSource(Name = "OpenTelemetry-Extensions-Hosting")] internal sealed class HostingExtensionsEventSource : EventSource { - public static HostingExtensionsEventSource Log = new(); + public static readonly HostingExtensionsEventSource Log = new(); [Event(1, Message = "OpenTelemetry TracerProvider was not found in application services. Tracing will remain disabled.", Level = EventLevel.Warning)] public void TracerProviderNotRegistered() diff --git a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetry.Extensions.Hosting.csproj b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetry.Extensions.Hosting.csproj index ee51a4f8543..22d961e6a88 100644 --- a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetry.Extensions.Hosting.csproj +++ b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetry.Extensions.Hosting.csproj @@ -5,7 +5,6 @@ Contains extensions to start OpenTelemetry in applications using Microsoft.Extensions.Hosting OpenTelemetry core- - latest-all @@ -16,4 +15,8 @@ + + + + diff --git a/src/OpenTelemetry.Extensions.Propagators/AssemblyInfo.cs b/src/OpenTelemetry.Extensions.Propagators/AssemblyInfo.cs deleted file mode 100644 index a06279f44f2..00000000000 --- a/src/OpenTelemetry.Extensions.Propagators/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Runtime.CompilerServices; - -#if SIGNED -[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Propagators.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898")] -#else -[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Propagators.Tests")] -#endif diff --git a/src/OpenTelemetry.Extensions.Propagators/B3Propagator.cs b/src/OpenTelemetry.Extensions.Propagators/B3Propagator.cs index 45239981f0a..a7ede4f0a6a 100644 --- a/src/OpenTelemetry.Extensions.Propagators/B3Propagator.cs +++ b/src/OpenTelemetry.Extensions.Propagators/B3Propagator.cs @@ -27,6 +27,7 @@ public sealed class B3Propagator : TextMapPropagator internal const string UpperTraceId = "0000000000000000"; // Sampled values via the X_B3_SAMPLED header. + internal const char SampledValueChar = '1'; internal const string SampledValue = "1"; // Some old zipkin implementations may send true/false for the sampled header. Only use this for checking incoming values. @@ -35,7 +36,7 @@ public sealed class B3Propagator : TextMapPropagator // "Debug" sampled value. internal const string FlagsValue = "1"; - private static readonly HashSet AllFields = new() { XB3TraceId, XB3SpanId, XB3ParentSpanId, XB3Sampled, XB3Flags }; + private static readonly HashSet AllFields = [XB3TraceId, XB3SpanId, XB3ParentSpanId, XB3Sampled, XB3Flags]; private static readonly HashSet SampledValues = new(StringComparer.Ordinal) { SampledValue, LegacySampledValue }; @@ -82,14 +83,7 @@ public override PropagationContext Extract(PropagationContext context, T carr return context; } - if (this.singleHeader) - { - return ExtractFromSingleHeader(context, carrier, getter); - } - else - { - return ExtractFromMultipleHeaders(context, carrier, getter); - } + return this.singleHeader ? ExtractFromSingleHeader(context, carrier, getter) : ExtractFromMultipleHeaders(context, carrier, getter); } /// @@ -122,7 +116,7 @@ public override void Inject(PropagationContext context, T carrier, Action(PropagationContext { try { - var header = getter(carrier, XB3Combined)?.FirstOrDefault(); + var headers = getter(carrier, XB3Combined); + if (headers == null) + { + return context; + } + + var header = headers.FirstOrDefault(); + if (string.IsNullOrWhiteSpace(header)) { return context; } - var parts = header!.Split(XB3CombinedDelimiter); + var parts = header.Split(XB3CombinedDelimiter); if (parts.Length < 2 || parts.Length > 4) { return context; diff --git a/src/OpenTelemetry.Extensions.Propagators/CHANGELOG.md b/src/OpenTelemetry.Extensions.Propagators/CHANGELOG.md index f340ca81f54..2170045f1d7 100644 --- a/src/OpenTelemetry.Extensions.Propagators/CHANGELOG.md +++ b/src/OpenTelemetry.Extensions.Propagators/CHANGELOG.md @@ -6,6 +6,18 @@ covering all components see: [Release Notes](../../RELEASENOTES.md). ## Unreleased +## 1.13.0 + +Released 2025-Oct-01 + +## 1.12.0 + +Released 2025-Apr-29 + +## 1.11.2 + +Released 2025-Mar-04 + ## 1.11.1 Released 2025-Jan-22 diff --git a/src/OpenTelemetry.Extensions.Propagators/JaegerPropagator.cs b/src/OpenTelemetry.Extensions.Propagators/JaegerPropagator.cs index 045fd93e581..d11d8d67912 100644 --- a/src/OpenTelemetry.Extensions.Propagators/JaegerPropagator.cs +++ b/src/OpenTelemetry.Extensions.Propagators/JaegerPropagator.cs @@ -17,7 +17,7 @@ public class JaegerPropagator : TextMapPropagator internal const string JaegerDelimiterEncoded = "%3A"; // while the spec defines the delimiter as a ':', some clients will url encode headers. internal const string SampledValue = "1"; - internal static readonly string[] JaegerDelimiters = { JaegerDelimiter, JaegerDelimiterEncoded }; + internal static readonly string[] JaegerDelimiters = [JaegerDelimiter, JaegerDelimiterEncoded]; private static readonly int TraceId128BitLength = "0af7651916cd43dd8448eb211c80319c".Length; private static readonly int SpanIdLength = "00f067aa0ba902b7".Length; @@ -49,14 +49,19 @@ public override PropagationContext Extract(PropagationContext context, T carr try { var jaegerHeaderCollection = getter(carrier, JaegerHeader); - var jaegerHeader = jaegerHeaderCollection?.First(); + if (jaegerHeaderCollection == null) + { + return context; + } + + var jaegerHeader = jaegerHeaderCollection.First(); if (string.IsNullOrWhiteSpace(jaegerHeader)) { return context; } - var jaegerHeaderParsed = TryExtractTraceContext(jaegerHeader!, out var traceId, out var spanId, out var traceOptions); + var jaegerHeaderParsed = TryExtractTraceContext(jaegerHeader, out var traceId, out var spanId, out var traceOptions); if (!jaegerHeaderParsed) { @@ -154,7 +159,7 @@ internal static bool TryExtractTraceContext(string jaegerHeader, out ActivityTra spanId = ActivitySpanId.CreateFromString(spanIdStr.AsSpan()); var traceFlagsStr = traceComponents[3]; - if (SampledValue.Equals(traceFlagsStr)) + if (SampledValue.Equals(traceFlagsStr, StringComparison.Ordinal)) { traceOptions |= ActivityTraceFlags.Recorded; } diff --git a/src/OpenTelemetry.Extensions.Propagators/OpenTelemetry.Extensions.Propagators.csproj b/src/OpenTelemetry.Extensions.Propagators/OpenTelemetry.Extensions.Propagators.csproj index 01ae76f4a17..34f57c8043e 100644 --- a/src/OpenTelemetry.Extensions.Propagators/OpenTelemetry.Extensions.Propagators.csproj +++ b/src/OpenTelemetry.Extensions.Propagators/OpenTelemetry.Extensions.Propagators.csproj @@ -12,7 +12,11 @@ - $(NoWarn),1591 + $(NoWarn),CS1591 + + + + diff --git a/src/OpenTelemetry.Shims.OpenTracing/AssemblyInfo.cs b/src/OpenTelemetry.Shims.OpenTracing/AssemblyInfo.cs deleted file mode 100644 index ae3704ebf27..00000000000 --- a/src/OpenTelemetry.Shims.OpenTracing/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Runtime.CompilerServices; - -#if SIGNED -[assembly: InternalsVisibleTo("OpenTelemetry.Shims.OpenTracing.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -#else -[assembly: InternalsVisibleTo("OpenTelemetry.Shims.OpenTracing.Tests")] -#endif diff --git a/src/OpenTelemetry.Shims.OpenTracing/CHANGELOG.md b/src/OpenTelemetry.Shims.OpenTracing/CHANGELOG.md index 65dcca4f66f..67954363842 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/CHANGELOG.md +++ b/src/OpenTelemetry.Shims.OpenTracing/CHANGELOG.md @@ -6,6 +6,20 @@ Notes](../../RELEASENOTES.md). ## Unreleased +## 1.12.0-beta.1 + +Released 2025-May-06 + +* Updated OpenTelemetry core component version(s) to `1.12.0`. + ([#6269](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6269)) + +## 1.11.2-beta.1 + +Released 2025-Mar-05 + +* Updated OpenTelemetry core component version(s) to `1.11.2`. + ([#6169](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6169)) + ## 1.11.0-beta.1 Released 2025-Jan-16 diff --git a/src/OpenTelemetry.Shims.OpenTracing/OpenTelemetry.Shims.OpenTracing.csproj b/src/OpenTelemetry.Shims.OpenTracing/OpenTelemetry.Shims.OpenTracing.csproj index 127cbfb3aa9..4262f637d14 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/OpenTelemetry.Shims.OpenTracing.csproj +++ b/src/OpenTelemetry.Shims.OpenTracing/OpenTelemetry.Shims.OpenTracing.csproj @@ -24,4 +24,8 @@ + + + + diff --git a/src/OpenTelemetry.Shims.OpenTracing/SpanBuilderShim.cs b/src/OpenTelemetry.Shims.OpenTracing/SpanBuilderShim.cs index f95b1b77be3..8f256920127 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/SpanBuilderShim.cs +++ b/src/OpenTelemetry.Shims.OpenTracing/SpanBuilderShim.cs @@ -27,7 +27,7 @@ internal sealed class SpanBuilderShim : ISpanBuilder /// /// The OpenTelemetry links. These correspond loosely to OpenTracing references. /// - private readonly List links = new(); + private readonly List links = []; /// /// The OpenTelemetry attributes. These correspond to OpenTracing Tags. @@ -65,7 +65,7 @@ public SpanBuilderShim(Tracer tracer, string spanName) this.ScopeManager = new ScopeManagerShim(); } - private IScopeManager ScopeManager { get; } + private ScopeManagerShim ScopeManager { get; } private bool ParentSet => this.parentSpan != null || this.parentSpanContext.IsValid; @@ -148,10 +148,7 @@ public ISpan Start() span = this.tracer.StartSpan(this.spanName, this.spanKind, this.parentSpanContext, this.attributes, this.links, this.explicitStartTime ?? default); } - if (span == null) - { - span = this.tracer.StartSpan(this.spanName, this.spanKind, default(SpanContext), this.attributes, null, this.explicitStartTime ?? default); - } + span ??= this.tracer.StartSpan(this.spanName, this.spanKind, default(SpanContext), this.attributes, null, this.explicitStartTime ?? default); if (this.error) { diff --git a/src/OpenTelemetry.Shims.OpenTracing/SpanShim.cs b/src/OpenTelemetry.Shims.OpenTracing/SpanShim.cs index c1bde927a1b..f8a5d981207 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/SpanShim.cs +++ b/src/OpenTelemetry.Shims.OpenTracing/SpanShim.cs @@ -14,8 +14,8 @@ internal sealed class SpanShim : ISpan /// public const string DefaultEventName = "log"; - private static readonly IReadOnlyCollection OpenTelemetrySupportedAttributeValueTypes = new List - { + private static readonly IReadOnlyCollection OpenTelemetrySupportedAttributeValueTypes = + [ typeof(string), typeof(bool), typeof(byte), @@ -24,7 +24,7 @@ internal sealed class SpanShim : ISpan typeof(long), typeof(float), typeof(double), - }; + ]; private readonly SpanContextShim spanContextShim; diff --git a/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs b/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs index 504b28c5a52..b67dfd9a82a 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs +++ b/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs @@ -48,13 +48,7 @@ public TracerShim(Trace.TracerProvider tracerProvider, TextMapPropagator? textFo /// public global::OpenTracing.ISpan? ActiveSpan => this.ScopeManager.Active?.Span; - private TextMapPropagator Propagator - { - get - { - return this.definedPropagator ?? Propagators.DefaultTextMapPropagator; - } - } + private TextMapPropagator Propagator => this.definedPropagator ?? Propagators.DefaultTextMapPropagator; /// public global::OpenTracing.ISpanBuilder BuildSpan(string operationName) @@ -76,7 +70,7 @@ private TextMapPropagator Propagator foreach (var entry in textMapCarrier) { - carrierMap.Add(entry.Key, new[] { entry.Value }); + carrierMap.Add(entry.Key, [entry.Value]); } static IEnumerable? GetCarrierKeyValue(Dictionary> source, string key) diff --git a/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt index 7c9a6d046ae..b9507b58b38 100644 --- a/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt @@ -1,27 +1,27 @@ -abstract OpenTelemetry.Metrics.ExemplarReservoir.Collect() -> OpenTelemetry.Metrics.ReadOnlyExemplarCollection -abstract OpenTelemetry.Metrics.ExemplarReservoir.Offer(in OpenTelemetry.Metrics.ExemplarMeasurement measurement) -> void -abstract OpenTelemetry.Metrics.ExemplarReservoir.Offer(in OpenTelemetry.Metrics.ExemplarMeasurement measurement) -> void +[OTEL1004]abstract OpenTelemetry.Metrics.ExemplarReservoir.Collect() -> OpenTelemetry.Metrics.ReadOnlyExemplarCollection +[OTEL1004]abstract OpenTelemetry.Metrics.ExemplarReservoir.Offer(in OpenTelemetry.Metrics.ExemplarMeasurement measurement) -> void +[OTEL1004]abstract OpenTelemetry.Metrics.ExemplarReservoir.Offer(in OpenTelemetry.Metrics.ExemplarMeasurement measurement) -> void OpenTelemetry.Logs.LogRecord.Logger.get -> OpenTelemetry.Logs.Logger! OpenTelemetry.Logs.LogRecord.Severity.get -> OpenTelemetry.Logs.LogRecordSeverity? OpenTelemetry.Logs.LogRecord.Severity.set -> void OpenTelemetry.Logs.LogRecord.SeverityText.get -> string? OpenTelemetry.Logs.LogRecord.SeverityText.set -> void -OpenTelemetry.Metrics.ExemplarMeasurement -OpenTelemetry.Metrics.ExemplarMeasurement.ExemplarMeasurement() -> void -OpenTelemetry.Metrics.ExemplarMeasurement.Tags.get -> System.ReadOnlySpan> -OpenTelemetry.Metrics.ExemplarMeasurement.Value.get -> T -OpenTelemetry.Metrics.ExemplarReservoir -OpenTelemetry.Metrics.ExemplarReservoir.ResetOnCollect.get -> bool -OpenTelemetry.Metrics.FixedSizeExemplarReservoir -OpenTelemetry.Metrics.FixedSizeExemplarReservoir.Capacity.get -> int -OpenTelemetry.Metrics.FixedSizeExemplarReservoir.FixedSizeExemplarReservoir(int capacity) -> void -OpenTelemetry.Metrics.FixedSizeExemplarReservoir.UpdateExemplar(int exemplarIndex, in OpenTelemetry.Metrics.ExemplarMeasurement measurement) -> void -OpenTelemetry.Metrics.FixedSizeExemplarReservoir.UpdateExemplar(int exemplarIndex, in OpenTelemetry.Metrics.ExemplarMeasurement measurement) -> void +[OTEL1004]OpenTelemetry.Metrics.ExemplarMeasurement +[OTEL1004]OpenTelemetry.Metrics.ExemplarMeasurement.ExemplarMeasurement() -> void +[OTEL1004]OpenTelemetry.Metrics.ExemplarMeasurement.Tags.get -> System.ReadOnlySpan> +[OTEL1004]OpenTelemetry.Metrics.ExemplarMeasurement.Value.get -> T +[OTEL1004]OpenTelemetry.Metrics.ExemplarReservoir +[OTEL1004]OpenTelemetry.Metrics.ExemplarReservoir.ResetOnCollect.get -> bool +[OTEL1004]OpenTelemetry.Metrics.FixedSizeExemplarReservoir +[OTEL1004]OpenTelemetry.Metrics.FixedSizeExemplarReservoir.Capacity.get -> int +[OTEL1004]OpenTelemetry.Metrics.FixedSizeExemplarReservoir.FixedSizeExemplarReservoir(int capacity) -> void +[OTEL1004]OpenTelemetry.Metrics.FixedSizeExemplarReservoir.UpdateExemplar(int exemplarIndex, in OpenTelemetry.Metrics.ExemplarMeasurement measurement) -> void +[OTEL1004]OpenTelemetry.Metrics.FixedSizeExemplarReservoir.UpdateExemplar(int exemplarIndex, in OpenTelemetry.Metrics.ExemplarMeasurement measurement) -> void OpenTelemetry.Metrics.MetricStreamConfiguration.ExemplarReservoirFactory.get -> System.Func? OpenTelemetry.Metrics.MetricStreamConfiguration.ExemplarReservoirFactory.set -> void -override sealed OpenTelemetry.Metrics.FixedSizeExemplarReservoir.Collect() -> OpenTelemetry.Metrics.ReadOnlyExemplarCollection -static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.UseOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder) -> Microsoft.Extensions.Logging.ILoggingBuilder! -static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.UseOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action! configure) -> Microsoft.Extensions.Logging.ILoggingBuilder! -static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.UseOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configureBuilder, System.Action? configureOptions) -> Microsoft.Extensions.Logging.ILoggingBuilder! -static OpenTelemetry.Sdk.CreateLoggerProviderBuilder() -> OpenTelemetry.Logs.LoggerProviderBuilder! -virtual OpenTelemetry.Metrics.FixedSizeExemplarReservoir.OnCollected() -> void +[OTEL1004]override sealed OpenTelemetry.Metrics.FixedSizeExemplarReservoir.Collect() -> OpenTelemetry.Metrics.ReadOnlyExemplarCollection +[OTEL1000]static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.UseOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder) -> Microsoft.Extensions.Logging.ILoggingBuilder! +[OTEL1000]static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.UseOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action! configure) -> Microsoft.Extensions.Logging.ILoggingBuilder! +[OTEL1000]static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.UseOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configureBuilder, System.Action? configureOptions) -> Microsoft.Extensions.Logging.ILoggingBuilder! +[OTEL1001]static OpenTelemetry.Sdk.CreateLoggerProviderBuilder() -> OpenTelemetry.Logs.LoggerProviderBuilder! +[OTEL1004]virtual OpenTelemetry.Metrics.FixedSizeExemplarReservoir.OnCollected() -> void diff --git a/src/OpenTelemetry/AssemblyInfo.cs b/src/OpenTelemetry/AssemblyInfo.cs deleted file mode 100644 index 46c323088a6..00000000000 --- a/src/OpenTelemetry/AssemblyInfo.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Console" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.InMemory" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.AspNetCore" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.HttpListener.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Tests.Stress.Metrics" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("Benchmarks" + AssemblyInfo.PublicKey)] - -#if SIGNED -file static class AssemblyInfo -{ - public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; -} -#else -file static class AssemblyInfo -{ - public const string PublicKey = ""; -} -#endif diff --git a/src/OpenTelemetry/BaseExportProcessor.cs b/src/OpenTelemetry/BaseExportProcessor.cs index 2e9fe7619c2..bafa4c117ca 100644 --- a/src/OpenTelemetry/BaseExportProcessor.cs +++ b/src/OpenTelemetry/BaseExportProcessor.cs @@ -29,13 +29,17 @@ public enum ExportProcessorType /// Implements processor that exports telemetry objects. /// /// The type of telemetry object to be exported. +#pragma warning disable CA1708 // Identifiers should differ by more than case public abstract class BaseExportProcessor : BaseProcessor +#pragma warning restore CA1708 // Identifiers should differ by more than case where T : class { /// /// Gets the exporter used by the processor. /// +#pragma warning disable CA1051 // Do not declare visible instance fields protected readonly BaseExporter exporter; +#pragma warning restore CA1051 // Do not declare visible instance fields private readonly string friendlyTypeName; private bool disposed; @@ -48,7 +52,9 @@ protected BaseExportProcessor(BaseExporter exporter) { Guard.ThrowIfNull(exporter); +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 this.friendlyTypeName = $"{this.GetType().Name}{{{exporter.GetType().Name}}}"; +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 this.exporter = exporter; } diff --git a/src/OpenTelemetry/BaseProcessor.cs b/src/OpenTelemetry/BaseProcessor.cs index daa7468c2d7..b75cc24d86e 100644 --- a/src/OpenTelemetry/BaseProcessor.cs +++ b/src/OpenTelemetry/BaseProcessor.cs @@ -9,7 +9,9 @@ namespace OpenTelemetry; /// Base processor base class. /// /// The type of object to be processed. +#pragma warning disable CA1012 // Abstract types should not have public constructors public abstract class BaseProcessor : IDisposable +#pragma warning restore CA1012 // Abstract types should not have public constructors { private readonly string typeName; private int shutdownCount; diff --git a/src/OpenTelemetry/Batch.cs b/src/OpenTelemetry/Batch.cs index 033b6ebb35b..4f0e0608dcf 100644 --- a/src/OpenTelemetry/Batch.cs +++ b/src/OpenTelemetry/Batch.cs @@ -29,7 +29,9 @@ namespace OpenTelemetry; public Batch(T[] items, int count) { Guard.ThrowIfNull(items); +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 Guard.ThrowIfOutOfRange(count, min: 0, max: items.Length); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 this.items = items; this.Count = this.targetCount = count; diff --git a/src/OpenTelemetry/BatchExportProcessor.cs b/src/OpenTelemetry/BatchExportProcessor.cs index b377d5e89e5..46a64b8e042 100644 --- a/src/OpenTelemetry/BatchExportProcessor.cs +++ b/src/OpenTelemetry/BatchExportProcessor.cs @@ -60,7 +60,9 @@ protected BatchExportProcessor( this.exporterThread = new Thread(this.ExporterProc) { IsBackground = true, +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 Name = $"OpenTelemetry-{nameof(BatchExportProcessor)}-{exporter.GetType().Name}", +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 }; this.exporterThread.Start(); } diff --git a/src/OpenTelemetry/BatchExportProcessorOptions.cs b/src/OpenTelemetry/BatchExportProcessorOptions.cs index 67345e9715c..6695c0c6a56 100644 --- a/src/OpenTelemetry/BatchExportProcessorOptions.cs +++ b/src/OpenTelemetry/BatchExportProcessorOptions.cs @@ -26,7 +26,7 @@ public class BatchExportProcessorOptions public int ExporterTimeoutMilliseconds { get; set; } = BatchExportProcessor.DefaultExporterTimeoutMilliseconds; /// - /// Gets or sets the maximum batch size of every export. It must be smaller or equal to MaxQueueLength. The default value is 512. + /// Gets or sets the maximum batch size of every export. It must be smaller or equal to . The default value is 512. /// public int MaxExportBatchSize { get; set; } = BatchExportProcessor.DefaultMaxExportBatchSize; } diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index 3219bd7166c..a11f9108e92 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -6,6 +6,42 @@ Notes](../../RELEASENOTES.md). ## Unreleased +## 1.13.0 + +Released 2025-Oct-01 + +* Added a verification to ensure that a `MetricReader` can only be registered + to a single `MeterProvider`, as required by the OpenTelemetry specification. + ([#6458](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6458)) + +* Added `FormatMessage` configuration option to self-diagnostics feature. When + set to `true` (default is false), log messages will be formatted by replacing + placeholders with actual parameter values for improved readability. + + Example `OTEL_DIAGNOSTICS.json`: + + ```json + { + "LogDirectory": ".", + "FileSize": 32768, + "LogLevel": "Warning", + "FormatMessage": true + } + ``` + +* Fixed parsing of `OTEL_TRACES_SAMPLER_ARG` decimal values to always use `.` + as the delimiter when using the `traceidratio` sampler, preventing + locale-specific parsing issues. + ([#6444](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6444)) + +## 1.12.0 + +Released 2025-Apr-29 + +## 1.11.2 + +Released 2025-Mar-04 + ## 1.11.1 Released 2025-Jan-22 diff --git a/src/OpenTelemetry/CompositeProcessor.cs b/src/OpenTelemetry/CompositeProcessor.cs index d59936ee87e..df484950e55 100644 --- a/src/OpenTelemetry/CompositeProcessor.cs +++ b/src/OpenTelemetry/CompositeProcessor.cs @@ -24,10 +24,12 @@ public CompositeProcessor(IEnumerable> processors) { Guard.ThrowIfNull(processors); +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 using var iter = processors.GetEnumerator(); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 if (!iter.MoveNext()) { - throw new ArgumentException($"'{iter}' is null or empty", nameof(iter)); + throw new ArgumentException($"'{iter}' is null or empty", nameof(processors)); } this.Head = new DoublyLinkedListNode(iter.Current); diff --git a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs index 88312e3a140..164b1450e5f 100644 --- a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs +++ b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs @@ -16,7 +16,7 @@ namespace OpenTelemetry.Internal; [EventSource(Name = "OpenTelemetry-Sdk")] internal sealed class OpenTelemetrySdkEventSource : EventSource, IConfigurationExtensionsLogger { - public static OpenTelemetrySdkEventSource Log = new(); + public static readonly OpenTelemetrySdkEventSource Log = new(); #if DEBUG public static OpenTelemetryEventListener Listener = new(); #endif @@ -416,7 +416,7 @@ protected override void OnEventWritten(EventWrittenEventArgs e) string? message; if (e.Message != null && e.Payload != null && e.Payload.Count > 0) { - message = string.Format(e.Message, e.Payload.ToArray()); + message = string.Format(System.Globalization.CultureInfo.CurrentCulture, e.Message, e.Payload.ToArray()); } else { diff --git a/src/OpenTelemetry/Internal/SelfDiagnosticsConfigParser.cs b/src/OpenTelemetry/Internal/SelfDiagnosticsConfigParser.cs index 6832ead4946..13ba5fc4df7 100644 --- a/src/OpenTelemetry/Internal/SelfDiagnosticsConfigParser.cs +++ b/src/OpenTelemetry/Internal/SelfDiagnosticsConfigParser.cs @@ -28,6 +28,9 @@ internal sealed class SelfDiagnosticsConfigParser private static readonly Regex LogLevelRegex = new( @"""LogLevel""\s*:\s*""(?.*?)""", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex FormatMessageRegex = new( + @"""FormatMessage""\s*:\s*(?:""(?.*?)""|(?true|false))", RegexOptions.IgnoreCase | RegexOptions.Compiled); + // This class is called in SelfDiagnosticsConfigRefresher.UpdateMemoryMappedFileFromConfiguration // in both main thread and the worker thread. // In theory the variable won't be access at the same time because worker thread first Task.Delay for a few seconds. @@ -36,11 +39,13 @@ internal sealed class SelfDiagnosticsConfigParser public bool TryGetConfiguration( [NotNullWhen(true)] out string? logDirectory, out int fileSizeInKB, - out EventLevel logLevel) + out EventLevel logLevel, + out bool formatMessage) { logDirectory = null; fileSizeInKB = 0; logLevel = EventLevel.LogAlways; + formatMessage = false; try { var configFilePath = ConfigFileName; @@ -107,6 +112,9 @@ public bool TryGetConfiguration( return false; } + // FormatMessage is optional, defaults to false + _ = TryParseFormatMessage(configJson, out formatMessage); + return Enum.TryParse(logLevelString, out logLevel); } catch (Exception) @@ -141,4 +149,17 @@ internal static bool TryParseLogLevel( logLevel = logLevelResult.Groups["LogLevel"].Value; return logLevelResult.Success && !string.IsNullOrWhiteSpace(logLevel); } + + internal static bool TryParseFormatMessage(string configJson, out bool formatMessage) + { + formatMessage = false; + var formatMessageResult = FormatMessageRegex.Match(configJson); + if (formatMessageResult.Success) + { + var formatMessageValue = formatMessageResult.Groups["FormatMessage"].Value; + return bool.TryParse(formatMessageValue, out formatMessage); + } + + return true; + } } diff --git a/src/OpenTelemetry/Internal/SelfDiagnosticsConfigRefresher.cs b/src/OpenTelemetry/Internal/SelfDiagnosticsConfigRefresher.cs index 3d14dc16e0b..5f515e70ca1 100644 --- a/src/OpenTelemetry/Internal/SelfDiagnosticsConfigRefresher.cs +++ b/src/OpenTelemetry/Internal/SelfDiagnosticsConfigRefresher.cs @@ -35,12 +35,15 @@ internal class SelfDiagnosticsConfigRefresher : IDisposable // Once the configuration file is valid, an eventListener object will be created. private SelfDiagnosticsEventListener? eventListener; +#pragma warning disable CA2213 // Disposable fields should be disposed private volatile FileStream? underlyingFileStreamForMemoryMappedFile; private volatile MemoryMappedFile? memoryMappedFile; +#pragma warning restore CA2213 // Disposable fields should be disposed private string? logDirectory; // Log directory for log files private int logFileSize; // Log file size in bytes private long logFilePosition; // The logger will write into the byte at this position private EventLevel logEventLevel = (EventLevel)(-1); + private bool formatMessage; public SelfDiagnosticsConfigRefresher() { @@ -136,25 +139,27 @@ private async Task Worker(CancellationToken cancellationToken) private void UpdateMemoryMappedFileFromConfiguration() { - if (this.configParser.TryGetConfiguration(out string? newLogDirectory, out int fileSizeInKB, out EventLevel newEventLevel)) + if (this.configParser.TryGetConfiguration(out string? newLogDirectory, out int fileSizeInKB, out EventLevel newEventLevel, out bool formatMessage)) { int newFileSize = fileSizeInKB * 1024; - if (!newLogDirectory.Equals(this.logDirectory) || this.logFileSize != newFileSize) + if (!newLogDirectory.Equals(this.logDirectory, StringComparison.Ordinal) || this.logFileSize != newFileSize) { this.CloseLogFile(); this.OpenLogFile(newLogDirectory, newFileSize); } - if (!newEventLevel.Equals(this.logEventLevel)) + if (!newEventLevel.Equals(this.logEventLevel) || this.formatMessage != formatMessage) { if (this.eventListener != null) { this.eventListener.Dispose(); } - this.eventListener = new SelfDiagnosticsEventListener(newEventLevel, this); + this.eventListener = new SelfDiagnosticsEventListener(newEventLevel, this, formatMessage); this.logEventLevel = newEventLevel; } + + this.formatMessage = formatMessage; } else { @@ -194,7 +199,11 @@ private void OpenLogFile(string newLogDirectory, int newFileSize) { Directory.CreateDirectory(newLogDirectory); var fileName = Path.GetFileName(Process.GetCurrentProcess().MainModule?.FileName ?? "OpenTelemetrySdk") + "." +#if NET + + Environment.ProcessId + ".log"; +#else + Process.GetCurrentProcess().Id + ".log"; +#endif var filePath = Path.Combine(newLogDirectory, fileName); // Because the API [MemoryMappedFile.CreateFromFile][1](the string version) behaves differently on @@ -255,10 +264,8 @@ private void Dispose(bool disposing) } // Dispose EventListener before files, because EventListener writes to files. - if (this.eventListener != null) - { - this.eventListener.Dispose(); - } + this.eventListener?.Dispose(); + this.eventListener = null; // Ensure worker thread properly finishes. // Or it might have created another MemoryMappedFile in that thread diff --git a/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs b/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs index 2ace3eb5526..cd5bed688bf 100644 --- a/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs +++ b/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs @@ -19,17 +19,19 @@ internal sealed class SelfDiagnosticsEventListener : EventListener private readonly Lock lockObj = new(); private readonly EventLevel logLevel; private readonly SelfDiagnosticsConfigRefresher configRefresher; + private readonly bool formatMessage; private readonly ThreadLocal writeBuffer = new(() => null); - private readonly List? eventSourcesBeforeConstructor = new(); + private readonly List? eventSourcesBeforeConstructor = []; - private bool disposedValue = false; + private bool disposedValue; - public SelfDiagnosticsEventListener(EventLevel logLevel, SelfDiagnosticsConfigRefresher configRefresher) + public SelfDiagnosticsEventListener(EventLevel logLevel, SelfDiagnosticsConfigRefresher configRefresher, bool formatMessage = false) { Guard.ThrowIfNull(configRefresher); this.logLevel = logLevel; this.configRefresher = configRefresher; + this.formatMessage = formatMessage; List eventSources; lock (this.lockObj) @@ -111,61 +113,6 @@ internal static int EncodeInBuffer(string? str, bool isParameter, byte[] buffer, return position; } - internal void WriteEvent(string? eventMessage, ReadOnlyCollection? payload) - { - try - { - var buffer = this.writeBuffer.Value; - if (buffer == null) - { - buffer = new byte[BUFFERSIZE]; - this.writeBuffer.Value = buffer; - } - - var pos = this.DateTimeGetBytes(DateTime.UtcNow, buffer, 0); - buffer[pos++] = (byte)':'; - pos = EncodeInBuffer(eventMessage, false, buffer, pos); - if (payload != null) - { - // Not using foreach because it can cause allocations - for (int i = 0; i < payload.Count; ++i) - { - object? obj = payload[i]; - if (obj != null) - { - pos = EncodeInBuffer(obj.ToString() ?? "null", true, buffer, pos); - } - else - { - pos = EncodeInBuffer("null", true, buffer, pos); - } - } - } - - buffer[pos++] = (byte)'\n'; - int byteCount = pos - 0; - if (this.configRefresher.TryGetLogStream(byteCount, out Stream? stream, out int availableByteCount)) - { - if (availableByteCount >= byteCount) - { - stream.Write(buffer, 0, byteCount); - } - else - { - stream.Write(buffer, 0, availableByteCount); - stream.Seek(0, SeekOrigin.Begin); - stream.Write(buffer, availableByteCount, byteCount - availableByteCount); - } - } - } - catch (Exception) - { - // Fail to allocate memory for buffer, or - // A concurrent condition: memory mapped file is disposed in other thread after TryGetLogStream() finishes. - // In this case, silently fail. - } - } - /// /// Write the datetime formatted string into bytes byte-array starting at byteIndex position. /// @@ -188,7 +135,7 @@ internal void WriteEvent(string? eventMessage, ReadOnlyCollection? payl /// Array of bytes to write. /// Starting index into bytes array. /// The number of bytes written. - internal int DateTimeGetBytes(DateTime datetime, byte[] bytes, int byteIndex) + internal static int DateTimeGetBytes(DateTime datetime, byte[] bytes, int byteIndex) { int num; int pos = byteIndex; @@ -271,6 +218,73 @@ internal int DateTimeGetBytes(DateTime datetime, byte[] bytes, int byteIndex) return pos - byteIndex; } + internal void WriteEvent(string? eventMessage, ReadOnlyCollection? payload) + { + try + { + var buffer = this.writeBuffer.Value; + if (buffer == null) + { + buffer = new byte[BUFFERSIZE]; + this.writeBuffer.Value = buffer; + } + + var pos = DateTimeGetBytes(DateTime.UtcNow, buffer, 0); + buffer[pos++] = (byte)':'; + + if (this.formatMessage && eventMessage != null && payload != null && payload.Count > 0) + { + // Use string.Format to format the message with parameters + string messageToWrite = string.Format(System.Globalization.CultureInfo.InvariantCulture, eventMessage, payload.ToArray()); + pos = EncodeInBuffer(messageToWrite, false, buffer, pos); + } + else + { + pos = EncodeInBuffer(eventMessage, false, buffer, pos); + if (payload != null) + { + // Not using foreach because it can cause allocations + for (int i = 0; i < payload.Count; ++i) + { + object? obj = payload[i]; + if (obj != null) + { + pos = EncodeInBuffer(obj.ToString() ?? "null", true, buffer, pos); + } + else + { + pos = EncodeInBuffer("null", true, buffer, pos); + } + } + } + } + + buffer[pos++] = (byte)'\n'; + int byteCount = pos - 0; +#pragma warning disable CA2000 // Dispose objects before losing scope + if (this.configRefresher.TryGetLogStream(byteCount, out Stream? stream, out int availableByteCount)) +#pragma warning restore CA2000 // Dispose objects before losing scope + { + if (availableByteCount >= byteCount) + { + stream.Write(buffer, 0, byteCount); + } + else + { + stream.Write(buffer, 0, availableByteCount); + stream.Seek(0, SeekOrigin.Begin); + stream.Write(buffer, availableByteCount, byteCount - availableByteCount); + } + } + } + catch (Exception) + { + // Fail to allocate memory for buffer, or + // A concurrent condition: memory mapped file is disposed in other thread after TryGetLogStream() finishes. + // In this case, silently fail. + } + } + protected override void OnEventSourceCreated(EventSource eventSource) { if (eventSource.Name.StartsWith(EventSourceNamePrefix, StringComparison.Ordinal)) @@ -283,7 +297,9 @@ protected override void OnEventSourceCreated(EventSource eventSource) { lock (this.lockObj) { +#pragma warning disable CA1508 // Avoid dead conditional code - see previous comment if (this.eventSourcesBeforeConstructor != null) +#pragma warning restore CA1508 // Avoid dead conditional code - see previous comment { this.eventSourcesBeforeConstructor.Add(eventSource); return; diff --git a/src/OpenTelemetry/Internal/WildcardHelper.cs b/src/OpenTelemetry/Internal/WildcardHelper.cs index 98ee62934ae..e792ade75ab 100644 --- a/src/OpenTelemetry/Internal/WildcardHelper.cs +++ b/src/OpenTelemetry/Internal/WildcardHelper.cs @@ -18,7 +18,11 @@ public static bool ContainsWildcard( return false; } +#if NET || NETSTANDARD2_1_OR_GREATER + return value.Contains('*', StringComparison.Ordinal) || value.Contains('?', StringComparison.Ordinal); +#else return value.Contains('*') || value.Contains('?'); +#endif } public static Regex GetWildcardRegex(IEnumerable patterns) @@ -27,7 +31,11 @@ public static Regex GetWildcardRegex(IEnumerable patterns) var convertedPattern = string.Join( "|", +#if NET || NETSTANDARD2_1_OR_GREATER + from p in patterns select "(?:" + Regex.Escape(p).Replace("\\*", ".*", StringComparison.Ordinal).Replace("\\?", ".", StringComparison.Ordinal) + ')'); +#else from p in patterns select "(?:" + Regex.Escape(p).Replace("\\*", ".*").Replace("\\?", ".") + ')'); +#endif return new Regex("^(?:" + convertedPattern + ")$", RegexOptions.Compiled | RegexOptions.IgnoreCase); } diff --git a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerProvider.cs b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerProvider.cs index 0dc251da0b6..c0888f1db77 100644 --- a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerProvider.cs +++ b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerProvider.cs @@ -18,7 +18,7 @@ public class OpenTelemetryLoggerProvider : BaseProvider, ILoggerProvider, ISuppo { internal readonly LoggerProvider Provider; private readonly bool ownsProvider; - private readonly Hashtable loggers = new(); + private readonly Hashtable loggers = []; private bool disposed; static OpenTelemetryLoggerProvider() @@ -37,7 +37,9 @@ public OpenTelemetryLoggerProvider(IOptionsMonitor o { Guard.ThrowIfNull(options); +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 var optionsInstance = options.CurrentValue; +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 this.Provider = Sdk .CreateLoggerProviderBuilder() @@ -77,7 +79,9 @@ internal OpenTelemetryLoggerProvider( internal IExternalScopeProvider? ScopeProvider { get; private set; } /// +#pragma warning disable CA1033 // Interface methods should be callable by child types void ISupportExternalScope.SetScopeProvider(IExternalScopeProvider scopeProvider) +#pragma warning restore CA1033 // Interface methods should be callable by child types { this.ScopeProvider = scopeProvider; diff --git a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs index e1a68730f2b..e61434ea7ec 100644 --- a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs +++ b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs @@ -2,12 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; -#if NET +#if NET || EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; #endif using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Configuration; using Microsoft.Extensions.Options; using OpenTelemetry.Internal; @@ -42,7 +41,9 @@ public static class OpenTelemetryLoggingExtensions [Obsolete("Call UseOpenTelemetry instead this method will be removed in a future version.")] */ public static ILoggingBuilder AddOpenTelemetry( this ILoggingBuilder builder) +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 => AddOpenTelemetryInternal(builder, configureBuilder: null, configureOptions: null); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 /// /// Adds an OpenTelemetry logger named 'OpenTelemetry' to the . @@ -58,7 +59,9 @@ public static ILoggingBuilder AddOpenTelemetry( public static ILoggingBuilder AddOpenTelemetry( this ILoggingBuilder builder, Action? configure) +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 => AddOpenTelemetryInternal(builder, configureBuilder: null, configureOptions: configure); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 #if EXPOSE_EXPERIMENTAL_FEATURES /// @@ -71,16 +74,16 @@ public static ILoggingBuilder AddOpenTelemetry( /// /// The to use. /// The supplied for call chaining. -#if NET [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else internal #endif static ILoggingBuilder UseOpenTelemetry( this ILoggingBuilder builder) +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 => AddOpenTelemetryInternal(builder, configureBuilder: null, configureOptions: null); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 #if EXPOSE_EXPERIMENTAL_FEATURES /// @@ -90,9 +93,7 @@ static ILoggingBuilder UseOpenTelemetry( /// The to use. /// configuration action. /// The supplied for call chaining. -#if NET [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else internal @@ -103,7 +104,9 @@ static ILoggingBuilder UseOpenTelemetry( { Guard.ThrowIfNull(configure); +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 return AddOpenTelemetryInternal(builder, configureBuilder: configure, configureOptions: null); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 } #if EXPOSE_EXPERIMENTAL_FEATURES @@ -115,9 +118,7 @@ static ILoggingBuilder UseOpenTelemetry( /// Optional configuration action. /// Optional configuration action. /// The supplied for call chaining. -#if NET [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else internal @@ -126,7 +127,9 @@ static ILoggingBuilder UseOpenTelemetry( this ILoggingBuilder builder, Action? configureBuilder, Action? configureOptions) +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 => AddOpenTelemetryInternal(builder, configureBuilder, configureOptions); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 private static ILoggingBuilder AddOpenTelemetryInternal( ILoggingBuilder builder, diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs index 8009687d551..097cdb2d3be 100644 --- a/src/OpenTelemetry/Logs/LogRecord.cs +++ b/src/OpenTelemetry/Logs/LogRecord.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; -#if EXPOSE_EXPERIMENTAL_FEATURES && NET +#if EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; #endif using System.Runtime.CompilerServices; @@ -355,9 +355,7 @@ public Exception? Exception /// known at the source. /// /// -#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else /// @@ -377,9 +375,7 @@ public Exception? Exception /// Gets or sets the log . /// /// -#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else /// @@ -405,9 +401,7 @@ public Exception? Exception /// typically the which emitted the however the value may be different if is modified. -#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public Logger Logger { get; internal set; } = InstrumentationScopeLogger.Default; #else /// @@ -429,19 +423,19 @@ public void ForEachScope(Action callback, TState { Guard.ThrowIfNull(callback); - var forEachScopeState = new ScopeForEachState(callback, state); - var bufferedScopes = this.ILoggerData.BufferedScopes; if (bufferedScopes != null) { foreach (object? scope in bufferedScopes) { - ScopeForEachState.ForEachScope(scope, forEachScopeState); +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 + callback(new(scope), state); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 } } else { - this.ILoggerData.ScopeProvider?.ForEachScope(ScopeForEachState.ForEachScope, forEachScopeState); + this.ILoggerData.ScopeProvider?.ForEachScope(ScopeForEachState.ForEachScope, new(callback, state)); } } diff --git a/src/OpenTelemetry/Logs/LoggerProviderExtensions.cs b/src/OpenTelemetry/Logs/LoggerProviderExtensions.cs index f5d795fba2b..617d98e6dcd 100644 --- a/src/OpenTelemetry/Logs/LoggerProviderExtensions.cs +++ b/src/OpenTelemetry/Logs/LoggerProviderExtensions.cs @@ -28,7 +28,9 @@ public static LoggerProvider AddProcessor(this LoggerProvider provider, BaseProc if (provider is LoggerProviderSdk loggerProviderSdk) { +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 loggerProviderSdk.AddProcessor(processor); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 } return provider; diff --git a/src/OpenTelemetry/Logs/LoggerProviderSdk.cs b/src/OpenTelemetry/Logs/LoggerProviderSdk.cs index 4287fcad3a2..151f48685cd 100644 --- a/src/OpenTelemetry/Logs/LoggerProviderSdk.cs +++ b/src/OpenTelemetry/Logs/LoggerProviderSdk.cs @@ -2,9 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; -#if NETSTANDARD2_1_OR_GREATER || NET using System.Diagnostics.CodeAnalysis; -#endif using System.Text; using Microsoft.Extensions.DependencyInjection; using OpenTelemetry.Internal; @@ -18,11 +16,11 @@ namespace OpenTelemetry.Logs; internal sealed class LoggerProviderSdk : LoggerProvider { internal readonly IServiceProvider ServiceProvider; - internal readonly IDisposable? OwnedServiceProvider; + internal IDisposable? OwnedServiceProvider; internal bool Disposed; internal int ShutdownCount; - private readonly List instrumentations = new(); + private readonly List instrumentations = []; private ILogRecordPool? threadStaticPool = LogRecordThreadStaticPool.Instance; public LoggerProviderSdk( @@ -90,13 +88,36 @@ public LoggerProviderSdk( public ILogRecordPool LogRecordPool => this.threadStaticPool ?? LogRecordSharedPool.Current; + public static bool ContainsBatchProcessor(BaseProcessor processor) + { + if (processor is BatchExportProcessor) + { + return true; + } + else if (processor is CompositeProcessor compositeProcessor) + { + var current = compositeProcessor.Head; + while (current != null) + { + if (ContainsBatchProcessor(current.Value)) + { + return true; + } + + current = current.Next; + } + } + + return false; + } + public void AddProcessor(BaseProcessor processor) { Guard.ThrowIfNull(processor); processor.SetParentProvider(this); - if (this.threadStaticPool != null && this.ContainsBatchProcessor(processor)) + if (this.threadStaticPool != null && ContainsBatchProcessor(processor)) { OpenTelemetrySdkEventSource.Log.LoggerProviderSdkEvent("Using shared thread pool."); @@ -127,10 +148,10 @@ public void AddProcessor(BaseProcessor processor) processorAdded.Append(processor); processorAdded.Append('\''); - var newCompositeProcessor = new CompositeProcessor(new[] - { + var newCompositeProcessor = new CompositeProcessor( + [ this.Processor, - }); + ]); newCompositeProcessor.SetParentProvider(this); newCompositeProcessor.AddProcessor(processor); this.Processor = newCompositeProcessor; @@ -170,29 +191,6 @@ public bool Shutdown(int timeoutMilliseconds) } } - public bool ContainsBatchProcessor(BaseProcessor processor) - { - if (processor is BatchExportProcessor) - { - return true; - } - else if (processor is CompositeProcessor compositeProcessor) - { - var current = compositeProcessor.Head; - while (current != null) - { - if (this.ContainsBatchProcessor(current.Value)) - { - return true; - } - - current = current.Next; - } - } - - return false; - } - /// #if EXPOSE_EXPERIMENTAL_FEATURES protected @@ -201,9 +199,7 @@ public bool ContainsBatchProcessor(BaseProcessor processor) #endif override bool TryCreateLogger( string? name, -#if NETSTANDARD2_1_OR_GREATER || NET [NotNullWhen(true)] -#endif out Logger? logger) { logger = new LoggerSdk(this, name); @@ -217,21 +213,20 @@ protected override void Dispose(bool disposing) { if (disposing) { - if (this.instrumentations != null) + foreach (var item in this.instrumentations) { - foreach (var item in this.instrumentations) - { - (item as IDisposable)?.Dispose(); - } - - this.instrumentations.Clear(); + (item as IDisposable)?.Dispose(); } + this.instrumentations.Clear(); + // Wait for up to 5 seconds grace period this.Processor?.Shutdown(5000); this.Processor?.Dispose(); + this.Processor = null; this.OwnedServiceProvider?.Dispose(); + this.OwnedServiceProvider = null; } this.Disposed = true; diff --git a/src/OpenTelemetry/Logs/LoggerSdk.cs b/src/OpenTelemetry/Logs/LoggerSdk.cs index a0bc47300d0..19a38cf72bb 100644 --- a/src/OpenTelemetry/Logs/LoggerSdk.cs +++ b/src/OpenTelemetry/Logs/LoggerSdk.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using Microsoft.Extensions.Logging; using OpenTelemetry.Internal; namespace OpenTelemetry.Logs; @@ -35,6 +36,7 @@ public override void EmitLog(in LogRecordData data, in LogRecordAttributeList at logRecord.Data = data; logRecord.ILoggerData = default; + logRecord.ILoggerData.EventId = new EventId(default, data.EventName); logRecord.Logger = this; diff --git a/src/OpenTelemetry/Logs/Processor/LogRecordExportProcessorOptions.cs b/src/OpenTelemetry/Logs/Processor/LogRecordExportProcessorOptions.cs index 95cdc830e35..b20accfdb47 100644 --- a/src/OpenTelemetry/Logs/Processor/LogRecordExportProcessorOptions.cs +++ b/src/OpenTelemetry/Logs/Processor/LogRecordExportProcessorOptions.cs @@ -1,7 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -using System.Diagnostics; using OpenTelemetry.Internal; namespace OpenTelemetry.Logs; @@ -24,9 +23,7 @@ public LogRecordExportProcessorOptions() internal LogRecordExportProcessorOptions( BatchExportLogRecordProcessorOptions defaultBatchExportLogRecordProcessorOptions) { - Debug.Assert(defaultBatchExportLogRecordProcessorOptions != null, "defaultBatchExportLogRecordProcessorOptions was null"); - - this.batchExportProcessorOptions = defaultBatchExportLogRecordProcessorOptions ?? new(); + this.batchExportProcessorOptions = defaultBatchExportLogRecordProcessorOptions; } /// diff --git a/src/OpenTelemetry/Metrics/AggregationTemporality.cs b/src/OpenTelemetry/Metrics/AggregationTemporality.cs index 2a9c036d3dc..e2f47d29535 100644 --- a/src/OpenTelemetry/Metrics/AggregationTemporality.cs +++ b/src/OpenTelemetry/Metrics/AggregationTemporality.cs @@ -7,7 +7,11 @@ namespace OpenTelemetry.Metrics; /// Enumeration used to define the aggregation temporality for a . /// +#pragma warning disable CA1008 // Enums should have zero value +#pragma warning disable CA1028 // Enum storage should be Int32 public enum AggregationTemporality : byte +#pragma warning restore CA1028 // Enum storage should be Int32 +#pragma warning restore CA1008 // Enums should have zero value { /// /// Cumulative. diff --git a/src/OpenTelemetry/Metrics/AggregatorStore.cs b/src/OpenTelemetry/Metrics/AggregatorStore.cs index 59214c3f090..cd6d33190d3 100644 --- a/src/OpenTelemetry/Metrics/AggregatorStore.cs +++ b/src/OpenTelemetry/Metrics/AggregatorStore.cs @@ -23,10 +23,10 @@ internal sealed class AggregatorStore internal readonly int NumberOfMetricPoints; internal readonly ConcurrentDictionary? TagsToMetricPointIndexDictionaryDelta; internal readonly Func? ExemplarReservoirFactory; - internal long DroppedMeasurements = 0; + internal long DroppedMeasurements; private const ExemplarFilterType DefaultExemplarFilter = ExemplarFilterType.AlwaysOff; - private static readonly Comparison> DimensionComparisonDelegate = (x, y) => x.Key.CompareTo(y.Key); + private static readonly Comparison> DimensionComparisonDelegate = (x, y) => string.Compare(x.Key, y.Key, StringComparison.Ordinal); private readonly Lock lockZeroTags = new(); private readonly Lock lockOverflowTag = new(); @@ -50,8 +50,8 @@ internal sealed class AggregatorStore private readonly ExemplarFilterType exemplarFilter; private readonly Func[], int, int> lookupAggregatorStore; - private int metricPointIndex = 0; - private int batchSize = 0; + private int metricPointIndex; + private int batchSize; private bool zeroTagMetricPointInitialized; private bool overflowTagMetricPointInitialized; @@ -243,7 +243,9 @@ internal void SnapshotDeltaWithMetricPointReclaim() // If the Collect thread now wakes up, it would be able to set the ReferenceCount to `int.MinValue`, thereby, marking the MetricPoint // invalid for newer updates. In such cases, the MetricPoint, should not be reclaimed before taking its Snapshot. +#pragma warning disable CA1508 // Avoid dead conditional code - see previous comment if (metricPoint.MetricPointStatus == MetricPointStatus.NoCollectPending) +#pragma warning restore CA1508 // Avoid dead conditional code - see previous comment { this.ReclaimMetricPoint(ref metricPoint, i); } diff --git a/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderBase.cs b/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderBase.cs index 49b519b66c2..097cbc372c2 100644 --- a/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderBase.cs +++ b/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderBase.cs @@ -74,7 +74,9 @@ MeterProviderBuilder IMeterProviderBuilder.ConfigureServices(Action +#pragma warning disable CA1033 // Interface methods should be callable by child types MeterProviderBuilder IDeferredMeterProviderBuilder.Configure(Action configure) +#pragma warning restore CA1033 // Interface methods should be callable by child types { this.innerBuilder.ConfigureBuilder(configure); diff --git a/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs index 1a5c1d950c3..31ad29f7c5a 100644 --- a/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs @@ -103,12 +103,20 @@ public static MeterProviderBuilder AddReader( /// The supplied for chaining. public static MeterProviderBuilder AddView(this MeterProviderBuilder meterProviderBuilder, string instrumentName, string name) { + Guard.ThrowIfNull(instrumentName); + if (!MeterProviderBuilderSdk.IsValidInstrumentName(name)) { throw new ArgumentException($"Custom view name {name} is invalid.", nameof(name)); } - if (instrumentName.IndexOf('*') != -1) +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 +#if NET || NETSTANDARD2_1_OR_GREATER + if (instrumentName.Contains('*', StringComparison.Ordinal)) +#else + if (instrumentName.Contains('*')) +#endif +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 { throw new ArgumentException( $"Instrument selection criteria is invalid. Instrument name '{instrumentName}' " + @@ -136,7 +144,13 @@ public static MeterProviderBuilder AddView(this MeterProviderBuilder meterProvid Guard.ThrowIfNullOrWhitespace(instrumentName); Guard.ThrowIfNull(metricStreamConfiguration); - if (metricStreamConfiguration.Name != null && instrumentName.IndexOf('*') != -1) +#if NET || NETSTANDARD2_1_OR_GREATER +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 + if (metricStreamConfiguration.Name != null && instrumentName.Contains('*', StringComparison.Ordinal)) +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 +#else + if (metricStreamConfiguration.Name != null && instrumentName.Contains('*')) +#endif { throw new ArgumentException( $"Instrument selection criteria is invalid. Instrument name '{instrumentName}' " + @@ -149,9 +163,17 @@ public static MeterProviderBuilder AddView(this MeterProviderBuilder meterProvid { if (builder is MeterProviderBuilderSdk meterProviderBuilderSdk) { - if (instrumentName.IndexOf('*') != -1) +#if NET || NETSTANDARD2_1_OR_GREATER + if (instrumentName.Contains('*', StringComparison.Ordinal)) +#else + if (instrumentName.Contains('*')) +#endif { +#if NET || NETSTANDARD2_1_OR_GREATER + var pattern = '^' + Regex.Escape(instrumentName).Replace("\\*", ".*", StringComparison.Ordinal); +#else var pattern = '^' + Regex.Escape(instrumentName).Replace("\\*", ".*"); +#endif var regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); meterProviderBuilderSdk.AddView(instrument => regex.IsMatch(instrument.Name) ? metricStreamConfiguration : null); } diff --git a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs index a7205a41eab..63196c9a0af 100644 --- a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs +++ b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs @@ -13,7 +13,7 @@ namespace OpenTelemetry.Metrics; internal sealed class CircularBufferBuckets { private long[]? trait; - private int begin = 0; + private int begin; private int end = -1; public CircularBufferBuckets(int capacity) @@ -246,9 +246,7 @@ static void Consolidate(long[] array, uint src, uint dst) [MethodImpl(MethodImplOptions.AggressiveInlining)] static void Exchange(long[] array, uint src, uint dst) { - var value = array[dst]; - array[dst] = array[src]; - array[src] = value; + (array[dst], array[src]) = (array[src], array[dst]); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -263,10 +261,11 @@ internal void Reset() { if (this.trait != null) { - for (var i = 0; i < this.trait.Length; ++i) - { - this.trait[i] = 0; - } +#if NET + Array.Clear(this.trait); +#else + Array.Clear(this.trait, 0, this.trait.Length); +#endif } } diff --git a/src/OpenTelemetry/Metrics/Exemplar/Exemplar.cs b/src/OpenTelemetry/Metrics/Exemplar/Exemplar.cs index d1801afdd39..3fb410d390c 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/Exemplar.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/Exemplar.cs @@ -5,6 +5,7 @@ using System.Collections.Frozen; #endif using System.Diagnostics; +using System.Globalization; namespace OpenTelemetry.Metrics; @@ -23,7 +24,7 @@ public struct Exemplar internal HashSet? ViewDefinedTagKeys; #endif - private static readonly ReadOnlyFilteredTagCollection Empty = new(excludedKeys: null, Array.Empty>(), count: 0); + private static readonly ReadOnlyFilteredTagCollection Empty = new(excludedKeys: null, [], count: 0); private int tagCount; private KeyValuePair[]? tagStorage; private MetricPointValueStorage valueStorage; @@ -115,7 +116,7 @@ internal void Update(in ExemplarMeasurement measurement) else { Debug.Fail("Invalid value type"); - this.DoubleValue = Convert.ToDouble(measurement.Value); + this.DoubleValue = Convert.ToDouble(measurement.Value, CultureInfo.InvariantCulture); } var currentActivity = Activity.Current; diff --git a/src/OpenTelemetry/Metrics/Exemplar/ExemplarMeasurement.cs b/src/OpenTelemetry/Metrics/Exemplar/ExemplarMeasurement.cs index 70d6ff3e762..d1ae9cc038b 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/ExemplarMeasurement.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/ExemplarMeasurement.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if EXPOSE_EXPERIMENTAL_FEATURES && NET +#if EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Internal; #endif @@ -14,9 +14,7 @@ namespace OpenTelemetry.Metrics; /// /// /// Measurement type. -#if NET [Experimental(DiagnosticDefinitions.ExemplarReservoirExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else internal diff --git a/src/OpenTelemetry/Metrics/Exemplar/ExemplarReservoir.cs b/src/OpenTelemetry/Metrics/Exemplar/ExemplarReservoir.cs index 0070074bed6..e118036ae26 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/ExemplarReservoir.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/ExemplarReservoir.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if EXPOSE_EXPERIMENTAL_FEATURES && NET +#if EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Internal; #endif @@ -17,9 +17,7 @@ namespace OpenTelemetry.Metrics; /// Specification: . /// -#if NET [Experimental(DiagnosticDefinitions.ExemplarReservoirExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else internal diff --git a/src/OpenTelemetry/Metrics/Exemplar/FixedSizeExemplarReservoir.cs b/src/OpenTelemetry/Metrics/Exemplar/FixedSizeExemplarReservoir.cs index bca577db0b6..68417fa09b7 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/FixedSizeExemplarReservoir.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/FixedSizeExemplarReservoir.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if EXPOSE_EXPERIMENTAL_FEATURES && NET +#if EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; #endif using OpenTelemetry.Internal; @@ -14,9 +14,7 @@ namespace OpenTelemetry.Metrics; /// number of s. /// /// -#if NET [Experimental(DiagnosticDefinitions.ExemplarReservoirExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else internal diff --git a/src/OpenTelemetry/Metrics/Exemplar/ReadOnlyExemplarCollection.cs b/src/OpenTelemetry/Metrics/Exemplar/ReadOnlyExemplarCollection.cs index 1ebf2a3e365..02c085c428b 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/ReadOnlyExemplarCollection.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/ReadOnlyExemplarCollection.cs @@ -8,9 +8,11 @@ namespace OpenTelemetry.Metrics; /// /// A read-only collection of s. /// +#pragma warning disable CA1711 // Identifiers should not have incorrect suffix public readonly struct ReadOnlyExemplarCollection +#pragma warning restore CA1711 // Identifiers should not have incorrect suffix { - internal static readonly ReadOnlyExemplarCollection Empty = new(Array.Empty()); + internal static readonly ReadOnlyExemplarCollection Empty = new([]); private readonly Exemplar[] exemplars; internal ReadOnlyExemplarCollection(Exemplar[] exemplars) @@ -77,7 +79,9 @@ internal IReadOnlyList ToReadOnlyList() /// /// Enumerates the elements of a . /// +#pragma warning disable CA1034 // Nested types should not be visible - already part of public API public struct Enumerator +#pragma warning restore CA1034 // Nested types should not be visible - already part of public API { private readonly Exemplar[] exemplars; private int index; diff --git a/src/OpenTelemetry/Metrics/ExportModes.cs b/src/OpenTelemetry/Metrics/ExportModes.cs index 2e3def35a4b..1d71eb2844f 100644 --- a/src/OpenTelemetry/Metrics/ExportModes.cs +++ b/src/OpenTelemetry/Metrics/ExportModes.cs @@ -7,7 +7,9 @@ namespace OpenTelemetry.Metrics; /// Describes the mode of a metric exporter. /// [Flags] +#pragma warning disable CA1028 // Enum storage should be Int32 public enum ExportModes : byte +#pragma warning restore CA1028 // Enum storage should be Int32 { /* 0 0 0 0 0 0 0 0 diff --git a/src/OpenTelemetry/Metrics/ExportModesAttribute.cs b/src/OpenTelemetry/Metrics/ExportModesAttribute.cs index 65b88ae3f34..4235b5dac7f 100644 --- a/src/OpenTelemetry/Metrics/ExportModesAttribute.cs +++ b/src/OpenTelemetry/Metrics/ExportModesAttribute.cs @@ -9,19 +9,17 @@ namespace OpenTelemetry.Metrics; [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public sealed class ExportModesAttribute : Attribute { - private readonly ExportModes supportedExportModes; - /// /// Initializes a new instance of the class. /// /// . public ExportModesAttribute(ExportModes supported) { - this.supportedExportModes = supported; + this.Supported = supported; } /// /// Gets the supported . /// - public ExportModes Supported => this.supportedExportModes; + public ExportModes Supported { get; } } diff --git a/src/OpenTelemetry/Metrics/MeterProviderSdk.cs b/src/OpenTelemetry/Metrics/MeterProviderSdk.cs index a07187de4ef..e2b9077eb19 100644 --- a/src/OpenTelemetry/Metrics/MeterProviderSdk.cs +++ b/src/OpenTelemetry/Metrics/MeterProviderSdk.cs @@ -17,20 +17,20 @@ internal sealed class MeterProviderSdk : MeterProvider internal const string ExemplarFilterHistogramsConfigKey = "OTEL_DOTNET_EXPERIMENTAL_METRICS_EXEMPLAR_FILTER_HISTOGRAMS"; internal readonly IServiceProvider ServiceProvider; - internal readonly IDisposable? OwnedServiceProvider; + internal IDisposable? OwnedServiceProvider; internal int ShutdownCount; internal bool Disposed; internal ExemplarFilterType? ExemplarFilter; internal ExemplarFilterType? ExemplarFilterForHistograms; internal Action? OnCollectObservableInstruments; - private readonly List instrumentations = new(); + private readonly List instrumentations = []; private readonly List> viewConfigs; private readonly Lock collectLock = new(); private readonly MeterListener listener; - private readonly MetricReader? reader; - private readonly CompositeMetricReader? compositeMetricReader; private readonly Func shouldListenTo = instrument => false; + private CompositeMetricReader? compositeMetricReader; + private MetricReader? reader; internal MeterProviderSdk( IServiceProvider serviceProvider, @@ -95,7 +95,7 @@ internal MeterProviderSdk( } else { - this.reader = new CompositeMetricReader(new[] { this.reader, reader }); + this.reader = new CompositeMetricReader([this.reader, reader]); } if (reader is PeriodicExportingMetricReader periodicExportingMetricReader) @@ -122,7 +122,7 @@ internal MeterProviderSdk( this.compositeMetricReader = this.reader as CompositeMetricReader; - if (state.Instrumentation.Any()) + if (state.Instrumentation.Count > 0) { foreach (var instrumentation in state.Instrumentation) { @@ -143,12 +143,12 @@ internal MeterProviderSdk( } // Setup Listener - if (state.MeterSources.Any(s => WildcardHelper.ContainsWildcard(s))) + if (state.MeterSources.Exists(WildcardHelper.ContainsWildcard)) { var regex = WildcardHelper.GetWildcardRegex(state.MeterSources); this.shouldListenTo = instrument => regex.IsMatch(instrument.Meter.Name); } - else if (state.MeterSources.Any()) + else if (state.MeterSources.Count > 0) { var meterSourcesToSubscribe = new HashSet(state.MeterSources, StringComparer.OrdinalIgnoreCase); this.shouldListenTo = instrument => meterSourcesToSubscribe.Contains(instrument.Meter.Name); @@ -451,24 +451,25 @@ protected override void Dispose(bool disposing) { if (disposing) { - if (this.instrumentations != null) + foreach (var item in this.instrumentations) { - foreach (var item in this.instrumentations) - { - (item as IDisposable)?.Dispose(); - } - - this.instrumentations.Clear(); + (item as IDisposable)?.Dispose(); } + this.instrumentations.Clear(); + // Wait for up to 5 seconds grace period this.reader?.Shutdown(5000); this.reader?.Dispose(); + this.reader = null; + this.compositeMetricReader?.Dispose(); + this.compositeMetricReader = null; this.listener?.Dispose(); this.OwnedServiceProvider?.Dispose(); + this.OwnedServiceProvider = null; } this.Disposed = true; diff --git a/src/OpenTelemetry/Metrics/MetricPoint/ExponentialHistogramBuckets.cs b/src/OpenTelemetry/Metrics/MetricPoint/ExponentialHistogramBuckets.cs index a48b8e40c60..8fc9dda8cc8 100644 --- a/src/OpenTelemetry/Metrics/MetricPoint/ExponentialHistogramBuckets.cs +++ b/src/OpenTelemetry/Metrics/MetricPoint/ExponentialHistogramBuckets.cs @@ -55,7 +55,9 @@ internal ExponentialHistogramBuckets Copy() /// Enumerates the bucket counts of an exponential histogram. /// // Note: Does not implement IEnumerator<> to prevent accidental boxing. +#pragma warning disable CA1034 // Nested types should not be visible - already part of public API public struct Enumerator +#pragma warning restore CA1034 // Nested types should not be visible - already part of public API { private readonly long[] buckets; private readonly int size; diff --git a/src/OpenTelemetry/Metrics/MetricPoint/HistogramBuckets.cs b/src/OpenTelemetry/Metrics/MetricPoint/HistogramBuckets.cs index 6ef36eb6867..78d8700fe60 100644 --- a/src/OpenTelemetry/Metrics/MetricPoint/HistogramBuckets.cs +++ b/src/OpenTelemetry/Metrics/MetricPoint/HistogramBuckets.cs @@ -33,6 +33,7 @@ public class HistogramBuckets internal HistogramBuckets(double[]? explicitBounds) { + explicitBounds = CleanUpInfinitiesFromExplicitBounds(explicitBounds); this.ExplicitBounds = explicitBounds; this.findHistogramBucketIndex = this.FindBucketIndexLinear; if (explicitBounds != null && explicitBounds.Length >= DefaultBoundaryCountForBinarySearch) @@ -158,11 +159,16 @@ internal void Snapshot(bool outputDelta) } } + private static double[]? CleanUpInfinitiesFromExplicitBounds(double[]? explicitBounds) => explicitBounds + ?.Where(b => !double.IsNegativeInfinity(b) && !double.IsPositiveInfinity(b)).ToArray(); + /// /// Enumerates the elements of a . /// // Note: Does not implement IEnumerator<> to prevent accidental boxing. +#pragma warning disable CA1034 // Nested types should not be visible - already part of public API public struct Enumerator +#pragma warning restore CA1034 // Nested types should not be visible - already part of public API { private readonly int numberOfBuckets; private readonly HistogramBuckets histogramMeasurements; diff --git a/src/OpenTelemetry/Metrics/MetricPoint/MetricPointOptionalComponents.cs b/src/OpenTelemetry/Metrics/MetricPoint/MetricPointOptionalComponents.cs index 440a9cc36b6..e22cbe54cc7 100644 --- a/src/OpenTelemetry/Metrics/MetricPoint/MetricPointOptionalComponents.cs +++ b/src/OpenTelemetry/Metrics/MetricPoint/MetricPointOptionalComponents.cs @@ -22,7 +22,7 @@ internal sealed class MetricPointOptionalComponents public ReadOnlyExemplarCollection Exemplars = ReadOnlyExemplarCollection.Empty; - private int isCriticalSectionOccupied = 0; + private int isCriticalSectionOccupied; public MetricPointOptionalComponents Copy() { diff --git a/src/OpenTelemetry/Metrics/MetricPoint/MetricPointsAccessor.cs b/src/OpenTelemetry/Metrics/MetricPoint/MetricPointsAccessor.cs index b79c1ac7096..3a6ab4a1fd6 100644 --- a/src/OpenTelemetry/Metrics/MetricPoint/MetricPointsAccessor.cs +++ b/src/OpenTelemetry/Metrics/MetricPoint/MetricPointsAccessor.cs @@ -13,9 +13,9 @@ public readonly struct MetricPointsAccessor { private readonly MetricPoint[] metricsPoints; private readonly int[] metricPointsToProcess; - private readonly long targetCount; + private readonly int targetCount; - internal MetricPointsAccessor(MetricPoint[] metricsPoints, int[] metricPointsToProcess, long targetCount) + internal MetricPointsAccessor(MetricPoint[] metricsPoints, int[] metricPointsToProcess, int targetCount) { Debug.Assert(metricsPoints != null, "metricPoints was null"); Debug.Assert(metricPointsToProcess != null, "metricPointsToProcess was null"); @@ -35,14 +35,16 @@ public Enumerator GetEnumerator() /// /// Enumerates the elements of a . /// +#pragma warning disable CA1034 // Nested types should not be visible - already part of public API public struct Enumerator +#pragma warning restore CA1034 // Nested types should not be visible - already part of public API { private readonly MetricPoint[] metricsPoints; private readonly int[] metricPointsToProcess; - private readonly long targetCount; - private long index; + private readonly int targetCount; + private int index; - internal Enumerator(MetricPoint[] metricsPoints, int[] metricPointsToProcess, long targetCount) + internal Enumerator(MetricPoint[] metricsPoints, int[] metricPointsToProcess, int targetCount) { this.metricsPoints = metricsPoints; this.metricPointsToProcess = metricPointsToProcess; diff --git a/src/OpenTelemetry/Metrics/MetricStreamIdentity.cs b/src/OpenTelemetry/Metrics/MetricStreamIdentity.cs index 2a5ed559dc2..7ed33324cd0 100644 --- a/src/OpenTelemetry/Metrics/MetricStreamIdentity.cs +++ b/src/OpenTelemetry/Metrics/MetricStreamIdentity.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics.Metrics; +using System.Globalization; namespace OpenTelemetry.Metrics; @@ -27,7 +28,7 @@ public MetricStreamIdentity(Instrument instrument, MetricStreamConfiguration? me this.ExponentialHistogramMaxScale = (metricStreamConfiguration as Base2ExponentialBucketHistogramConfiguration)?.MaxScale ?? 0; this.HistogramRecordMinMax = (metricStreamConfiguration as HistogramConfiguration)?.RecordMinMax ?? true; -#if NET +#if NET || NETSTANDARD2_1_OR_GREATER HashCode hashCode = default; hashCode.Add(this.InstrumentType); hashCode.Add(this.MeterName); @@ -190,7 +191,7 @@ public bool Equals(MetricStreamIdentity other) for (int i = 0; i < adviceExplicitBucketBoundaries.Count; i++) { - explicitBucketBoundaries[i] = Convert.ToDouble(adviceExplicitBucketBoundaries[i]); + explicitBucketBoundaries[i] = Convert.ToDouble(adviceExplicitBucketBoundaries[i], CultureInfo.InvariantCulture); } return explicitBucketBoundaries; diff --git a/src/OpenTelemetry/Metrics/MetricType.cs b/src/OpenTelemetry/Metrics/MetricType.cs index 526bdbb466f..2080ed17f36 100644 --- a/src/OpenTelemetry/Metrics/MetricType.cs +++ b/src/OpenTelemetry/Metrics/MetricType.cs @@ -7,7 +7,11 @@ namespace OpenTelemetry.Metrics; /// Enumeration used to define the type of a . /// [Flags] +#pragma warning disable CA1028 // Enum storage should be Int32 +#pragma warning disable CA2217 // Do not mark enums with FlagsAttribute public enum MetricType : byte +#pragma warning restore CA2217 // Do not mark enums with FlagsAttribute +#pragma warning restore CA1028 // Enum storage should be Int32 { /* Type: diff --git a/src/OpenTelemetry/Metrics/Reader/BaseExportingMetricReader.cs b/src/OpenTelemetry/Metrics/Reader/BaseExportingMetricReader.cs index 80f96cc143a..0d9d9c4572b 100644 --- a/src/OpenTelemetry/Metrics/Reader/BaseExportingMetricReader.cs +++ b/src/OpenTelemetry/Metrics/Reader/BaseExportingMetricReader.cs @@ -10,14 +10,17 @@ namespace OpenTelemetry.Metrics; /// MetricReader implementation which exports metrics to the configured /// MetricExporter upon . /// +#pragma warning disable CA1708 // Identifiers should differ by more than case public class BaseExportingMetricReader : MetricReader +#pragma warning restore CA1708 // Identifiers should differ by more than case { /// /// Gets the exporter used by the metric reader. /// +#pragma warning disable CA1051 // Do not declare visible instance fields protected readonly BaseExporter exporter; +#pragma warning restore CA1051 // Do not declare visible instance fields - private readonly ExportModes supportedExportModes = ExportModes.Push | ExportModes.Pull; private readonly string exportCalledMessage; private readonly string exportSucceededMessage; private readonly string exportFailedMessage; @@ -33,17 +36,19 @@ public BaseExportingMetricReader(BaseExporter exporter) this.exporter = exporter; +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 var exporterType = exporter.GetType(); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 var attributes = exporterType.GetCustomAttributes(typeof(ExportModesAttribute), true); if (attributes.Length > 0) { var attr = (ExportModesAttribute)attributes[attributes.Length - 1]; - this.supportedExportModes = attr.Supported; + this.SupportedExportModes = attr.Supported; } if (exporter is IPullMetricExporter pullExporter) { - if (this.supportedExportModes.HasFlag(ExportModes.Push)) + if (this.SupportedExportModes.HasFlag(ExportModes.Push)) { pullExporter.Collect = this.Collect; } @@ -69,7 +74,7 @@ public BaseExportingMetricReader(BaseExporter exporter) /// /// Gets the supported . /// - protected ExportModes SupportedExportModes => this.supportedExportModes; + protected ExportModes SupportedExportModes { get; } = ExportModes.Push | ExportModes.Pull; internal override void SetParentProvider(BaseProvider parentProvider) { @@ -106,12 +111,12 @@ internal override bool ProcessMetrics(in Batch metrics, int timeoutMilli /// protected override bool OnCollect(int timeoutMilliseconds) { - if (this.supportedExportModes.HasFlag(ExportModes.Push)) + if (this.SupportedExportModes.HasFlag(ExportModes.Push)) { return base.OnCollect(timeoutMilliseconds); } - if (this.supportedExportModes.HasFlag(ExportModes.Pull) && PullMetricScope.IsPullAllowed) + if (this.SupportedExportModes.HasFlag(ExportModes.Pull) && PullMetricScope.IsPullAllowed) { return base.OnCollect(timeoutMilliseconds); } diff --git a/src/OpenTelemetry/Metrics/Reader/CompositeMetricReader.cs b/src/OpenTelemetry/Metrics/Reader/CompositeMetricReader.cs index a213db5285f..1323b6980b1 100644 --- a/src/OpenTelemetry/Metrics/Reader/CompositeMetricReader.cs +++ b/src/OpenTelemetry/Metrics/Reader/CompositeMetricReader.cs @@ -24,7 +24,7 @@ public CompositeMetricReader(IEnumerable readers) using var iter = readers.GetEnumerator(); if (!iter.MoveNext()) { - throw new ArgumentException($"'{iter}' is null or empty", nameof(iter)); + throw new ArgumentException($"'{iter}' is null or empty", nameof(readers)); } this.Head = new DoublyLinkedListNode(iter.Current); diff --git a/src/OpenTelemetry/Metrics/Reader/MetricReader.cs b/src/OpenTelemetry/Metrics/Reader/MetricReader.cs index 52353a1155a..04d267293d3 100644 --- a/src/OpenTelemetry/Metrics/Reader/MetricReader.cs +++ b/src/OpenTelemetry/Metrics/Reader/MetricReader.cs @@ -125,7 +125,7 @@ public bool Collect(int timeoutMilliseconds = Timeout.Infinite) if (!shouldRunCollect) { - return Task.WaitAny(tcs.Task, this.shutdownTcs.Task, Task.Delay(timeoutMilliseconds)) == 0 && tcs.Task.Result; + return Task.WaitAny([tcs.Task, this.shutdownTcs.Task], timeoutMilliseconds) == 0 && tcs.Task.Result; } var result = false; @@ -218,6 +218,11 @@ public void Dispose() internal virtual void SetParentProvider(BaseProvider parentProvider) { + if (this.parentProvider != null && this.parentProvider != parentProvider) + { + throw new NotSupportedException("A MetricReader must not be registered with multiple MeterProviders."); + } + this.parentProvider = parentProvider; } diff --git a/src/OpenTelemetry/Metrics/Reader/MetricReaderExt.cs b/src/OpenTelemetry/Metrics/Reader/MetricReaderExt.cs index 6074dd072f6..e366e63720a 100644 --- a/src/OpenTelemetry/Metrics/Reader/MetricReaderExt.cs +++ b/src/OpenTelemetry/Metrics/Reader/MetricReaderExt.cs @@ -19,8 +19,8 @@ public abstract partial class MetricReader private readonly Lock instrumentCreationLock = new(); private int metricLimit; private int cardinalityLimit; - private Metric?[]? metrics; - private Metric[]? metricsCurrentBatch; + private Metric?[] metrics = []; + private Metric[] metricsCurrentBatch = []; private int metricIndex = -1; private ExemplarFilterType? exemplarFilter; private ExemplarFilterType? exemplarFilterForHistograms; diff --git a/src/OpenTelemetry/Metrics/Reader/MetricReaderOptions.cs b/src/OpenTelemetry/Metrics/Reader/MetricReaderOptions.cs index 59b850e88aa..7be100a64c9 100644 --- a/src/OpenTelemetry/Metrics/Reader/MetricReaderOptions.cs +++ b/src/OpenTelemetry/Metrics/Reader/MetricReaderOptions.cs @@ -26,7 +26,9 @@ internal MetricReaderOptions( { Debug.Assert(defaultPeriodicExportingMetricReaderOptions != null, "defaultPeriodicExportingMetricReaderOptions was null"); +#pragma warning disable CA1508 // Avoid dead conditional code this.periodicExportingMetricReaderOptions = defaultPeriodicExportingMetricReaderOptions ?? new(); +#pragma warning restore CA1508 // Avoid dead conditional code } /// diff --git a/src/OpenTelemetry/Metrics/Reader/MetricReaderTemporalityPreference.cs b/src/OpenTelemetry/Metrics/Reader/MetricReaderTemporalityPreference.cs index 019fdd69027..c3471ffb661 100644 --- a/src/OpenTelemetry/Metrics/Reader/MetricReaderTemporalityPreference.cs +++ b/src/OpenTelemetry/Metrics/Reader/MetricReaderTemporalityPreference.cs @@ -7,7 +7,9 @@ namespace OpenTelemetry.Metrics; /// Defines the behavior of a /// with respect to . /// +#pragma warning disable CA1008 // Enums should have zero value public enum MetricReaderTemporalityPreference +#pragma warning restore CA1008 // Enums should have zero value { /// /// All aggregations are performed using cumulative temporality. diff --git a/src/OpenTelemetry/Metrics/Reader/PeriodicExportingMetricReader.cs b/src/OpenTelemetry/Metrics/Reader/PeriodicExportingMetricReader.cs index 8f994bbc83d..0596e18af18 100644 --- a/src/OpenTelemetry/Metrics/Reader/PeriodicExportingMetricReader.cs +++ b/src/OpenTelemetry/Metrics/Reader/PeriodicExportingMetricReader.cs @@ -47,10 +47,12 @@ public PeriodicExportingMetricReader( this.ExportIntervalMilliseconds = exportIntervalMilliseconds; this.ExportTimeoutMilliseconds = exportTimeoutMilliseconds; - this.exporterThread = new Thread(new ThreadStart(this.ExporterProc)) + this.exporterThread = new Thread(this.ExporterProc) { IsBackground = true, +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 Name = $"OpenTelemetry-{nameof(PeriodicExportingMetricReader)}-{exporter.GetType().Name}", +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 }; this.exporterThread.Start(); } diff --git a/src/OpenTelemetry/Metrics/StringArrayEqualityComparer.cs b/src/OpenTelemetry/Metrics/StringArrayEqualityComparer.cs index 0418c236e19..2a2e394e784 100644 --- a/src/OpenTelemetry/Metrics/StringArrayEqualityComparer.cs +++ b/src/OpenTelemetry/Metrics/StringArrayEqualityComparer.cs @@ -41,7 +41,7 @@ public int GetHashCode(string[] strings) { Debug.Assert(strings != null, "strings was null"); -#if NET +#if NET || NETSTANDARD2_1_OR_GREATER HashCode hashCode = default; for (int i = 0; i < strings.Length; i++) diff --git a/src/OpenTelemetry/Metrics/Tags.cs b/src/OpenTelemetry/Metrics/Tags.cs index ad166ebccad..7460231b7b0 100644 --- a/src/OpenTelemetry/Metrics/Tags.cs +++ b/src/OpenTelemetry/Metrics/Tags.cs @@ -11,7 +11,7 @@ namespace OpenTelemetry.Metrics; internal readonly struct Tags : IEquatable { - public static readonly Tags EmptyTags = new(Array.Empty>()); + public static readonly Tags EmptyTags = new([]); private readonly int hashCode; @@ -53,8 +53,7 @@ public readonly bool Equals(Tags other) ref var theirs = ref MemoryMarshal.GetArrayDataReference(theirKvps); while (true) { - // Note: string.Equals performs an ordinal comparison - if (!ours.Key.Equals(theirs.Key)) + if (!ours.Key.Equals(theirs.Key, StringComparison.Ordinal)) { return false; } @@ -81,8 +80,7 @@ public readonly bool Equals(Tags other) // Note: Bounds check happens here for theirKvps element access ref var theirs = ref theirKvps[i]; - // Note: string.Equals performs an ordinal comparison - if (!ours.Key.Equals(theirs.Key)) + if (!ours.Key.Equals(theirs.Key, StringComparison.Ordinal)) { return false; } @@ -104,13 +102,13 @@ private static int ComputeHashCode(KeyValuePair[] keyValuePairs { Debug.Assert(keyValuePairs != null, "keyValuePairs was null"); -#if NET +#if NET || NETSTANDARD2_1_OR_GREATER HashCode hashCode = default; for (int i = 0; i < keyValuePairs.Length; i++) { ref var item = ref keyValuePairs[i]; - hashCode.Add(item.Key.GetHashCode()); + hashCode.Add(item.Key.GetHashCode(StringComparison.Ordinal)); hashCode.Add(item.Value); } diff --git a/src/OpenTelemetry/Metrics/View/ExplicitBucketHistogramConfiguration.cs b/src/OpenTelemetry/Metrics/View/ExplicitBucketHistogramConfiguration.cs index 8d040da86a5..3149b0d8d18 100644 --- a/src/OpenTelemetry/Metrics/View/ExplicitBucketHistogramConfiguration.cs +++ b/src/OpenTelemetry/Metrics/View/ExplicitBucketHistogramConfiguration.cs @@ -23,19 +23,11 @@ public class ExplicitBucketHistogramConfiguration : HistogramConfiguration /// /// Note: A copy is made of the provided array. /// +#pragma warning disable CA1819 // Properties should not return arrays public double[]? Boundaries +#pragma warning restore CA1819 // Properties should not return arrays { - get - { - if (this.CopiedBoundaries != null) - { - double[] copy = new double[this.CopiedBoundaries.Length]; - this.CopiedBoundaries.AsSpan().CopyTo(copy); - return copy; - } - - return null; - } + get => this.CopiedBoundaries?.ToArray(); set { @@ -46,9 +38,7 @@ public double[]? Boundaries throw new ArgumentException($"Histogram boundaries are invalid. Histogram boundaries must be in ascending order with distinct values.", nameof(value)); } - double[] copy = new double[value.Length]; - value.AsSpan().CopyTo(copy); - this.CopiedBoundaries = copy; + this.CopiedBoundaries = value.ToArray(); } else { diff --git a/src/OpenTelemetry/Metrics/View/MetricStreamConfiguration.cs b/src/OpenTelemetry/Metrics/View/MetricStreamConfiguration.cs index cf5e06661a0..ba1e55941b1 100644 --- a/src/OpenTelemetry/Metrics/View/MetricStreamConfiguration.cs +++ b/src/OpenTelemetry/Metrics/View/MetricStreamConfiguration.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if EXPOSE_EXPERIMENTAL_FEATURES && NET +#if EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; #endif using OpenTelemetry.Internal; @@ -15,7 +15,7 @@ public class MetricStreamConfiguration { private string? name; - private int? cardinalityLimit = null; + private int? cardinalityLimit; /// /// Gets the drop configuration. @@ -69,33 +69,12 @@ public string? Name /// A copy is made of the provided array. /// /// +#pragma warning disable CA1819 // Properties should not return arrays public string[]? TagKeys +#pragma warning restore CA1819 // Properties should not return arrays { - get - { - if (this.CopiedTagKeys != null) - { - string[] copy = new string[this.CopiedTagKeys.Length]; - this.CopiedTagKeys.AsSpan().CopyTo(copy); - return copy; - } - - return null; - } - - set - { - if (value != null) - { - string[] copy = new string[value.Length]; - value.AsSpan().CopyTo(copy); - this.CopiedTagKeys = copy; - } - else - { - this.CopiedTagKeys = null; - } - } + get => this.CopiedTagKeys?.ToArray(); + set => this.CopiedTagKeys = value?.ToArray(); } /// @@ -141,9 +120,7 @@ public int? CardinalityLimit /// Specification: . /// -#if NET [Experimental(DiagnosticDefinitions.ExemplarReservoirExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public Func? ExemplarReservoirFactory { get; set; } #else internal Func? ExemplarReservoirFactory { get; set; } diff --git a/src/OpenTelemetry/OpenTelemetry.csproj b/src/OpenTelemetry/OpenTelemetry.csproj index 71e3b242cd0..0e8202d99e1 100644 --- a/src/OpenTelemetry/OpenTelemetry.csproj +++ b/src/OpenTelemetry/OpenTelemetry.csproj @@ -4,6 +4,7 @@ $(TargetFrameworksForLibrariesExtended) OpenTelemetry .NET SDK core- + $(NoWarn);CA1815 @@ -26,4 +27,19 @@ + + + + + + + + + + + + + + + diff --git a/src/OpenTelemetry/OpenTelemetryBuilderSdkExtensions.cs b/src/OpenTelemetry/OpenTelemetryBuilderSdkExtensions.cs index c499ef02631..acb2822b0f7 100644 --- a/src/OpenTelemetry/OpenTelemetryBuilderSdkExtensions.cs +++ b/src/OpenTelemetry/OpenTelemetryBuilderSdkExtensions.cs @@ -37,7 +37,9 @@ public static IOpenTelemetryBuilder ConfigureResource( Guard.ThrowIfNull(builder); Guard.ThrowIfNull(configure); +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 builder.Services.ConfigureOpenTelemetryMeterProvider( +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 builder => builder.ConfigureResource(configure)); builder.Services.ConfigureOpenTelemetryTracerProvider( @@ -84,8 +86,10 @@ public static IOpenTelemetryBuilder WithMetrics( Action configure) { OpenTelemetryMetricsBuilderExtensions.RegisterMetricsListener( +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 builder.Services, configure); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 return builder; } @@ -119,9 +123,11 @@ public static IOpenTelemetryBuilder WithTracing( { Guard.ThrowIfNull(configure); +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 var tracerProviderBuilder = new TracerProviderBuilderBase(builder.Services); configure(tracerProviderBuilder); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 return builder; } @@ -183,7 +189,11 @@ public static IOpenTelemetryBuilder WithLogging( Action? configureBuilder, Action? configureOptions) { + Guard.ThrowIfNull(builder); + +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 builder.Services.AddLogging( +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 logging => logging.UseOpenTelemetry(configureBuilder, configureOptions)); return builder; diff --git a/src/OpenTelemetry/OpenTelemetrySdkExtensions.cs b/src/OpenTelemetry/OpenTelemetrySdkExtensions.cs index f1f01e0f20a..f7d15ef8014 100644 --- a/src/OpenTelemetry/OpenTelemetrySdkExtensions.cs +++ b/src/OpenTelemetry/OpenTelemetrySdkExtensions.cs @@ -12,8 +12,6 @@ namespace OpenTelemetry; /// public static class OpenTelemetrySdkExtensions { - private static readonly NullLoggerFactory NoopLoggerFactory = new(); - /// /// Gets the contained in an instance. @@ -30,7 +28,9 @@ public static ILoggerFactory GetLoggerFactory(this OpenTelemetrySdk sdk) { Guard.ThrowIfNull(sdk); +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 return (ILoggerFactory?)sdk.Services.GetService(typeof(ILoggerFactory)) - ?? NoopLoggerFactory; +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 + ?? NullLoggerFactory.Instance; } } diff --git a/src/OpenTelemetry/README.md b/src/OpenTelemetry/README.md index e05a403f42a..bffb7bf03e2 100644 --- a/src/OpenTelemetry/README.md +++ b/src/OpenTelemetry/README.md @@ -82,7 +82,8 @@ the following content: { "LogDirectory": ".", "FileSize": 32768, - "LogLevel": "Warning" + "LogLevel": "Warning", + "FormatMessage": "true" } ``` @@ -117,6 +118,25 @@ You can also find the exact directory by calling these methods from your code. higher severity levels. For example, `Warning` includes the `Error` and `Critical` levels. +4. `FormatMessage` is a boolean value that controls whether log messages should + be formatted by replacing placeholders (`{0}`, `{1}`, etc.) with their actual + parameter values. When set to `false` (default), messages are logged with + unformatted placeholders followed by raw parameter values. When set to + `true`, placeholders are replaced with formatted parameter values for + improved readability. + + **Example with `FormatMessage: false` (default):** + + ```txt + 2025-07-24T01:45:04.1020880Z:Measurements from Instrument '{0}', Meter '{1}' will be ignored. Reason: '{2}'. Suggested action: '{3}'{dotnet.gc.collections}{System.Runtime}{Instrument belongs to a Meter not subscribed by the provider.}{Use AddMeter to add the Meter to the provider.} + ``` + + **Example with `FormatMessage: true`:** + + ```txt + 2025-07-24T01:44:44.7059260Z:Measurements from Instrument 'dotnet.gc.collections', Meter 'System.Runtime' will be ignored. Reason: 'Instrument belongs to a Meter not subscribed by the provider.'. Suggested action: 'Use AddMeter to add the Meter to the provider.' + ``` + #### Remarks A `FileSize`-KiB log file named as `ExecutableName.ProcessId.log` (e.g. diff --git a/src/OpenTelemetry/ReadOnlyFilteredTagCollection.cs b/src/OpenTelemetry/ReadOnlyFilteredTagCollection.cs index 6369018ca0b..4eb4320d56f 100644 --- a/src/OpenTelemetry/ReadOnlyFilteredTagCollection.cs +++ b/src/OpenTelemetry/ReadOnlyFilteredTagCollection.cs @@ -14,7 +14,9 @@ namespace OpenTelemetry; /// // Note: Does not implement IReadOnlyCollection<> or IEnumerable<> to // prevent accidental boxing. +#pragma warning disable CA1711 // Identifiers should not have incorrect suffix public readonly struct ReadOnlyFilteredTagCollection +#pragma warning restore CA1711 // Identifiers should not have incorrect suffix { #if NET private readonly FrozenSet? excludedKeys; @@ -22,7 +24,6 @@ public readonly struct ReadOnlyFilteredTagCollection private readonly HashSet? excludedKeys; #endif private readonly KeyValuePair[] tags; - private readonly int count; internal ReadOnlyFilteredTagCollection( #if NET @@ -38,7 +39,7 @@ internal ReadOnlyFilteredTagCollection( this.excludedKeys = excludedKeys; this.tags = tags; - this.count = count; + this.MaximumCount = count; } /// @@ -48,7 +49,7 @@ internal ReadOnlyFilteredTagCollection( /// Note: Enumerating the collection may return fewer results depending on /// the filter. /// - internal int MaximumCount => this.count; + internal int MaximumCount { get; } /// /// Returns an enumerator that iterates through the tags. @@ -72,7 +73,9 @@ internal ReadOnlyFilteredTagCollection( /// Enumerates the elements of a . /// // Note: Does not implement IEnumerator<> to prevent accidental boxing. +#pragma warning disable CA1034 // Nested types should not be visible - already part of public API public struct Enumerator +#pragma warning restore CA1034 // Nested types should not be visible - already part of public API { private readonly ReadOnlyFilteredTagCollection source; private int index; diff --git a/src/OpenTelemetry/ReadOnlyTagCollection.cs b/src/OpenTelemetry/ReadOnlyTagCollection.cs index f8582e1af99..c89386da8d7 100644 --- a/src/OpenTelemetry/ReadOnlyTagCollection.cs +++ b/src/OpenTelemetry/ReadOnlyTagCollection.cs @@ -8,13 +8,15 @@ namespace OpenTelemetry; /// // Note: Does not implement IReadOnlyCollection<> or IEnumerable<> to // prevent accidental boxing. +#pragma warning disable CA1711 // Identifiers should not have incorrect suffix public readonly struct ReadOnlyTagCollection +#pragma warning restore CA1711 // Identifiers should not have incorrect suffix { internal readonly KeyValuePair[] KeyAndValues; internal ReadOnlyTagCollection(KeyValuePair[]? keyAndValues) { - this.KeyAndValues = keyAndValues ?? Array.Empty>(); + this.KeyAndValues = keyAndValues ?? []; } /// @@ -32,7 +34,9 @@ internal ReadOnlyTagCollection(KeyValuePair[]? keyAndValues) /// Enumerates the elements of a . /// // Note: Does not implement IEnumerator<> to prevent accidental boxing. +#pragma warning disable CA1034 // Nested types should not be visible - already part of public API public struct Enumerator +#pragma warning restore CA1034 // Nested types should not be visible - already part of public API { private readonly ReadOnlyTagCollection source; private int index; diff --git a/src/OpenTelemetry/Resources/OtelEnvResourceDetector.cs b/src/OpenTelemetry/Resources/OtelEnvResourceDetector.cs index 41b5e7f28b1..468b37ca1a9 100644 --- a/src/OpenTelemetry/Resources/OtelEnvResourceDetector.cs +++ b/src/OpenTelemetry/Resources/OtelEnvResourceDetector.cs @@ -24,14 +24,14 @@ public Resource Detect() if (this.configuration.TryGetStringValue(EnvVarKey, out string? envResourceAttributeValue)) { - var attributes = ParseResourceAttributes(envResourceAttributeValue!); + var attributes = ParseResourceAttributes(envResourceAttributeValue); resource = new Resource(attributes); } return resource; } - private static IEnumerable> ParseResourceAttributes(string resourceAttributes) + private static List> ParseResourceAttributes(string resourceAttributes) { var attributes = new List>(); diff --git a/src/OpenTelemetry/Resources/OtelServiceNameEnvVarDetector.cs b/src/OpenTelemetry/Resources/OtelServiceNameEnvVarDetector.cs index 0b58f1d55b4..4bee4eadbbb 100644 --- a/src/OpenTelemetry/Resources/OtelServiceNameEnvVarDetector.cs +++ b/src/OpenTelemetry/Resources/OtelServiceNameEnvVarDetector.cs @@ -24,7 +24,7 @@ public Resource Detect() { resource = new Resource(new Dictionary { - [ResourceSemanticConventions.AttributeServiceName] = envResourceAttributeValue!, + [ResourceSemanticConventions.AttributeServiceName] = envResourceAttributeValue, }); } diff --git a/src/OpenTelemetry/Resources/Resource.cs b/src/OpenTelemetry/Resources/Resource.cs index a911deddf2e..afb21552675 100644 --- a/src/OpenTelemetry/Resources/Resource.cs +++ b/src/OpenTelemetry/Resources/Resource.cs @@ -24,7 +24,7 @@ public Resource(IEnumerable> attributes) if (attributes == null) { OpenTelemetrySdkEventSource.Log.InvalidArgument("Create resource", "attributes", "are null"); - this.Attributes = Enumerable.Empty>(); + this.Attributes = []; return; } @@ -35,7 +35,7 @@ public Resource(IEnumerable> attributes) /// /// Gets an empty Resource. /// - public static Resource Empty { get; } = new Resource(Enumerable.Empty>()); + public static Resource Empty { get; } = new([]); /// /// Gets the collection of key-value pairs describing the resource. @@ -105,8 +105,8 @@ private static object SanitizeValue(object value, string keyName) bool[] => value, double[] => value, long[] => value, - int => Convert.ToInt64(value), - short => Convert.ToInt64(value), + int => Convert.ToInt64(value, CultureInfo.InvariantCulture), + short => Convert.ToInt64(value, CultureInfo.InvariantCulture), float => Convert.ToDouble(value, CultureInfo.InvariantCulture), int[] v => Array.ConvertAll(v, Convert.ToInt64), short[] v => Array.ConvertAll(v, Convert.ToInt64), diff --git a/src/OpenTelemetry/Resources/ResourceBuilder.cs b/src/OpenTelemetry/Resources/ResourceBuilder.cs index f85755d7d02..0ab88d4d3f9 100644 --- a/src/OpenTelemetry/Resources/ResourceBuilder.cs +++ b/src/OpenTelemetry/Resources/ResourceBuilder.cs @@ -11,31 +11,8 @@ namespace OpenTelemetry.Resources; /// public class ResourceBuilder { - internal readonly List ResourceDetectors = new(); - private static readonly Resource DefaultResource; - - static ResourceBuilder() - { - var defaultServiceName = "unknown_service"; - - try - { - var processName = Process.GetCurrentProcess().ProcessName; - if (!string.IsNullOrWhiteSpace(processName)) - { - defaultServiceName = $"{defaultServiceName}:{processName}"; - } - } - catch - { - // GetCurrentProcess can throw PlatformNotSupportedException - } - - DefaultResource = new Resource(new Dictionary - { - [ResourceSemanticConventions.AttributeServiceName] = defaultServiceName, - }); - } + internal readonly List ResourceDetectors = []; + private static readonly Resource DefaultResource = PrepareDefaultResource(); private ResourceBuilder() { @@ -155,6 +132,29 @@ internal ResourceBuilder AddResource(Resource resource) return this; } + private static Resource PrepareDefaultResource() + { + var defaultServiceName = "unknown_service"; + + try + { + var processName = Process.GetCurrentProcess().ProcessName; + if (!string.IsNullOrWhiteSpace(processName)) + { + defaultServiceName = $"{defaultServiceName}:{processName}"; + } + } + catch + { + // GetCurrentProcess can throw PlatformNotSupportedException + } + + return new Resource(new Dictionary + { + [ResourceSemanticConventions.AttributeServiceName] = defaultServiceName, + }); + } + internal sealed class WrapperResourceDetector : IResourceDetector { private readonly Resource resource; @@ -189,7 +189,9 @@ public Resource Detect() Debug.Assert(detector != null, "detector was null"); +#pragma warning disable CA1508 // Avoid dead conditional code return detector?.Detect() ?? Resource.Empty; +#pragma warning restore CA1508 // Avoid dead conditional code } } } diff --git a/src/OpenTelemetry/Resources/ResourceBuilderExtensions.cs b/src/OpenTelemetry/Resources/ResourceBuilderExtensions.cs index 1f02b55381f..07b0040490a 100644 --- a/src/OpenTelemetry/Resources/ResourceBuilderExtensions.cs +++ b/src/OpenTelemetry/Resources/ResourceBuilderExtensions.cs @@ -42,10 +42,11 @@ public static ResourceBuilder AddService( bool autoGenerateServiceInstanceId = true, string? serviceInstanceId = null) { - Dictionary resourceAttributes = new Dictionary(); - + Guard.ThrowIfNull(resourceBuilder); Guard.ThrowIfNullOrEmpty(serviceName); + Dictionary resourceAttributes = new Dictionary(); + resourceAttributes.Add(ResourceSemanticConventions.AttributeServiceName, serviceName); if (!string.IsNullOrEmpty(serviceNamespace)) @@ -68,7 +69,9 @@ public static ResourceBuilder AddService( resourceAttributes.Add(ResourceSemanticConventions.AttributeServiceInstance, serviceInstanceId); } +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 return resourceBuilder.AddResource(new Resource(resourceAttributes)); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 } /// @@ -81,7 +84,10 @@ public static ResourceBuilder AddService( /// Returns for chaining. public static ResourceBuilder AddTelemetrySdk(this ResourceBuilder resourceBuilder) { + Guard.ThrowIfNull(resourceBuilder); +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 return resourceBuilder.AddResource(TelemetryResource); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 } /// @@ -92,7 +98,10 @@ public static ResourceBuilder AddTelemetrySdk(this ResourceBuilder resourceBuild /// Returns for chaining. public static ResourceBuilder AddAttributes(this ResourceBuilder resourceBuilder, IEnumerable> attributes) { + Guard.ThrowIfNull(resourceBuilder); +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 return resourceBuilder.AddResource(new Resource(attributes)); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 } /// @@ -105,9 +114,12 @@ public static ResourceBuilder AddAttributes(this ResourceBuilder resourceBuilder /// Returns for chaining. public static ResourceBuilder AddEnvironmentVariableDetector(this ResourceBuilder resourceBuilder) { + Guard.ThrowIfNull(resourceBuilder); Lazy configuration = new Lazy(() => new ConfigurationBuilder().AddEnvironmentVariables().Build()); +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 return resourceBuilder +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 .AddDetectorInternal(sp => new OtelEnvResourceDetector(sp?.GetService() ?? configuration.Value)) .AddDetectorInternal(sp => new OtelServiceNameEnvVarDetector(sp?.GetService() ?? configuration.Value)); } diff --git a/src/OpenTelemetry/Sdk.cs b/src/OpenTelemetry/Sdk.cs index 2cb4e0b6c9f..0711a414828 100644 --- a/src/OpenTelemetry/Sdk.cs +++ b/src/OpenTelemetry/Sdk.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; -#if EXPOSE_EXPERIMENTAL_FEATURES && NET +#if EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; #endif using OpenTelemetry.Context.Propagation; @@ -89,9 +89,7 @@ public static TracerProviderBuilder CreateTracerProviderBuilder() /// WARNING: This is an experimental API which might change or be removed in the future. Use at your own risk. /// instance, which is used /// to build a . -#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif public #else /// diff --git a/src/OpenTelemetry/SuppressInstrumentationScope.cs b/src/OpenTelemetry/SuppressInstrumentationScope.cs index 68168bc79d1..f568c0053f6 100644 --- a/src/OpenTelemetry/SuppressInstrumentationScope.cs +++ b/src/OpenTelemetry/SuppressInstrumentationScope.cs @@ -17,7 +17,9 @@ public sealed class SuppressInstrumentationScope : IDisposable // * Depth = [1, int.MaxValue]: instrumentation is suppressed in a reference-counting mode private static readonly RuntimeContextSlot Slot = RuntimeContext.RegisterSlot("otel.suppress_instrumentation"); +#pragma warning disable CA2213 // Disposable fields should be disposed private readonly SuppressInstrumentationScope? previousScope; +#pragma warning restore CA2213 // Disposable fields should be disposed private bool disposed; internal SuppressInstrumentationScope(bool value = true) @@ -73,10 +75,12 @@ public static int Enter() if (currentScope == null) { Slot.Set( +#pragma warning disable CA2000 // Dispose objects before losing scope new SuppressInstrumentationScope() { Depth = 1, }); +#pragma warning restore CA2000 // Dispose objects before losing scope return 1; } diff --git a/src/OpenTelemetry/Trace/Builder/TracerProviderBuilderBase.cs b/src/OpenTelemetry/Trace/Builder/TracerProviderBuilderBase.cs index 062fea7faee..894ebe7b265 100644 --- a/src/OpenTelemetry/Trace/Builder/TracerProviderBuilderBase.cs +++ b/src/OpenTelemetry/Trace/Builder/TracerProviderBuilderBase.cs @@ -82,7 +82,9 @@ TracerProviderBuilder ITracerProviderBuilder.ConfigureServices(Action +#pragma warning disable CA1033 // Interface methods should be callable by child types TracerProviderBuilder IDeferredTracerProviderBuilder.Configure(Action configure) +#pragma warning restore CA1033 // Interface methods should be callable by child types { this.innerBuilder.ConfigureBuilder(configure); diff --git a/src/OpenTelemetry/Trace/Processor/ActivityExportProcessorOptions.cs b/src/OpenTelemetry/Trace/Processor/ActivityExportProcessorOptions.cs index 86acb043015..895863cb41e 100644 --- a/src/OpenTelemetry/Trace/Processor/ActivityExportProcessorOptions.cs +++ b/src/OpenTelemetry/Trace/Processor/ActivityExportProcessorOptions.cs @@ -26,7 +26,9 @@ internal ActivityExportProcessorOptions( { Debug.Assert(defaultBatchExportActivityProcessorOptions != null, "defaultBatchExportActivityProcessorOptions was null"); +#pragma warning disable CA1508 // Avoid dead conditional code this.batchExportProcessorOptions = defaultBatchExportActivityProcessorOptions ?? new(); +#pragma warning restore CA1508 // Avoid dead conditional code } /// diff --git a/src/OpenTelemetry/Trace/Processor/BatchActivityExportProcessor.cs b/src/OpenTelemetry/Trace/Processor/BatchActivityExportProcessor.cs index 22b55f0e39f..73e7968c78f 100644 --- a/src/OpenTelemetry/Trace/Processor/BatchActivityExportProcessor.cs +++ b/src/OpenTelemetry/Trace/Processor/BatchActivityExportProcessor.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; +using OpenTelemetry.Internal; namespace OpenTelemetry; @@ -36,7 +37,10 @@ public BatchActivityExportProcessor( /// public override void OnEnd(Activity data) { + Guard.ThrowIfNull(data); +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 if (!data.Recorded) +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 { return; } diff --git a/src/OpenTelemetry/Trace/Processor/ExceptionProcessor.cs b/src/OpenTelemetry/Trace/Processor/ExceptionProcessor.cs index f3845519408..a9297f2ded6 100644 --- a/src/OpenTelemetry/Trace/Processor/ExceptionProcessor.cs +++ b/src/OpenTelemetry/Trace/Processor/ExceptionProcessor.cs @@ -62,9 +62,9 @@ public override void OnEnd(Activity activity) if (snapshot != pointers) { -#pragma warning disable +#pragma warning disable CS0618 // Type or member is obsolete activity.SetStatus(Status.Error); -#pragma warning restore +#pragma warning restore CS0618 // Type or member is obsolete } } } diff --git a/src/OpenTelemetry/Trace/Processor/SimpleActivityExportProcessor.cs b/src/OpenTelemetry/Trace/Processor/SimpleActivityExportProcessor.cs index 463bc6d111f..956d035e2c4 100644 --- a/src/OpenTelemetry/Trace/Processor/SimpleActivityExportProcessor.cs +++ b/src/OpenTelemetry/Trace/Processor/SimpleActivityExportProcessor.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; +using OpenTelemetry.Internal; namespace OpenTelemetry; @@ -22,7 +23,10 @@ public SimpleActivityExportProcessor(BaseExporter exporter) /// public override void OnEnd(Activity data) { + Guard.ThrowIfNull(data); +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 if (!data.Recorded) +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 { return; } diff --git a/src/OpenTelemetry/Trace/Sampler/ParentBasedSampler.cs b/src/OpenTelemetry/Trace/Sampler/ParentBasedSampler.cs index c7665cc3f36..354cf5bf0db 100644 --- a/src/OpenTelemetry/Trace/Sampler/ParentBasedSampler.cs +++ b/src/OpenTelemetry/Trace/Sampler/ParentBasedSampler.cs @@ -33,7 +33,9 @@ public ParentBasedSampler(Sampler rootSampler) Guard.ThrowIfNull(rootSampler); this.rootSampler = rootSampler; +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 this.Description = $"ParentBased{{{rootSampler.Description}}}"; +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 this.remoteParentSampled = new AlwaysOnSampler(); this.remoteParentNotSampled = new AlwaysOffSampler(); diff --git a/src/OpenTelemetry/Trace/Sampler/SamplingResult.cs b/src/OpenTelemetry/Trace/Sampler/SamplingResult.cs index 40cac1c201d..bedebd0abaa 100644 --- a/src/OpenTelemetry/Trace/Sampler/SamplingResult.cs +++ b/src/OpenTelemetry/Trace/Sampler/SamplingResult.cs @@ -102,7 +102,7 @@ public override bool Equals(object? obj) /// public override int GetHashCode() { -#if NET +#if NET || NETSTANDARD2_1_OR_GREATER HashCode hashCode = default; hashCode.Add(this.Decision); hashCode.Add(this.Attributes); diff --git a/src/OpenTelemetry/Trace/TracerProviderExtensions.cs b/src/OpenTelemetry/Trace/TracerProviderExtensions.cs index 864e36959b1..7dc8c8f1376 100644 --- a/src/OpenTelemetry/Trace/TracerProviderExtensions.cs +++ b/src/OpenTelemetry/Trace/TracerProviderExtensions.cs @@ -24,7 +24,9 @@ public static TracerProvider AddProcessor(this TracerProvider provider, BaseProc if (provider is TracerProviderSdk tracerProviderSdk) { +#pragma warning disable CA1062 // Validate arguments of public methods - needed for netstandard2.1 tracerProviderSdk.AddProcessor(processor); +#pragma warning restore CA1062 // Validate arguments of public methods - needed for netstandard2.1 } return provider; diff --git a/src/OpenTelemetry/Trace/TracerProviderSdk.cs b/src/OpenTelemetry/Trace/TracerProviderSdk.cs index fb5b24bfe7c..d79fe0b051c 100644 --- a/src/OpenTelemetry/Trace/TracerProviderSdk.cs +++ b/src/OpenTelemetry/Trace/TracerProviderSdk.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; +using System.Globalization; using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; @@ -18,11 +19,11 @@ internal sealed class TracerProviderSdk : TracerProvider internal const string TracesSamplerArgConfigKey = "OTEL_TRACES_SAMPLER_ARG"; internal readonly IServiceProvider ServiceProvider; - internal readonly IDisposable? OwnedServiceProvider; + internal IDisposable? OwnedServiceProvider; internal int ShutdownCount; internal bool Disposed; - private readonly List instrumentations = new(); + private readonly List instrumentations = []; private readonly ActivityListener listener; private readonly Sampler sampler; private readonly Action getRequestedDataAction; @@ -251,7 +252,7 @@ internal TracerProviderSdk( // Sources can be null. This happens when user // is only interested in InstrumentationLibraries // which do not depend on ActivitySources. - if (state.Sources.Any()) + if (state.Sources.Count > 0) { // Validation of source name is already done in builder. if (state.Sources.Any(s => WildcardHelper.ContainsWildcard(s))) @@ -352,18 +353,14 @@ internal bool OnForceFlush(int timeoutMilliseconds) internal bool OnShutdown(int timeoutMilliseconds) { // TO DO Put OnShutdown logic in a task to run within the user provider timeOutMilliseconds - bool? result; - if (this.instrumentations != null) + foreach (var item in this.instrumentations) { - foreach (var item in this.instrumentations) - { - (item as IDisposable)?.Dispose(); - } - - this.instrumentations.Clear(); + (item as IDisposable)?.Dispose(); } - result = this.processor?.Shutdown(timeoutMilliseconds); + this.instrumentations.Clear(); + + bool? result = this.processor?.Shutdown(timeoutMilliseconds); this.listener?.Dispose(); return result ?? true; } @@ -374,21 +371,19 @@ protected override void Dispose(bool disposing) { if (disposing) { - if (this.instrumentations != null) + foreach (var item in this.instrumentations) { - foreach (var item in this.instrumentations) - { - (item as IDisposable)?.Dispose(); - } - - this.instrumentations.Clear(); + (item as IDisposable)?.Dispose(); } + this.instrumentations.Clear(); + (this.sampler as IDisposable)?.Dispose(); // Wait for up to 5 seconds grace period this.processor?.Shutdown(5000); this.processor?.Dispose(); + this.processor = null; // Shutdown the listener last so that anything created while instrumentation cleans up will still be processed. // Redis instrumentation, for example, flushes during dispose which creates Activity objects for any profiling @@ -396,6 +391,7 @@ protected override void Dispose(bool disposing) this.listener?.Dispose(); this.OwnedServiceProvider?.Dispose(); + this.OwnedServiceProvider = null; } this.Disposed = true; @@ -447,7 +443,7 @@ private static Sampler GetSampler(IConfiguration configuration, Sampler? stateSa } default: - OpenTelemetrySdkEventSource.Log.TracesSamplerConfigInvalid(configValue ?? string.Empty); + OpenTelemetrySdkEventSource.Log.TracesSamplerConfigInvalid(configValue); break; } @@ -463,7 +459,7 @@ private static Sampler GetSampler(IConfiguration configuration, Sampler? stateSa private static double ReadTraceIdRatio(IConfiguration configuration) { if (configuration.TryGetStringValue(TracesSamplerArgConfigKey, out var configValue) && - double.TryParse(configValue, out var traceIdRatio)) + double.TryParse(configValue, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var traceIdRatio)) { return traceIdRatio; } diff --git a/src/Shared/AssemblyVersionExtensions.cs b/src/Shared/AssemblyVersionExtensions.cs index 67926d99c03..9d4b351a3ab 100644 --- a/src/Shared/AssemblyVersionExtensions.cs +++ b/src/Shared/AssemblyVersionExtensions.cs @@ -46,7 +46,11 @@ private static string ParsePackageVersion(string informationalVersion) // The following parts are optional: pre-release label, pre-release version, git height, Git SHA of current commit // For package version, value of AssemblyInformationalVersionAttribute without commit hash is returned. - var indexOfPlusSign = informationalVersion!.IndexOf('+'); +#if NET || NETSTANDARD2_1_OR_GREATER + var indexOfPlusSign = informationalVersion.IndexOf('+', StringComparison.Ordinal); +#else + var indexOfPlusSign = informationalVersion.IndexOf('+'); +#endif return indexOfPlusSign > 0 ? informationalVersion.Substring(0, indexOfPlusSign) : informationalVersion; diff --git a/src/Shared/Configuration/OpenTelemetryConfigurationExtensions.cs b/src/Shared/Configuration/OpenTelemetryConfigurationExtensions.cs index c713fd166dd..589f960d512 100644 --- a/src/Shared/Configuration/OpenTelemetryConfigurationExtensions.cs +++ b/src/Shared/Configuration/OpenTelemetryConfigurationExtensions.cs @@ -2,9 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; -#if !NETFRAMEWORK && !NETSTANDARD2_0 using System.Diagnostics.CodeAnalysis; -#endif using System.Globalization; namespace Microsoft.Extensions.Configuration; @@ -13,17 +11,13 @@ internal static class OpenTelemetryConfigurationExtensions { public delegate bool TryParseFunc( string value, -#if !NETFRAMEWORK && !NETSTANDARD2_0 [NotNullWhen(true)] -#endif out T? parsedValue); public static bool TryGetStringValue( this IConfiguration configuration, string key, -#if !NETFRAMEWORK && !NETSTANDARD2_0 [NotNullWhen(true)] -#endif out string? value) { Debug.Assert(configuration != null, "configuration was null"); @@ -37,9 +31,7 @@ public static bool TryGetUriValue( this IConfiguration configuration, IConfigurationExtensionsLogger logger, string key, -#if !NETFRAMEWORK && !NETSTANDARD2_0 [NotNullWhen(true)] -#endif out Uri? value) { Debug.Assert(logger != null, "logger was null"); @@ -52,7 +44,7 @@ public static bool TryGetUriValue( if (!Uri.TryCreate(stringValue, UriKind.Absolute, out value)) { - logger!.LogInvalidConfigurationValue(key, stringValue!); + logger!.LogInvalidConfigurationValue(key, stringValue); return false; } @@ -75,7 +67,7 @@ public static bool TryGetIntValue( if (!int.TryParse(stringValue, NumberStyles.None, CultureInfo.InvariantCulture, out value)) { - logger!.LogInvalidConfigurationValue(key, stringValue!); + logger!.LogInvalidConfigurationValue(key, stringValue); return false; } @@ -98,7 +90,7 @@ public static bool TryGetBoolValue( if (!bool.TryParse(stringValue, out value)) { - logger!.LogInvalidConfigurationValue(key, stringValue!); + logger!.LogInvalidConfigurationValue(key, stringValue); return false; } @@ -110,9 +102,7 @@ public static bool TryGetValue( IConfigurationExtensionsLogger logger, string key, TryParseFunc tryParseFunc, -#if !NETFRAMEWORK && !NETSTANDARD2_0 [NotNullWhen(true)] -#endif out T? value) { Debug.Assert(logger != null, "logger was null"); @@ -123,9 +113,9 @@ public static bool TryGetValue( return false; } - if (!tryParseFunc(stringValue!, out value)) + if (!tryParseFunc(stringValue, out value)) { - logger!.LogInvalidConfigurationValue(key, stringValue!); + logger!.LogInvalidConfigurationValue(key, stringValue); return false; } diff --git a/src/Shared/MathHelper.cs b/src/Shared/MathHelper.cs index 21367918d51..bb6edaa00a9 100644 --- a/src/Shared/MathHelper.cs +++ b/src/Shared/MathHelper.cs @@ -12,8 +12,8 @@ namespace OpenTelemetry.Internal; internal static class MathHelper { // https://en.wikipedia.org/wiki/Leading_zero - private static readonly byte[] LeadingZeroLookupTable = new byte[] - { + private static readonly byte[] LeadingZeroLookupTable = + [ 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, @@ -29,8 +29,8 @@ internal static class MathHelper 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - }; + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LeadingZero8(byte value) diff --git a/src/Shared/Metrics/Base2ExponentialBucketHistogramHelper.cs b/src/Shared/Metrics/Base2ExponentialBucketHistogramHelper.cs index 27f498c0bda..f9a1ece4c6f 100644 --- a/src/Shared/Metrics/Base2ExponentialBucketHistogramHelper.cs +++ b/src/Shared/Metrics/Base2ExponentialBucketHistogramHelper.cs @@ -105,7 +105,7 @@ private static double ScaleB(double x, int n) } } - double u = BitConverter.Int64BitsToDouble(((long)(0x3ff + n) << 52)); + double u = BitConverter.Int64BitsToDouble((long)(0x3ff + n) << 52); return y * u; } #pragma warning restore SA1119 // Statement should not use unnecessary parenthesis diff --git a/src/Shared/Options/DelegatingOptionsFactory.cs b/src/Shared/Options/DelegatingOptionsFactory.cs index 995b69ae49c..ac5a59fa94b 100644 --- a/src/Shared/Options/DelegatingOptionsFactory.cs +++ b/src/Shared/Options/DelegatingOptionsFactory.cs @@ -64,9 +64,9 @@ public DelegatingOptionsFactory( this.optionsFactoryFunc = optionsFactoryFunc!; this.configuration = configuration!; - _setups = setups as IConfigureOptions[] ?? new List>(setups).ToArray(); - _postConfigures = postConfigures as IPostConfigureOptions[] ?? new List>(postConfigures).ToArray(); - _validations = validations as IValidateOptions[] ?? new List>(validations).ToArray(); + _setups = setups as IConfigureOptions[] ?? setups.ToArray(); + _postConfigures = postConfigures as IPostConfigureOptions[] ?? postConfigures.ToArray(); + _validations = validations as IValidateOptions[] ?? validations.ToArray(); } /// diff --git a/src/Shared/PeerServiceResolver.cs b/src/Shared/PeerServiceResolver.cs deleted file mode 100644 index 21201b38368..00000000000 --- a/src/Shared/PeerServiceResolver.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Runtime.CompilerServices; -using OpenTelemetry.Trace; - -namespace OpenTelemetry.Exporter; - -internal static class PeerServiceResolver -{ - private static readonly Dictionary PeerServiceKeyResolutionDictionary = new(StringComparer.OrdinalIgnoreCase) - { - [SemanticConventions.AttributePeerService] = 0, // priority 0 (highest). - ["peer.hostname"] = 1, - ["peer.address"] = 1, - [SemanticConventions.AttributeHttpHost] = 2, // peer.service for Http. - [SemanticConventions.AttributeDbInstance] = 2, // peer.service for Redis. - }; - - public interface IPeerServiceState - { - string? PeerService { get; set; } - - int? PeerServicePriority { get; set; } - - string? HostName { get; set; } - - string? IpAddress { get; set; } - - long Port { get; set; } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InspectTag(ref T state, string key, string? value) - where T : struct, IPeerServiceState - { - if (PeerServiceKeyResolutionDictionary.TryGetValue(key, out int priority) - && (state.PeerService == null || priority < state.PeerServicePriority)) - { - state.PeerService = value; - state.PeerServicePriority = priority; - } - else if (key == SemanticConventions.AttributeNetPeerName) - { - state.HostName = value; - } - else if (key == SemanticConventions.AttributeNetPeerIp) - { - state.IpAddress = value; - } - else if (key == SemanticConventions.AttributeNetPeerPort && long.TryParse(value, out var port)) - { - state.Port = port; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InspectTag(ref T state, string key, long value) - where T : struct, IPeerServiceState - { - if (key == SemanticConventions.AttributeNetPeerPort) - { - state.Port = value; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Resolve(ref T state, out string? peerServiceName, out bool addAsTag) - where T : struct, IPeerServiceState - { - peerServiceName = state.PeerService; - - // If priority = 0 that means peer.service was included in tags - addAsTag = state.PeerServicePriority != 0; - - if (addAsTag) - { - var hostNameOrIpAddress = state.HostName ?? state.IpAddress; - - // peer.service has not already been included, but net.peer.name/ip and optionally net.peer.port are present - if (hostNameOrIpAddress != null) - { - peerServiceName = state.Port == default - ? hostNameOrIpAddress - : $"{hostNameOrIpAddress}:{state.Port}"; - } - else if (state.PeerService != null) - { - peerServiceName = state.PeerService; - } - } - } -} diff --git a/src/Shared/Proto/opentelemetry/proto/logs/v1/logs.proto b/src/Shared/Proto/opentelemetry/proto/logs/v1/logs.proto index 261d22916b9..5ce16fa0f86 100644 --- a/src/Shared/Proto/opentelemetry/proto/logs/v1/logs.proto +++ b/src/Shared/Proto/opentelemetry/proto/logs/v1/logs.proto @@ -221,7 +221,5 @@ message LogRecord { // as an event. // // [Optional]. - // - // Status: [Development] string event_name = 12; -} +} \ No newline at end of file diff --git a/src/Shared/SemanticConventions.cs b/src/Shared/SemanticConventions.cs index f84bd6de490..22c5f9b626c 100644 --- a/src/Shared/SemanticConventions.cs +++ b/src/Shared/SemanticConventions.cs @@ -24,4 +24,22 @@ internal static class SemanticConventions public const string AttributeExceptionType = "exception.type"; public const string AttributeExceptionMessage = "exception.message"; public const string AttributeExceptionStacktrace = "exception.stacktrace"; + + public const string AttributeServerAddress = "server.address"; + + public const string AttributeNetworkPeerAddress = "network.peer.address"; + public const string AttributeNetworkPeerPort = "network.peer.port"; + + public const string AttributeServerSocketDomain = "server.socket.domain"; + public const string AttributeServerSocketAddress = "server.socket.address"; + public const string AttributeServerSocketPort = "server.socket.port"; + + public const string AttributeNetSockPeerName = "net.sock.peer.name"; + public const string AttributeNetSockPeerAddr = "net.sock.peer.addr"; + public const string AttributeNetSockPeerPort = "net.sock.peer.port"; + + public const string AttributePeerHostname = "peer.hostname"; + public const string AttributePeerAddress = "peer.address"; + + public const string AttributeDbName = "db.name"; } diff --git a/src/Shared/Shims/ExperimentalAttribute.cs b/src/Shared/Shims/ExperimentalAttribute.cs new file mode 100644 index 00000000000..2794b177c37 --- /dev/null +++ b/src/Shared/Shims/ExperimentalAttribute.cs @@ -0,0 +1,76 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Source: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/ExperimentalAttribute.cs + +#if NETFRAMEWORK || NETSTANDARD2_0_OR_GREATER + +#nullable enable + +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Indicates that an API is experimental and it may change in the future. + /// + /// + /// This attribute allows call sites to be flagged with a diagnostic that indicates that an experimental + /// feature is used. Authors can use this attribute to ship preview features in their assemblies. + /// + [AttributeUsage(AttributeTargets.Assembly | + AttributeTargets.Module | + AttributeTargets.Class | + AttributeTargets.Struct | + AttributeTargets.Enum | + AttributeTargets.Constructor | + AttributeTargets.Method | + AttributeTargets.Property | + AttributeTargets.Field | + AttributeTargets.Event | + AttributeTargets.Interface | + AttributeTargets.Delegate, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class ExperimentalAttribute : Attribute + { + /// + /// Initializes a new instance of the class, specifying the ID that the compiler will use + /// when reporting a use of the API the attribute applies to. + /// + /// The ID that the compiler will use when reporting a use of the API the attribute applies to. + public ExperimentalAttribute(string diagnosticId) + { + DiagnosticId = diagnosticId; + } + + /// + /// Gets the ID that the compiler will use when reporting a use of the API the attribute applies to. + /// + /// The unique diagnostic ID. + /// + /// The diagnostic ID is shown in build output for warnings and errors. + /// This property represents the unique ID that can be used to suppress the warnings or errors, if needed. + /// + public string DiagnosticId { get; } + + /// + /// Gets or sets an optional message associated with the experimental attribute. + /// + /// The message that provides additional information about the experimental feature. + /// + /// This message can be used to provide more context or guidance about the experimental feature. + /// + public string? Message { get; set; } + + /// + /// Gets or sets the URL for corresponding documentation. + /// The API accepts a format string instead of an actual URL, creating a generic URL that includes the diagnostic ID. + /// + /// The format string that represents a URL to corresponding documentation. + /// An example format string is https://contoso.com/obsoletion-warnings/{0}. + public string? UrlFormat { get; set; } + } +} +#endif diff --git a/src/Shared/Shims/NullableAttributes.cs b/src/Shared/Shims/NullableAttributes.cs index 3126b557623..a5f776258e4 100644 --- a/src/Shared/Shims/NullableAttributes.cs +++ b/src/Shared/Shims/NullableAttributes.cs @@ -51,7 +51,7 @@ internal sealed class MemberNotNullWhenAttribute : Attribute public MemberNotNullWhenAttribute(bool returnValue, string member) { ReturnValue = returnValue; - Members = new[] { member }; + Members = [member]; } public MemberNotNullWhenAttribute(bool returnValue, params string[] members) diff --git a/src/Shared/TagWriter/JsonStringArrayTagWriter.cs b/src/Shared/TagWriter/JsonStringArrayTagWriter.cs index 8eefc4b4f94..d79b6446bee 100644 --- a/src/Shared/TagWriter/JsonStringArrayTagWriter.cs +++ b/src/Shared/TagWriter/JsonStringArrayTagWriter.cs @@ -25,6 +25,8 @@ protected sealed override void WriteArrayTag(ref TTagState writer, string key, r protected abstract void WriteArrayTag(ref TTagState writer, string key, ArraySegment arrayUtf8JsonBytes); + protected override bool TryWriteByteArrayTag(ref TTagState consoleTag, string key, ReadOnlySpan value) => false; + internal readonly struct JsonArrayTagWriterState(MemoryStream stream, Utf8JsonWriter writer) { public MemoryStream Stream { get; } = stream; diff --git a/src/Shared/TagWriter/TagWriter.cs b/src/Shared/TagWriter/TagWriter.cs index fdd83a76bfd..3794a9d76eb 100644 --- a/src/Shared/TagWriter/TagWriter.cs +++ b/src/Shared/TagWriter/TagWriter.cs @@ -36,7 +36,7 @@ public bool TryWriteTag( { if (value == null) { - return false; + return this.TryWriteEmptyTag(ref state, key, value); } switch (value) @@ -60,13 +60,18 @@ public bool TryWriteTag( case int: case uint: case long: - this.WriteIntegralTag(ref state, key, Convert.ToInt64(value)); + this.WriteIntegralTag(ref state, key, Convert.ToInt64(value, CultureInfo.InvariantCulture)); break; case float: case double: - this.WriteFloatingPointTag(ref state, key, Convert.ToDouble(value)); + this.WriteFloatingPointTag(ref state, key, Convert.ToDouble(value, CultureInfo.InvariantCulture)); break; case Array array: + if (value.GetType() == typeof(byte[]) && this.TryWriteByteArrayTag(ref state, key, ((byte[])value).AsSpan())) + { + return true; + } + try { this.WriteArrayTagInternal(ref state, key, array, tagValueMaxLength); @@ -117,6 +122,10 @@ public bool TryWriteTag( return true; } + protected abstract bool TryWriteEmptyTag(ref TTagState state, string key, object? value); + + protected abstract bool TryWriteByteArrayTag(ref TTagState state, string key, ReadOnlySpan value); + protected abstract void WriteIntegralTag(ref TTagState state, string key, long value); protected abstract void WriteFloatingPointTag(ref TTagState state, string key, double value); @@ -140,15 +149,13 @@ private static ReadOnlySpan TruncateString(ReadOnlySpan value, int? private void WriteCharTag(ref TTagState state, string key, char value) { - Span destination = stackalloc char[1]; - destination[0] = value; + Span destination = [value]; this.WriteStringTag(ref state, key, destination); } private void WriteCharValue(ref TArrayState state, char value) { - Span destination = stackalloc char[1]; - destination[0] = value; + Span destination = [value]; this.arrayWriter.WriteStringValue(ref state, destination); } @@ -196,7 +203,7 @@ private void WriteArrayTagInternal(ref TTagState state, string key, Array array, key, "TRUNCATED".AsSpan()); - this.LogUnsupportedTagTypeAndReturnFalse(key, array!.GetType().ToString()); + this.LogUnsupportedTagTypeAndReturnFalse(key, array.GetType().ToString()); return; } @@ -266,11 +273,11 @@ private void WriteToArrayTypeChecked(ref TArrayState arrayState, Array array, in case int: case uint: case long: - this.arrayWriter.WriteIntegralValue(ref arrayState, Convert.ToInt64(item)); + this.arrayWriter.WriteIntegralValue(ref arrayState, Convert.ToInt64(item, CultureInfo.InvariantCulture)); break; case float: case double: - this.arrayWriter.WriteFloatingPointValue(ref arrayState, Convert.ToDouble(item)); + this.arrayWriter.WriteFloatingPointValue(ref arrayState, Convert.ToDouble(item, CultureInfo.InvariantCulture)); break; // All other types are converted to strings including the following diff --git a/src/Shared/ThreadSafeRandom.cs b/src/Shared/ThreadSafeRandom.cs index 10cd995809a..b0419b482fb 100644 --- a/src/Shared/ThreadSafeRandom.cs +++ b/src/Shared/ThreadSafeRandom.cs @@ -9,7 +9,9 @@ internal static class ThreadSafeRandom #if NET public static int Next(int min, int max) { +#pragma warning disable CA5394 // Do not use insecure randomness return Random.Shared.Next(min, max); +#pragma warning restore CA5394 // Do not use insecure randomness } #else private static readonly Random GlobalRandom = new(); @@ -25,6 +27,7 @@ public static int Next(int min, int max) int seed; lock (GlobalRandom) { +#pragma warning disable CA5394 // Do not use insecure randomness seed = GlobalRandom.Next(); } @@ -32,6 +35,7 @@ public static int Next(int min, int max) } return local.Next(min, max); +#pragma warning restore CA5394 // Do not use insecure randomness } #endif } diff --git a/test/Benchmarks/Benchmarks.csproj b/test/Benchmarks/Benchmarks.csproj index 34d74cb0bd7..c820c9e0d14 100644 --- a/test/Benchmarks/Benchmarks.csproj +++ b/test/Benchmarks/Benchmarks.csproj @@ -3,6 +3,7 @@ Exe $(TargetFrameworksForTests) + $(NoWarn);CA1515 diff --git a/test/Benchmarks/Context/Propagation/TraceContextPropagatorBenchmarks.cs b/test/Benchmarks/Context/Propagation/TraceContextPropagatorBenchmarks.cs index fdae9b0c4f7..e73d65c9f3f 100644 --- a/test/Benchmarks/Context/Propagation/TraceContextPropagatorBenchmarks.cs +++ b/test/Benchmarks/Context/Propagation/TraceContextPropagatorBenchmarks.cs @@ -49,7 +49,9 @@ public void Setup() // We want a unique key for each member for (var j = 0; j < length - 2; j++) { +#pragma warning disable CA5394 // Do not use insecure randomness keyBuffer[j] = (char)('a' + Random.Next(0, 26)); +#pragma warning restore CA5394 // Do not use insecure randomness } var key = keyBuffer.ToString(); @@ -66,5 +68,5 @@ public void Setup() } [Benchmark(Baseline = true)] - public void Extract() => _ = TraceContextPropagator!.Extract(default, this.Headers!, Getter); + public void Extract() => _ = TraceContextPropagator.Extract(default, this.Headers!, Getter); } diff --git a/test/Benchmarks/EventSourceBenchmarks.cs b/test/Benchmarks/EventSourceBenchmarks.cs index ca8b4ffcd7f..5e4460bc33d 100644 --- a/test/Benchmarks/EventSourceBenchmarks.cs +++ b/test/Benchmarks/EventSourceBenchmarks.cs @@ -10,7 +10,9 @@ namespace OpenTelemetry.Benchmarks; public class EventSourceBenchmarks { [Benchmark] +#pragma warning disable CA1822 // Mark members as static public void EventWithIdAllocation() +#pragma warning restore CA1822 // Mark members as static { using var activity = new Activity("TestActivity"); activity.SetIdFormat(ActivityIdFormat.W3C); @@ -21,7 +23,9 @@ public void EventWithIdAllocation() } [Benchmark] +#pragma warning disable CA1822 // Mark members as static public void EventWithCheck() +#pragma warning restore CA1822 // Mark members as static { using var activity = new Activity("TestActivity"); activity.SetIdFormat(ActivityIdFormat.W3C); diff --git a/test/Benchmarks/Exporter/OtlpGrpcExporterBenchmarks.cs b/test/Benchmarks/Exporter/OtlpGrpcExporterBenchmarks.cs index 7d880a04ddf..d84c9e599e4 100644 --- a/test/Benchmarks/Exporter/OtlpGrpcExporterBenchmarks.cs +++ b/test/Benchmarks/Exporter/OtlpGrpcExporterBenchmarks.cs @@ -15,7 +15,9 @@ namespace Benchmarks.Exporter; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public class OtlpGrpcExporterBenchmarks +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { private OtlpTraceExporter? exporter; private Activity? activity; @@ -35,7 +37,9 @@ public void GlobalSetup() options, new SdkLimitOptions(), new ExperimentalOptions(), +#pragma warning disable CA2000 // Dispose objects before losing scope new OtlpExporterTransmissionHandler(new OtlpGrpcExportClient(options, options.HttpClientFactory(), "opentelemetry.proto.collector.trace.v1.TraceService/Export"), options.TimeoutMilliseconds)); +#pragma warning restore CA2000 // Dispose objects before losing scope this.activity = ActivityHelper.CreateTestActivity(); this.activityBatch = new CircularBuffer(this.NumberOfSpans); @@ -44,6 +48,7 @@ public void GlobalSetup() [GlobalCleanup] public void GlobalCleanup() { + this.activity?.Dispose(); this.exporter?.Shutdown(); this.exporter?.Dispose(); } diff --git a/test/Benchmarks/Exporter/OtlpHttpExporterBenchmarks.cs b/test/Benchmarks/Exporter/OtlpHttpExporterBenchmarks.cs index 02953143bcb..0137f9055d8 100644 --- a/test/Benchmarks/Exporter/OtlpHttpExporterBenchmarks.cs +++ b/test/Benchmarks/Exporter/OtlpHttpExporterBenchmarks.cs @@ -16,7 +16,9 @@ namespace Benchmarks.Exporter; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public class OtlpHttpExporterBenchmarks +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { private readonly byte[] buffer = new byte[1024 * 1024]; private IDisposable? server; @@ -63,7 +65,9 @@ public void GlobalSetup() options, new SdkLimitOptions(), new ExperimentalOptions(), +#pragma warning disable CA2000 // Dispose objects before losing scope new OtlpExporterTransmissionHandler(new OtlpHttpExportClient(options, options.HttpClientFactory(), "v1/traces"), options.TimeoutMilliseconds)); +#pragma warning restore CA2000 // Dispose objects before losing scope this.activity = ActivityHelper.CreateTestActivity(); this.activityBatch = new CircularBuffer(this.NumberOfSpans); @@ -72,6 +76,7 @@ public void GlobalSetup() [GlobalCleanup] public void GlobalCleanup() { + this.activity?.Dispose(); this.exporter?.Shutdown(); this.exporter?.Dispose(); this.server?.Dispose(); diff --git a/test/Benchmarks/Exporter/OtlpLogExporterBenchmarks.cs b/test/Benchmarks/Exporter/OtlpLogExporterBenchmarks.cs index 8dc435b8fd8..eba68dfd7e5 100644 --- a/test/Benchmarks/Exporter/OtlpLogExporterBenchmarks.cs +++ b/test/Benchmarks/Exporter/OtlpLogExporterBenchmarks.cs @@ -34,7 +34,9 @@ .NET SDK 7.0.400 namespace Benchmarks.Exporter; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public class OtlpLogExporterBenchmarks +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { private OtlpLogExporter? exporter; private LogRecord? logRecord; @@ -128,13 +130,15 @@ public void OtlpLogExporter_Grpc() this.exporter!.Export(new Batch(this.logRecordBatch!, 1)); } +#pragma warning disable CA1812 // Avoid uninstantiated internal classes private sealed class MockLogService : OtlpCollector.LogsService.LogsServiceBase +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { - private static OtlpCollector.ExportLogsServiceResponse response = new OtlpCollector.ExportLogsServiceResponse(); + private static readonly OtlpCollector.ExportLogsServiceResponse Response = new(); public override Task Export(OtlpCollector.ExportLogsServiceRequest request, ServerCallContext context) { - return Task.FromResult(response); + return Task.FromResult(Response); } } } diff --git a/test/Benchmarks/Exporter/OtlpTraceExporterBenchmarks.cs b/test/Benchmarks/Exporter/OtlpTraceExporterBenchmarks.cs index cadbe1e89c4..7afea3fa8be 100644 --- a/test/Benchmarks/Exporter/OtlpTraceExporterBenchmarks.cs +++ b/test/Benchmarks/Exporter/OtlpTraceExporterBenchmarks.cs @@ -34,7 +34,9 @@ .NET SDK 7.0.400 namespace Benchmarks.Exporter; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public class OtlpTraceExporterBenchmarks +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { private OtlpTraceExporter? exporter; private Activity? activity; @@ -130,13 +132,15 @@ public void OtlpTraceExporter_Grpc() this.exporter!.Export(new Batch(this.activityBatch!, 1)); } +#pragma warning disable CA1812 // Avoid uninstantiated internal classes private sealed class MockTraceService : OtlpCollector.TraceService.TraceServiceBase +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { - private static OtlpCollector.ExportTraceServiceResponse response = new OtlpCollector.ExportTraceServiceResponse(); + private static readonly OtlpCollector.ExportTraceServiceResponse Response = new(); public override Task Export(OtlpCollector.ExportTraceServiceRequest request, ServerCallContext context) { - return Task.FromResult(response); + return Task.FromResult(Response); } } } diff --git a/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs b/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs index 79f5dde5749..47d04dd0d10 100644 --- a/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs +++ b/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs @@ -10,13 +10,15 @@ namespace Benchmarks.Exporter; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public class PrometheusSerializerBenchmarks +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { - private readonly List metrics = new(); + private readonly List metrics = []; private readonly byte[] buffer = new byte[85000]; + private readonly Dictionary cache = []; private Meter? meter; private MeterProvider? meterProvider; - private Dictionary cache = new Dictionary(); [Params(1, 1000, 10000)] public int NumberOfSerializeCalls { get; set; } diff --git a/test/Benchmarks/Helper/LogRecordHelper.cs b/test/Benchmarks/Helper/LogRecordHelper.cs index f08e62527f5..40b6c4983e2 100644 --- a/test/Benchmarks/Helper/LogRecordHelper.cs +++ b/test/Benchmarks/Helper/LogRecordHelper.cs @@ -1,12 +1,13 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using Benchmarks.Logs; using Microsoft.Extensions.Logging; using OpenTelemetry.Logs; namespace Benchmarks.Helper; -internal class LogRecordHelper +internal static class LogRecordHelper { internal static LogRecord CreateTestLogRecord() { @@ -18,7 +19,7 @@ internal static LogRecord CreateTestLogRecord() })); var logger = factory.CreateLogger("TestLogger"); - logger.LogInformation("Hello from {Food} {Price}.", "artichoke", 3.99); + logger.HelloFrom("artichoke", 3.99); return items[0]; } } diff --git a/test/Benchmarks/Helper/TestExporter.cs b/test/Benchmarks/Helper/TestExporter.cs index 3d2b0038f8d..9aba87deec2 100644 --- a/test/Benchmarks/Helper/TestExporter.cs +++ b/test/Benchmarks/Helper/TestExporter.cs @@ -3,7 +3,7 @@ namespace OpenTelemetry.Tests; -internal class TestExporter : BaseExporter +internal sealed class TestExporter : BaseExporter where T : class { private readonly Action> processBatchAction; diff --git a/test/Benchmarks/Logs/LogBenchmarks.cs b/test/Benchmarks/Logs/LogBenchmarks.cs index 915c115852a..0ba40f0a8cc 100644 --- a/test/Benchmarks/Logs/LogBenchmarks.cs +++ b/test/Benchmarks/Logs/LogBenchmarks.cs @@ -32,7 +32,7 @@ namespace Benchmarks.Logs; public class LogBenchmarks { private const double FoodPrice = 2.99; - private static readonly string FoodName = "tomato"; + private const string FoodName = "tomato"; private readonly ILogger loggerWithNoListener; private readonly ILogger loggerWithOneProcessor; @@ -95,13 +95,19 @@ public void GlobalCleanup() [Benchmark] public void NoListenerStringInterpolation() { +#pragma warning disable CA2254 // Template should be a static expression +#pragma warning disable CA1848 // Use the LoggerMessage delegates this.loggerWithNoListener.LogInformation($"Hello from {FoodName} {FoodPrice}."); +#pragma warning restore CA1848 // Use the LoggerMessage delegates +#pragma warning restore CA2254 // Template should be a static expression } [Benchmark] public void NoListenerExtensionMethod() { - this.loggerWithNoListener.LogInformation("Hello from {name} {price}.", FoodName, FoodPrice); +#pragma warning disable CA1848 // Use the LoggerMessage delegates + this.loggerWithNoListener.LogInformation("Hello from {Name} {Price}.", FoodName, FoodPrice); +#pragma warning restore CA1848 // Use the LoggerMessage delegates } [Benchmark] @@ -185,11 +191,11 @@ public void ThreeProcessors() companyName: "Contoso Fresh Vegetables, Inc."); } - internal class NoopLogProcessor : BaseProcessor + internal sealed class NoopLogProcessor : BaseProcessor { } - internal class NoopExporter : BaseExporter + internal sealed class NoopExporter : BaseExporter { public override ExportResult Export(in Batch batch) { diff --git a/test/Benchmarks/Logs/LogScopeBenchmarks.cs b/test/Benchmarks/Logs/LogScopeBenchmarks.cs index b8bedab2319..ffea0671d9e 100644 --- a/test/Benchmarks/Logs/LogScopeBenchmarks.cs +++ b/test/Benchmarks/Logs/LogScopeBenchmarks.cs @@ -39,24 +39,21 @@ public class LogScopeBenchmarks public LogScopeBenchmarks() { this.scopeProvider.Push(new ReadOnlyCollection>( - new List> - { + [ new("item1", "value1"), new("item2", "value2"), - })); + ])); this.scopeProvider.Push(new ReadOnlyCollection>( - new List> - { + [ new("item3", "value3"), - })); + ])); this.scopeProvider.Push(new ReadOnlyCollection>( - new List> - { + [ new("item4", "value4"), new("item5", "value5"), - })); + ])); #pragma warning disable CS0618 // Type or member is obsolete this.logRecord = new LogRecord( diff --git a/test/Benchmarks/Logs/LoggerExtensions.cs b/test/Benchmarks/Logs/LoggerExtensions.cs index 47cd5962435..caf13626284 100644 --- a/test/Benchmarks/Logs/LoggerExtensions.cs +++ b/test/Benchmarks/Logs/LoggerExtensions.cs @@ -15,4 +15,7 @@ public static partial void FoodRecallNotice( string productType, string recallReasonDescription, string companyName); + + [LoggerMessage(LogLevel.Information, "Hello from {Food} {Price}.")] + public static partial void HelloFrom(this ILogger logger, string food, double price); } diff --git a/test/Benchmarks/Metrics/Base2ExponentialHistogramBenchmarks.cs b/test/Benchmarks/Metrics/Base2ExponentialHistogramBenchmarks.cs index 43a2dfdc4f9..c37c9517cd5 100644 --- a/test/Benchmarks/Metrics/Base2ExponentialHistogramBenchmarks.cs +++ b/test/Benchmarks/Metrics/Base2ExponentialHistogramBenchmarks.cs @@ -27,7 +27,9 @@ .NET SDK 8.0.100 namespace Benchmarks.Metrics; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public class Base2ExponentialHistogramBenchmarks +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { private const int MaxValue = 10000; private readonly Random random = new(); @@ -64,6 +66,7 @@ public void Cleanup() [Benchmark] public void HistogramHotPath() { +#pragma warning disable CA5394 // Do not use insecure randomness this.histogram!.Record(this.random.Next(MaxValue)); } @@ -111,5 +114,6 @@ public void HistogramWith7LabelsHotPath() { "DimName7", this.dimensionValues[this.random.Next(0, 1)] }, }; this.histogram!.Record(this.random.Next(MaxValue), tags); +#pragma warning restore CA5394 // Do not use insecure randomness } } diff --git a/test/Benchmarks/Metrics/Base2ExponentialHistogramMapToIndexBenchmarks.cs b/test/Benchmarks/Metrics/Base2ExponentialHistogramMapToIndexBenchmarks.cs index 5207716c424..71d0ee64174 100644 --- a/test/Benchmarks/Metrics/Base2ExponentialHistogramMapToIndexBenchmarks.cs +++ b/test/Benchmarks/Metrics/Base2ExponentialHistogramMapToIndexBenchmarks.cs @@ -39,6 +39,8 @@ public void Setup() [Benchmark] public void MapToIndex() { +#pragma warning disable CA5394 // Do not use insecure randomness this.exponentialHistogram!.MapToIndex(this.random.Next(MaxValue)); +#pragma warning restore CA5394 // Do not use insecure randomness } } diff --git a/test/Benchmarks/Metrics/Base2ExponentialHistogramScaleBenchmarks.cs b/test/Benchmarks/Metrics/Base2ExponentialHistogramScaleBenchmarks.cs index 4c7d01f925c..8e72b079ff1 100644 --- a/test/Benchmarks/Metrics/Base2ExponentialHistogramScaleBenchmarks.cs +++ b/test/Benchmarks/Metrics/Base2ExponentialHistogramScaleBenchmarks.cs @@ -24,7 +24,9 @@ .NET SDK 8.0.100 namespace Benchmarks.Metrics; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public class Base2ExponentialHistogramScaleBenchmarks +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { private const int MaxValue = 10000; private readonly Random random = new(); @@ -72,6 +74,8 @@ public void Cleanup() [Benchmark] public void HistogramHotPath() { +#pragma warning disable CA5394 // Do not use insecure randomness this.histogram!.Record(this.random.Next(MaxValue)); +#pragma warning restore CA5394 // Do not use insecure randomness } } diff --git a/test/Benchmarks/Metrics/ExemplarBenchmarks.cs b/test/Benchmarks/Metrics/ExemplarBenchmarks.cs index d9369d26e3e..e2ac358ac7e 100644 --- a/test/Benchmarks/Metrics/ExemplarBenchmarks.cs +++ b/test/Benchmarks/Metrics/ExemplarBenchmarks.cs @@ -38,10 +38,12 @@ .NET SDK 8.0.200 namespace Benchmarks.Metrics; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public class ExemplarBenchmarks +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { private static readonly ThreadLocal ThreadLocalRandom = new(() => new Random()); - private readonly string[] dimensionValues = new string[] { "DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10" }; + private readonly string[] dimensionValues = ["DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10"]; private Histogram? histogramWithoutTagReduction; private Histogram? histogramWithTagReduction; private Counter? counterWithoutTagReduction; @@ -82,11 +84,15 @@ public void Setup() .SetExemplarFilter(exemplarFilter) .AddView(i => { +#if NET + if (i.Name.Contains("WithTagReduction", StringComparison.Ordinal)) +#else if (i.Name.Contains("WithTagReduction")) +#endif { return new MetricStreamConfiguration() { - TagKeys = new string[] { "DimName1", "DimName2", "DimName3" }, + TagKeys = ["DimName1", "DimName2", "DimName3"], ExemplarReservoirFactory = CreateExemplarReservoir, }; } @@ -125,6 +131,7 @@ public void HistogramNoTagReduction() var random = ThreadLocalRandom.Value!; var tags = new TagList { +#pragma warning disable CA5394 // Do not use insecure randomness { "DimName1", this.dimensionValues[random.Next(0, 2)] }, { "DimName2", this.dimensionValues[random.Next(0, 2)] }, { "DimName3", this.dimensionValues[random.Next(0, 5)] }, @@ -181,6 +188,7 @@ public void CounterWithTagReduction() }; this.counterWithTagReduction!.Add(random.Next(1000), tags); +#pragma warning restore CA5394 // Do not use insecure randomness } private sealed class HighValueExemplarReservoir : FixedSizeExemplarReservoir diff --git a/test/Benchmarks/Metrics/HistogramBenchmarks.cs b/test/Benchmarks/Metrics/HistogramBenchmarks.cs index e30ae2ffad9..d5f92fdae64 100644 --- a/test/Benchmarks/Metrics/HistogramBenchmarks.cs +++ b/test/Benchmarks/Metrics/HistogramBenchmarks.cs @@ -42,11 +42,13 @@ .NET SDK 8.0.100 namespace Benchmarks.Metrics; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public class HistogramBenchmarks +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { private const int MaxValue = 10000; private readonly Random random = new(); - private readonly string[] dimensionValues = new string[] { "DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10" }; + private readonly string[] dimensionValues = ["DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10"]; private Histogram? histogram; private MeterProvider? meterProvider; private Meter? meter; @@ -91,6 +93,7 @@ public void Cleanup() [Benchmark] public void HistogramHotPath() { +#pragma warning disable CA5394 // Do not use insecure randomness this.histogram!.Record(this.random.Next(MaxValue)); } @@ -138,5 +141,6 @@ public void HistogramWith7LabelsHotPath() { "DimName7", this.dimensionValues[this.random.Next(0, 1)] }, }; this.histogram!.Record(this.random.Next(MaxValue), tags); +#pragma warning restore CA5394 // Do not use insecure randomness } } diff --git a/test/Benchmarks/Metrics/MetricCollectBenchmarks.cs b/test/Benchmarks/Metrics/MetricCollectBenchmarks.cs index ac3f0cf20cb..cd921b99f11 100644 --- a/test/Benchmarks/Metrics/MetricCollectBenchmarks.cs +++ b/test/Benchmarks/Metrics/MetricCollectBenchmarks.cs @@ -23,9 +23,11 @@ .NET SDK 8.0.100 namespace Benchmarks.Metrics; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public class MetricCollectBenchmarks +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { - private readonly string[] dimensionValues = new string[] { "DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10" }; + private readonly string[] dimensionValues = ["DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10"]; // TODO: Confirm if this needs to be thread-safe private readonly Random random = new(); @@ -42,7 +44,9 @@ public class MetricCollectBenchmarks [GlobalSetup] public void Setup() { +#pragma warning disable CA2000 // Dispose objects before losing scope var metricExporter = new TestExporter(ProcessExport); +#pragma warning restore CA2000 // Dispose objects before losing scope void ProcessExport(Batch batch) { double sum = 0; @@ -86,9 +90,11 @@ void ProcessExport(Batch batch) { while (!this.token.IsCancellationRequested) { +#pragma warning disable CA5394 // Do not use insecure randomness var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 10)]); var tag2 = new KeyValuePair("DimName2", this.dimensionValues[this.random.Next(0, 10)]); var tag3 = new KeyValuePair("DimName3", this.dimensionValues[this.random.Next(0, 10)]); +#pragma warning restore CA5394 // Do not use insecure randomness this.counter.Add(100.00, tag1, tag2, tag3); } }); diff --git a/test/Benchmarks/Metrics/MetricsBenchmarks.cs b/test/Benchmarks/Metrics/MetricsBenchmarks.cs index 117bf59a5e1..0adc5895a3b 100644 --- a/test/Benchmarks/Metrics/MetricsBenchmarks.cs +++ b/test/Benchmarks/Metrics/MetricsBenchmarks.cs @@ -56,10 +56,12 @@ .NET SDK 8.0.101 namespace Benchmarks.Metrics; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public class MetricsBenchmarks +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { private readonly Random random = new(); - private readonly string[] dimensionValues = new string[] { "DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10" }; + private readonly string[] dimensionValues = ["DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10"]; private Counter? counter; private MeterProvider? meterProvider; private Meter? meter; @@ -101,6 +103,7 @@ public void CounterHotPath() [Benchmark] public void CounterWith1LabelsHotPath() { +#pragma warning disable CA5394 // Do not use insecure randomness var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 10)]); this.counter!.Add(100, tag1); } @@ -290,6 +293,7 @@ public void CounterWith9LabelsHotPathUsingTagList() { "DimName7", this.dimensionValues[this.random.Next(0, 5)] }, { "DimName8", this.dimensionValues[this.random.Next(0, 5)] }, { "DimName9", this.dimensionValues[this.random.Next(0, 5)] }, +#pragma warning restore CA5394 // Do not use insecure randomness }; this.counter!.Add(100, tags); } diff --git a/test/Benchmarks/Metrics/MetricsViewBenchmarks.cs b/test/Benchmarks/Metrics/MetricsViewBenchmarks.cs index ce9cb40b6c5..fc08872018e 100644 --- a/test/Benchmarks/Metrics/MetricsViewBenchmarks.cs +++ b/test/Benchmarks/Metrics/MetricsViewBenchmarks.cs @@ -27,11 +27,12 @@ .NET SDK 8.0.100 namespace Benchmarks.Metrics; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public class MetricsViewBenchmarks +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { private static readonly ThreadLocal ThreadLocalRandom = new(() => new Random()); - private static readonly string[] DimensionValues = new string[] { "DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10" }; - private static readonly int DimensionsValuesLength = DimensionValues.Length; + private static readonly string[] DimensionValues = ["DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10"]; private List? metrics; private Counter? counter; private MeterProvider? meterProvider; @@ -83,7 +84,7 @@ public void Setup() { this.meter = new Meter(Utils.GetCurrentMethodName()); this.counter = this.meter.CreateCounter("counter"); - this.metrics = new List(); + this.metrics = []; if (this.ViewConfig == ViewConfiguration.NoView) { @@ -96,7 +97,7 @@ public void Setup() { this.meterProvider = Sdk.CreateMeterProviderBuilder() .AddMeter(this.meter.Name) - .AddView("nomatch", new MetricStreamConfiguration() { TagKeys = new string[] { "DimName1", "DimName2", "DimName3" } }) + .AddView("nomatch", new MetricStreamConfiguration() { TagKeys = ["DimName1", "DimName2", "DimName3"] }) .AddInMemoryExporter(this.metrics) .Build(); } @@ -104,7 +105,7 @@ public void Setup() { this.meterProvider = Sdk.CreateMeterProviderBuilder() .AddMeter(this.meter.Name) - .AddView(this.counter.Name, new MetricStreamConfiguration() { TagKeys = new string[] { "DimName1", "DimName2", "DimName3" } }) + .AddView(this.counter.Name, new MetricStreamConfiguration() { TagKeys = ["DimName1", "DimName2", "DimName3"] }) .AddInMemoryExporter(this.metrics) .Build(); } @@ -120,7 +121,7 @@ public void Setup() { this.meterProvider = Sdk.CreateMeterProviderBuilder() .AddMeter(this.meter.Name) - .AddView(this.counter.Name, new MetricStreamConfiguration() { TagKeys = Array.Empty() }) + .AddView(this.counter.Name, new MetricStreamConfiguration() { TagKeys = [] }) .AddInMemoryExporter(this.metrics) .Build(); } @@ -139,11 +140,13 @@ public void CounterHotPath() var random = ThreadLocalRandom.Value!; var tags = new TagList { +#pragma warning disable CA5394 // Do not use insecure randomness { "DimName1", DimensionValues[random.Next(0, 2)] }, { "DimName2", DimensionValues[random.Next(0, 2)] }, { "DimName3", DimensionValues[random.Next(0, 5)] }, { "DimName4", DimensionValues[random.Next(0, 5)] }, { "DimName5", DimensionValues[random.Next(0, 10)] }, +#pragma warning restore CA5394 // Do not use insecure randomness }; this.counter?.Add( diff --git a/test/Benchmarks/SuppressInstrumentationScopeBenchmarks.cs b/test/Benchmarks/SuppressInstrumentationScopeBenchmarks.cs index b99b20c23d5..a757ef697eb 100644 --- a/test/Benchmarks/SuppressInstrumentationScopeBenchmarks.cs +++ b/test/Benchmarks/SuppressInstrumentationScopeBenchmarks.cs @@ -8,7 +8,9 @@ namespace OpenTelemetry.Benchmarks; public class SuppressInstrumentationScopeBenchmarks { [Benchmark] +#pragma warning disable CA1822 // Mark members as static public void Begin() +#pragma warning restore CA1822 // Mark members as static { using var scope1 = SuppressInstrumentationScope.Begin(); @@ -18,7 +20,9 @@ public void Begin() } [Benchmark] +#pragma warning disable CA1822 // Mark members as static public void Enter() +#pragma warning restore CA1822 // Mark members as static { SuppressInstrumentationScope.Enter(); diff --git a/test/Benchmarks/Trace/ActivityCreationBenchmarks.cs b/test/Benchmarks/Trace/ActivityCreationBenchmarks.cs index 2ec79e4ef36..f85c919a2ee 100644 --- a/test/Benchmarks/Trace/ActivityCreationBenchmarks.cs +++ b/test/Benchmarks/Trace/ActivityCreationBenchmarks.cs @@ -25,7 +25,9 @@ .NET SDK 8.0.100 namespace Benchmarks.Trace; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public class ActivityCreationBenchmarks +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { private readonly ActivitySource benchmarkSource = new("Benchmark"); private readonly ActivityContext parentCtx = new(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None); @@ -36,7 +38,9 @@ public void GlobalSetup() { this.tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource("BenchMark") +#pragma warning disable CA2000 // Dispose objects before losing scope .AddProcessor(new NoopActivityProcessor()) +#pragma warning restore CA2000 // Dispose objects before losing scope .Build(); } @@ -59,7 +63,7 @@ public void GlobalCleanup() [Benchmark] public void CreateActivity_WithAddTags_NoopProcessor() => ActivityCreationScenarios.CreateActivityWithAddTags(this.benchmarkSource); - internal class NoopActivityProcessor : BaseProcessor + internal sealed class NoopActivityProcessor : BaseProcessor { } } diff --git a/test/Benchmarks/Trace/SamplerBenchmarks.cs b/test/Benchmarks/Trace/SamplerBenchmarks.cs index a835daea375..819de8d466c 100644 --- a/test/Benchmarks/Trace/SamplerBenchmarks.cs +++ b/test/Benchmarks/Trace/SamplerBenchmarks.cs @@ -23,15 +23,26 @@ .NET SDK 8.0.100 namespace Benchmarks.Trace; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public class SamplerBenchmarks +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { - private readonly ActivitySource sourceNotModifyTracestate = new("SamplerNotModifyingTraceState"); - private readonly ActivitySource sourceModifyTracestate = new("SamplerModifyingTraceState"); - private readonly ActivitySource sourceAppendTracestate = new("SamplerAppendingTraceState"); - private readonly ActivityContext parentContext = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded, "a=b", true); - - public SamplerBenchmarks() + private ActivitySource? sourceNotModifyTracestate; + private ActivitySource? sourceModifyTracestate; + private ActivitySource? sourceAppendTracestate; + private ActivityContext parentContext; + private TracerProvider? tracerProviderNotModifyTracestate; + private TracerProvider? tracerProviderModifyTracestate; + private TracerProvider? tracerProviderAppendTracestate; + + [GlobalSetup] + public void Setup() { + this.sourceNotModifyTracestate = new("SamplerNotModifyingTraceState"); + this.sourceModifyTracestate = new("SamplerModifyingTraceState"); + this.sourceAppendTracestate = new("SamplerAppendingTraceState"); + this.parentContext = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded, "a=b", true); + var testSamplerNotModifyTracestate = new TestSampler { SamplingAction = (samplingParams) => @@ -56,41 +67,52 @@ public SamplerBenchmarks() }, }; - Sdk.CreateTracerProviderBuilder() + this.tracerProviderNotModifyTracestate = Sdk.CreateTracerProviderBuilder() .SetSampler(testSamplerNotModifyTracestate) .AddSource(this.sourceNotModifyTracestate.Name) .Build(); - Sdk.CreateTracerProviderBuilder() + this.tracerProviderModifyTracestate = Sdk.CreateTracerProviderBuilder() .SetSampler(testSamplerModifyTracestate) .AddSource(this.sourceModifyTracestate.Name) .Build(); - Sdk.CreateTracerProviderBuilder() + this.tracerProviderAppendTracestate = Sdk.CreateTracerProviderBuilder() .SetSampler(testSamplerAppendTracestate) .AddSource(this.sourceAppendTracestate.Name) .Build(); } + [GlobalCleanup] + public void Cleanup() + { + this.sourceNotModifyTracestate?.Dispose(); + this.sourceModifyTracestate?.Dispose(); + this.sourceAppendTracestate?.Dispose(); + this.tracerProviderNotModifyTracestate?.Dispose(); + this.tracerProviderModifyTracestate?.Dispose(); + this.tracerProviderAppendTracestate?.Dispose(); + } + [Benchmark] public void SamplerNotModifyingTraceState() { - using var activity = this.sourceNotModifyTracestate.StartActivity("Benchmark", ActivityKind.Server, this.parentContext); + using var activity = this.sourceNotModifyTracestate!.StartActivity("Benchmark", ActivityKind.Server, this.parentContext); } [Benchmark] public void SamplerModifyingTraceState() { - using var activity = this.sourceModifyTracestate.StartActivity("Benchmark", ActivityKind.Server, this.parentContext); + using var activity = this.sourceModifyTracestate!.StartActivity("Benchmark", ActivityKind.Server, this.parentContext); } [Benchmark] public void SamplerAppendingTraceState() { - using var activity = this.sourceAppendTracestate.StartActivity("Benchmark", ActivityKind.Server, this.parentContext); + using var activity = this.sourceAppendTracestate!.StartActivity("Benchmark", ActivityKind.Server, this.parentContext); } - internal class TestSampler : Sampler + internal sealed class TestSampler : Sampler { public Func? SamplingAction { get; set; } diff --git a/test/Benchmarks/Trace/TraceBenchmarks.cs b/test/Benchmarks/Trace/TraceBenchmarks.cs index e85e52cbd5a..d9e10bda977 100644 --- a/test/Benchmarks/Trace/TraceBenchmarks.cs +++ b/test/Benchmarks/Trace/TraceBenchmarks.cs @@ -31,60 +31,92 @@ .NET SDK 8.0.100 namespace Benchmarks.Trace; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup public class TraceBenchmarks +#pragma warning restore CA1001 // Types that own disposable fields should be disposable - handled by GlobalCleanup { - private readonly ActivitySource sourceWithNoListener = new("Benchmark.NoListener"); - private readonly ActivitySource sourceWithPropagationDataListner = new("Benchmark.PropagationDataListner"); - private readonly ActivitySource sourceWithAllDataListner = new("Benchmark.AllDataListner"); - private readonly ActivitySource sourceWithAllDataAndRecordedListner = new("Benchmark.AllDataAndRecordedListner"); - private readonly ActivitySource sourceWithOneProcessor = new("Benchmark.OneProcessor"); - private readonly ActivitySource sourceWithTwoProcessors = new("Benchmark.TwoProcessors"); - private readonly ActivitySource sourceWithThreeProcessors = new("Benchmark.ThreeProcessors"); - private readonly ActivitySource sourceWithOneLegacyActivityOperationNameSubscription = new("Benchmark.OneInstrumentation"); - private readonly ActivitySource sourceWithTwoLegacyActivityOperationNameSubscriptions = new("Benchmark.TwoInstrumentations"); - - public TraceBenchmarks() + private ActivitySource? sourceWithNoListener; + private ActivitySource? sourceWithPropagationDataListner; + private ActivitySource? sourceWithAllDataListner; + private ActivitySource? sourceWithAllDataAndRecordedListner; + private ActivitySource? sourceWithOneProcessor; + private ActivitySource? sourceWithTwoProcessors; + private ActivitySource? sourceWithThreeProcessors; + private ActivitySource? sourceWithOneLegacyActivityOperationNameSubscription; + private ActivitySource? sourceWithTwoLegacyActivityOperationNameSubscriptions; + + private TracerProvider? tracerProvierWithOneProcessor; + private TracerProvider? tracerProvierWithTwoProcessors; + private TracerProvider? tracerProvierWithThreeProcessors; + private TracerProvider? tracerProvierWithOneLegacyActivityOperationNameSubscription; + private TracerProvider? tracerProvierWithTwoLegacyActivityOperationNameSubscriptions; + private TracerProvider? tracerProvierWithExactMatchLegacyActivityListner; + private TracerProvider? tracerProvierWithWildcardMatchLegacyActivityListner; + + private ActivityListener? activityListenerPropagationData; + private ActivityListener? activityListenerAllData; + private ActivityListener? activityListenerAllDataAndRecordedData; + + [GlobalSetup] + public void Setup() { Activity.DefaultIdFormat = ActivityIdFormat.W3C; - ActivitySource.AddActivityListener(new ActivityListener + this.sourceWithNoListener = new("Benchmark.NoListener"); + this.sourceWithPropagationDataListner = new("Benchmark.PropagationDataListner"); + this.sourceWithAllDataListner = new("Benchmark.AllDataListner"); + this.sourceWithAllDataAndRecordedListner = new("Benchmark.AllDataAndRecordedListner"); + this.sourceWithOneProcessor = new("Benchmark.OneProcessor"); + this.sourceWithTwoProcessors = new("Benchmark.TwoProcessors"); + this.sourceWithThreeProcessors = new("Benchmark.ThreeProcessors"); + this.sourceWithOneLegacyActivityOperationNameSubscription = new("Benchmark.OneInstrumentation"); + this.sourceWithTwoLegacyActivityOperationNameSubscriptions = new("Benchmark.TwoInstrumentations"); + + this.activityListenerPropagationData = new ActivityListener { ActivityStarted = null, ActivityStopped = null, ShouldListenTo = (activitySource) => activitySource.Name == this.sourceWithPropagationDataListner.Name, Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.PropagationData, - }); + }; - ActivitySource.AddActivityListener(new ActivityListener + ActivitySource.AddActivityListener(this.activityListenerPropagationData); + + this.activityListenerAllData = new ActivityListener { ActivityStarted = null, ActivityStopped = null, ShouldListenTo = (activitySource) => activitySource.Name == this.sourceWithAllDataListner.Name, Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllData, - }); + }; + + ActivitySource.AddActivityListener(this.activityListenerAllData); - ActivitySource.AddActivityListener(new ActivityListener + this.activityListenerAllDataAndRecordedData = new ActivityListener { ActivityStarted = null, ActivityStopped = null, ShouldListenTo = (activitySource) => activitySource.Name == this.sourceWithAllDataAndRecordedListner.Name, Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllDataAndRecorded, - }); + }; + + ActivitySource.AddActivityListener(this.activityListenerAllDataAndRecordedData); - Sdk.CreateTracerProviderBuilder() + this.tracerProvierWithOneProcessor = Sdk.CreateTracerProviderBuilder() .SetSampler(new AlwaysOnSampler()) .AddSource(this.sourceWithOneProcessor.Name) +#pragma warning disable CA2000 // Dispose objects before losing scope .AddProcessor(new DummyActivityProcessor()) .Build(); - Sdk.CreateTracerProviderBuilder() + this.tracerProvierWithTwoProcessors = Sdk.CreateTracerProviderBuilder() .SetSampler(new AlwaysOnSampler()) .AddSource(this.sourceWithTwoProcessors.Name) .AddProcessor(new DummyActivityProcessor()) .AddProcessor(new DummyActivityProcessor()) .Build(); - Sdk.CreateTracerProviderBuilder() + this.tracerProvierWithThreeProcessors = Sdk.CreateTracerProviderBuilder() .SetSampler(new AlwaysOnSampler()) .AddSource(this.sourceWithThreeProcessors.Name) .AddProcessor(new DummyActivityProcessor()) @@ -92,14 +124,14 @@ public TraceBenchmarks() .AddProcessor(new DummyActivityProcessor()) .Build(); - Sdk.CreateTracerProviderBuilder() + this.tracerProvierWithOneLegacyActivityOperationNameSubscription = Sdk.CreateTracerProviderBuilder() .SetSampler(new AlwaysOnSampler()) .AddSource(this.sourceWithOneLegacyActivityOperationNameSubscription.Name) .AddLegacySource("TestOperationName") .AddProcessor(new DummyActivityProcessor()) .Build(); - Sdk.CreateTracerProviderBuilder() + this.tracerProvierWithTwoLegacyActivityOperationNameSubscriptions = Sdk.CreateTracerProviderBuilder() .SetSampler(new AlwaysOnSampler()) .AddSource(this.sourceWithTwoLegacyActivityOperationNameSubscriptions.Name) .AddLegacySource("TestOperationName1") @@ -107,90 +139,121 @@ public TraceBenchmarks() .AddProcessor(new DummyActivityProcessor()) .Build(); - Sdk.CreateTracerProviderBuilder() + this.tracerProvierWithExactMatchLegacyActivityListner = Sdk.CreateTracerProviderBuilder() .SetSampler(new AlwaysOnSampler()) .AddLegacySource("ExactMatch.OperationName1") .AddProcessor(new DummyActivityProcessor()) .Build(); - Sdk.CreateTracerProviderBuilder() + this.tracerProvierWithWildcardMatchLegacyActivityListner = Sdk.CreateTracerProviderBuilder() .SetSampler(new AlwaysOnSampler()) .AddLegacySource("WildcardMatch.*") .AddProcessor(new DummyActivityProcessor()) +#pragma warning restore CA2000 // Dispose objects before losing scope .Build(); } + [GlobalCleanup] + public void Cleanup() + { + this.sourceWithNoListener?.Dispose(); + this.sourceWithPropagationDataListner?.Dispose(); + this.sourceWithAllDataListner?.Dispose(); + this.sourceWithAllDataAndRecordedListner?.Dispose(); + this.sourceWithOneProcessor?.Dispose(); + this.sourceWithTwoProcessors?.Dispose(); + this.sourceWithThreeProcessors?.Dispose(); + this.sourceWithOneLegacyActivityOperationNameSubscription?.Dispose(); + this.sourceWithTwoLegacyActivityOperationNameSubscriptions?.Dispose(); + + this.tracerProvierWithOneProcessor?.Dispose(); + this.tracerProvierWithTwoProcessors?.Dispose(); + this.tracerProvierWithThreeProcessors?.Dispose(); + this.tracerProvierWithOneLegacyActivityOperationNameSubscription?.Dispose(); + this.tracerProvierWithTwoLegacyActivityOperationNameSubscriptions?.Dispose(); + this.tracerProvierWithExactMatchLegacyActivityListner?.Dispose(); + this.tracerProvierWithWildcardMatchLegacyActivityListner?.Dispose(); + + this.activityListenerPropagationData?.Dispose(); + this.activityListenerAllData?.Dispose(); + this.activityListenerAllDataAndRecordedData?.Dispose(); + } + [Benchmark] public void NoListener() { // this activity won't be created as there is no listener - using var activity = this.sourceWithNoListener.StartActivity("Benchmark"); + using var activity = this.sourceWithNoListener!.StartActivity("Benchmark"); } [Benchmark] public void PropagationDataListner() { // this activity will be created and feed into an ActivityListener that simply drops everything on the floor - using var activity = this.sourceWithPropagationDataListner.StartActivity("Benchmark"); + using var activity = this.sourceWithPropagationDataListner!.StartActivity("Benchmark"); } [Benchmark] public void AllDataListner() { // this activity will be created and feed into an ActivityListener that simply drops everything on the floor - using var activity = this.sourceWithAllDataListner.StartActivity("Benchmark"); + using var activity = this.sourceWithAllDataListner!.StartActivity("Benchmark"); } [Benchmark] public void AllDataAndRecordedListner() { // this activity will be created and feed into an ActivityListener that simply drops everything on the floor - using var activity = this.sourceWithAllDataAndRecordedListner.StartActivity("Benchmark"); + using var activity = this.sourceWithAllDataAndRecordedListner!.StartActivity("Benchmark"); } [Benchmark] public void OneProcessor() { - using var activity = this.sourceWithOneProcessor.StartActivity("Benchmark"); + using var activity = this.sourceWithOneProcessor!.StartActivity("Benchmark"); } [Benchmark] public void TwoProcessors() { - using var activity = this.sourceWithTwoProcessors.StartActivity("Benchmark"); + using var activity = this.sourceWithTwoProcessors!.StartActivity("Benchmark"); } [Benchmark] public void ThreeProcessors() { - using var activity = this.sourceWithThreeProcessors.StartActivity("Benchmark"); + using var activity = this.sourceWithThreeProcessors!.StartActivity("Benchmark"); } [Benchmark] public void OneInstrumentation() { - using var activity = this.sourceWithOneLegacyActivityOperationNameSubscription.StartActivity("Benchmark"); + using var activity = this.sourceWithOneLegacyActivityOperationNameSubscription!.StartActivity("Benchmark"); } [Benchmark] public void TwoInstrumentations() { - using var activity = this.sourceWithTwoLegacyActivityOperationNameSubscriptions.StartActivity("Benchmark"); + using var activity = this.sourceWithTwoLegacyActivityOperationNameSubscriptions!.StartActivity("Benchmark"); } [Benchmark] +#pragma warning disable CA1822 // Mark members as static public void LegacyActivity_ExactMatchMode() +#pragma warning restore CA1822 // Mark members as static { - using var activity = new Activity("ExactMatch.OperationName1").Start(); + using var activity = new Activity("ExactMatch.OperationName1"); + activity.Start(); } [Benchmark] +#pragma warning disable CA1822 // Mark members as static public void LegacyActivity_WildcardMatchMode() +#pragma warning restore CA1822 // Mark members as static { - using var activity = new Activity("WildcardMatch.OperationName1").Start(); + using var activity = new Activity("WildcardMatch.OperationName1"); + activity.Start(); } - internal class DummyActivityProcessor : BaseProcessor - { - } + internal sealed class DummyActivityProcessor : BaseProcessor; } diff --git a/test/Benchmarks/Trace/TraceShimBenchmarks.cs b/test/Benchmarks/Trace/TraceShimBenchmarks.cs index b9d11939936..2b0526fffd8 100644 --- a/test/Benchmarks/Trace/TraceShimBenchmarks.cs +++ b/test/Benchmarks/Trace/TraceShimBenchmarks.cs @@ -38,6 +38,7 @@ public TraceShimBenchmarks() Sdk.CreateTracerProviderBuilder() .SetSampler(new AlwaysOnSampler()) .AddSource("Benchmark.OneProcessor") +#pragma warning disable CA2000 // Dispose objects before losing scope .AddProcessor(new DummyActivityProcessor()) .Build(); @@ -54,6 +55,7 @@ public TraceShimBenchmarks() .AddProcessor(new DummyActivityProcessor()) .AddProcessor(new DummyActivityProcessor()) .AddProcessor(new DummyActivityProcessor()) +#pragma warning restore CA2000 // Dispose objects before losing scope .Build(); } @@ -82,7 +84,5 @@ public void ThreeProcessors() using var activity = this.tracerWithThreeProcessors.StartActiveSpan("Benchmark"); } - internal class DummyActivityProcessor : BaseProcessor - { - } + internal sealed class DummyActivityProcessor : BaseProcessor; } diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 032e1e27ec9..4c709d51681 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -1,3 +1,4 @@ + diff --git a/test/Directory.Build.targets b/test/Directory.Build.targets index d1457ef4608..55ac9b04c79 100644 --- a/test/Directory.Build.targets +++ b/test/Directory.Build.targets @@ -9,7 +9,7 @@ reference is needed to mitigate: https://github.com/advisories/GHSA-hh2w-p6rv-4g7w. Remove this if Coyote publishes a fixed version. --> - + diff --git a/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/Metrics/TestMeterProviderBuilder.cs b/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/Metrics/TestMeterProviderBuilder.cs index 424b271417a..922b8f61a20 100644 --- a/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/Metrics/TestMeterProviderBuilder.cs +++ b/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/Metrics/TestMeterProviderBuilder.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Api.ProviderBuilderExtensions.Tests; -public sealed class TestMeterProviderBuilder : MeterProviderBuilder, IMeterProviderBuilder, IDisposable +internal sealed class TestMeterProviderBuilder : MeterProviderBuilder, IMeterProviderBuilder, IDisposable { public TestMeterProviderBuilder() { @@ -17,9 +17,9 @@ public TestMeterProviderBuilder() public ServiceProvider? ServiceProvider { get; private set; } - public List Meters { get; } = new(); + public List Meters { get; } = []; - public List Instrumentation { get; } = new(); + public List Instrumentation { get; } = []; public MeterProvider? Provider { get; private set; } diff --git a/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/OpenTelemetry.Api.ProviderBuilderExtensions.Tests.csproj b/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/OpenTelemetry.Api.ProviderBuilderExtensions.Tests.csproj index de24f0131fe..95ac41acb81 100644 --- a/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/OpenTelemetry.Api.ProviderBuilderExtensions.Tests.csproj +++ b/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/OpenTelemetry.Api.ProviderBuilderExtensions.Tests.csproj @@ -1,4 +1,4 @@ - + Unit test project for OpenTelemetry .NET dependency injection extensions $(TargetFrameworksForTests) @@ -9,11 +9,6 @@ - - - - runtime; build; native; contentfiles; analyzers - diff --git a/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/ServiceCollectionExtensionsTests.cs b/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/ServiceCollectionExtensionsTests.cs index fa3fb4e13d9..ccbc88938d8 100644 --- a/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/ServiceCollectionExtensionsTests.cs +++ b/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/ServiceCollectionExtensionsTests.cs @@ -30,7 +30,7 @@ public void ConfigureOpenTelemetryTracerProvider(int numberOfCalls) using var serviceProvider = services.BuildServiceProvider(); - var registrations = serviceProvider.GetServices(); + var registrations = serviceProvider.GetServices().ToArray(); Assert.Equal(numberOfCalls, beforeServiceProviderInvocations); Assert.Equal(0, afterServiceProviderInvocations); @@ -43,7 +43,7 @@ public void ConfigureOpenTelemetryTracerProvider(int numberOfCalls) Assert.Equal(numberOfCalls, beforeServiceProviderInvocations); Assert.Equal(numberOfCalls, afterServiceProviderInvocations); - Assert.Equal(numberOfCalls * 2, registrations.Count()); + Assert.Equal(numberOfCalls * 2, registrations.Length); } [Theory] @@ -65,7 +65,7 @@ public void ConfigureOpenTelemetryMeterProvider(int numberOfCalls) using var serviceProvider = services.BuildServiceProvider(); - var registrations = serviceProvider.GetServices(); + var registrations = serviceProvider.GetServices().ToArray(); Assert.Equal(numberOfCalls, beforeServiceProviderInvocations); Assert.Equal(0, afterServiceProviderInvocations); @@ -78,7 +78,7 @@ public void ConfigureOpenTelemetryMeterProvider(int numberOfCalls) Assert.Equal(numberOfCalls, beforeServiceProviderInvocations); Assert.Equal(numberOfCalls, afterServiceProviderInvocations); - Assert.Equal(numberOfCalls * 2, registrations.Count()); + Assert.Equal(numberOfCalls * 2, registrations.Length); } [Theory] @@ -100,7 +100,7 @@ public void ConfigureOpenTelemetryLoggerProvider(int numberOfCalls) using var serviceProvider = services.BuildServiceProvider(); - var registrations = serviceProvider.GetServices(); + var registrations = serviceProvider.GetServices().ToArray(); Assert.Equal(numberOfCalls, beforeServiceProviderInvocations); Assert.Equal(0, afterServiceProviderInvocations); @@ -113,6 +113,6 @@ public void ConfigureOpenTelemetryLoggerProvider(int numberOfCalls) Assert.Equal(numberOfCalls, beforeServiceProviderInvocations); Assert.Equal(numberOfCalls, afterServiceProviderInvocations); - Assert.Equal(numberOfCalls * 2, registrations.Count()); + Assert.Equal(numberOfCalls * 2, registrations.Length); } } diff --git a/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/Trace/TestTracerProviderBuilder.cs b/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/Trace/TestTracerProviderBuilder.cs index db64a8549c2..89a8bc10ec7 100644 --- a/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/Trace/TestTracerProviderBuilder.cs +++ b/test/OpenTelemetry.Api.ProviderBuilderExtensions.Tests/Trace/TestTracerProviderBuilder.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Api.ProviderBuilderExtensions.Tests; -public sealed class TestTracerProviderBuilder : TracerProviderBuilder, ITracerProviderBuilder, IDisposable +internal sealed class TestTracerProviderBuilder : TracerProviderBuilder, ITracerProviderBuilder, IDisposable { public TestTracerProviderBuilder() { @@ -17,11 +17,11 @@ public TestTracerProviderBuilder() public ServiceProvider? ServiceProvider { get; private set; } - public List Sources { get; } = new(); + public List Sources { get; } = []; - public List LegacySources { get; } = new(); + public List LegacySources { get; } = []; - public List Instrumentation { get; } = new(); + public List Instrumentation { get; } = []; public TracerProvider? Provider { get; private set; } diff --git a/test/OpenTelemetry.Api.Tests/BaggageTests.cs b/test/OpenTelemetry.Api.Tests/BaggageTests.cs index 904cba58968..2c17b782f87 100644 --- a/test/OpenTelemetry.Api.Tests/BaggageTests.cs +++ b/test/OpenTelemetry.Api.Tests/BaggageTests.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using Xunit; namespace OpenTelemetry.Tests; @@ -39,8 +40,10 @@ public void SetAndGetTest() Assert.Equal(list, Baggage.GetBaggage(Baggage.Current)); Assert.Equal(V1, Baggage.GetBaggage(K1)); - Assert.Equal(V1, Baggage.GetBaggage(K1.ToLower())); - Assert.Equal(V1, Baggage.GetBaggage(K1.ToUpper())); +#pragma warning disable CA1308 // Normalize strings to uppercase + Assert.Equal(V1, Baggage.GetBaggage(K1.ToLower(CultureInfo.InvariantCulture))); +#pragma warning restore CA1308 // Normalize strings to uppercase + Assert.Equal(V1, Baggage.GetBaggage(K1.ToUpper(CultureInfo.InvariantCulture))); Assert.Null(Baggage.GetBaggage("NO_KEY")); Assert.Equal(V2, Baggage.Current.GetBaggage(K2)); @@ -166,7 +169,7 @@ public void EnumeratorTest() var tag2 = enumerator.Current; Assert.False(enumerator.MoveNext()); - Assert.Equal(list, new List> { tag1, tag2 }); + Assert.Equal(list, [tag1, tag2]); Baggage.ClearBaggage(); diff --git a/test/OpenTelemetry.Api.Tests/Context/Propagation/B3PropagatorTest.cs b/test/OpenTelemetry.Api.Tests/Context/Propagation/B3PropagatorTests.cs similarity index 97% rename from test/OpenTelemetry.Api.Tests/Context/Propagation/B3PropagatorTest.cs rename to test/OpenTelemetry.Api.Tests/Context/Propagation/B3PropagatorTests.cs index 4ff05249c52..e7f93d78848 100644 --- a/test/OpenTelemetry.Api.Tests/Context/Propagation/B3PropagatorTest.cs +++ b/test/OpenTelemetry.Api.Tests/Context/Propagation/B3PropagatorTests.cs @@ -7,7 +7,7 @@ namespace OpenTelemetry.Context.Propagation.Tests; -public class B3PropagatorTest +public class B3PropagatorTests { private const string TraceIdBase16 = "ff000000000000000000000000000041"; private const string TraceIdBase16EightBytes = "0000000000000041"; @@ -37,7 +37,7 @@ public class B3PropagatorTest private readonly ITestOutputHelper output; - public B3PropagatorTest(ITestOutputHelper output) + public B3PropagatorTests(ITestOutputHelper output) { this.output = output; } @@ -359,7 +359,7 @@ public void Fields_list() { ContainsExactly( this.b3propagator.Fields, - new List { B3Propagator.XB3TraceId, B3Propagator.XB3SpanId, B3Propagator.XB3ParentSpanId, B3Propagator.XB3Sampled, B3Propagator.XB3Flags }); + [B3Propagator.XB3TraceId, B3Propagator.XB3SpanId, B3Propagator.XB3ParentSpanId, B3Propagator.XB3Sampled, B3Propagator.XB3Flags]); } private static void ContainsExactly(ISet list, List items) @@ -371,7 +371,7 @@ private static void ContainsExactly(ISet list, List items) } } - private void ContainsExactly(IDictionary dict, IDictionary items) + private void ContainsExactly(Dictionary dict, Dictionary items) { foreach (var d in dict) { diff --git a/test/OpenTelemetry.Api.Tests/Context/Propagation/BaggagePropagatorTest.cs b/test/OpenTelemetry.Api.Tests/Context/Propagation/BaggagePropagatorTests.cs similarity index 99% rename from test/OpenTelemetry.Api.Tests/Context/Propagation/BaggagePropagatorTest.cs rename to test/OpenTelemetry.Api.Tests/Context/Propagation/BaggagePropagatorTests.cs index 6575d2949f1..13c21c28cec 100644 --- a/test/OpenTelemetry.Api.Tests/Context/Propagation/BaggagePropagatorTest.cs +++ b/test/OpenTelemetry.Api.Tests/Context/Propagation/BaggagePropagatorTests.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Context.Propagation.Tests; -public class BaggagePropagatorTest +public class BaggagePropagatorTests { private static readonly Func, string, IEnumerable> Getter = (d, k) => diff --git a/test/OpenTelemetry.Api.Tests/Context/Propagation/CompositePropagatorTest.cs b/test/OpenTelemetry.Api.Tests/Context/Propagation/CompositePropagatorTests.cs similarity index 95% rename from test/OpenTelemetry.Api.Tests/Context/Propagation/CompositePropagatorTest.cs rename to test/OpenTelemetry.Api.Tests/Context/Propagation/CompositePropagatorTests.cs index b70565affa8..105941ecd70 100644 --- a/test/OpenTelemetry.Api.Tests/Context/Propagation/CompositePropagatorTest.cs +++ b/test/OpenTelemetry.Api.Tests/Context/Propagation/CompositePropagatorTests.cs @@ -6,15 +6,15 @@ namespace OpenTelemetry.Context.Propagation.Tests; -public class CompositePropagatorTest +public class CompositePropagatorTests { - private static readonly string[] Empty = Array.Empty(); + private static readonly string[] Empty = []; private static readonly Func, string, IEnumerable> Getter = (headers, name) => { count++; if (headers.TryGetValue(name, out var value)) { - return new[] { value }; + return [value]; } return Empty; @@ -25,7 +25,7 @@ public class CompositePropagatorTest carrier[name] = value; }; - private static int count = 0; + private static int count; private readonly ActivityTraceId traceId = ActivityTraceId.CreateRandom(); private readonly ActivitySpanId spanId = ActivitySpanId.CreateRandom(); @@ -141,11 +141,11 @@ public void CompositePropagator_UsingSameTag() [Fact] public void CompositePropagator_ActivityContext_Baggage() { - var compositePropagator = new CompositeTextMapPropagator(new List - { + var compositePropagator = new CompositeTextMapPropagator( + [ new TraceContextPropagator(), new BaggagePropagator(), - }); + ]); var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null, isRemote: true); var baggage = new Dictionary { ["key1"] = "value1" }; @@ -159,12 +159,12 @@ public void CompositePropagator_ActivityContext_Baggage() var extractedContext = compositePropagator.Extract(default, carrier, Getter); Assert.Equal(propagationContextActivityOnly, extractedContext); - carrier = new Dictionary(); + carrier = []; compositePropagator.Inject(propagationContextBaggageOnly, carrier, Setter); extractedContext = compositePropagator.Extract(default, carrier, Getter); Assert.Equal(propagationContextBaggageOnly, extractedContext); - carrier = new Dictionary(); + carrier = []; compositePropagator.Inject(propagationContextBoth, carrier, Setter); extractedContext = compositePropagator.Extract(default, carrier, Getter); Assert.Equal(propagationContextBoth, extractedContext); diff --git a/test/OpenTelemetry.Api.Tests/Context/Propagation/PropagatorsTest.cs b/test/OpenTelemetry.Api.Tests/Context/Propagation/PropagatorsTests.cs similarity index 91% rename from test/OpenTelemetry.Api.Tests/Context/Propagation/PropagatorsTest.cs rename to test/OpenTelemetry.Api.Tests/Context/Propagation/PropagatorsTests.cs index b7993e0d848..a80574db7f8 100644 --- a/test/OpenTelemetry.Api.Tests/Context/Propagation/PropagatorsTest.cs +++ b/test/OpenTelemetry.Api.Tests/Context/Propagation/PropagatorsTests.cs @@ -5,9 +5,9 @@ namespace OpenTelemetry.Context.Propagation.Tests; -public class PropagatorsTest : IDisposable +public sealed class PropagatorsTests : IDisposable { - public PropagatorsTest() + public PropagatorsTests() { Propagators.Reset(); } diff --git a/test/OpenTelemetry.Api.Tests/Context/Propagation/TestPropagator.cs b/test/OpenTelemetry.Api.Tests/Context/Propagation/TestPropagator.cs index ec791743f56..ab652152473 100644 --- a/test/OpenTelemetry.Api.Tests/Context/Propagation/TestPropagator.cs +++ b/test/OpenTelemetry.Api.Tests/Context/Propagation/TestPropagator.cs @@ -5,14 +5,14 @@ namespace OpenTelemetry.Context.Propagation.Tests; -public class TestPropagator : TextMapPropagator +internal sealed class TestPropagator : TextMapPropagator { private readonly string idHeaderName; private readonly string stateHeaderName; private readonly bool defaultContext; - private int extractCount = 0; - private int injectCount = 0; + private int extractCount; + private int injectCount; public TestPropagator(string idHeaderName, string stateHeaderName, bool defaultContext = false) { diff --git a/test/OpenTelemetry.Api.Tests/Context/Propagation/TracestateUtilsTests.cs b/test/OpenTelemetry.Api.Tests/Context/Propagation/TracestateUtilsTests.cs index deb9fc9e8fb..ae9de6f0ff0 100644 --- a/test/OpenTelemetry.Api.Tests/Context/Propagation/TracestateUtilsTests.cs +++ b/test/OpenTelemetry.Api.Tests/Context/Propagation/TracestateUtilsTests.cs @@ -11,7 +11,7 @@ public class TracestateUtilsTests public void NullTracestate() { var tracestateEntries = new List>(); - Assert.False(TraceStateUtilsNew.AppendTraceState(null!, tracestateEntries)); + Assert.False(TraceStateUtils.AppendTraceState(null!, tracestateEntries)); Assert.Empty(tracestateEntries); } @@ -22,7 +22,7 @@ public void NullTracestate() public void EmptyTracestate(string tracestate) { var tracestateEntries = new List>(); - Assert.False(TraceStateUtilsNew.AppendTraceState(tracestate, tracestateEntries)); + Assert.False(TraceStateUtils.AppendTraceState(tracestate, tracestateEntries)); Assert.Empty(tracestateEntries); } @@ -39,7 +39,7 @@ public void EmptyTracestate(string tracestate) public void InvalidTracestate(string tracestate) { var tracestateEntries = new List>(); - Assert.False(TraceStateUtilsNew.AppendTraceState(tracestate, tracestateEntries)); + Assert.False(TraceStateUtils.AppendTraceState(tracestate, tracestateEntries)); Assert.Empty(tracestateEntries); } @@ -49,11 +49,11 @@ public void MaxEntries() var tracestateEntries = new List>(); var tracestate = "k0=v,k1=v,k2=v,k3=v,k4=v,k5=v,k6=v,k7=v1,k8=v,k9=v,k10=v,k11=v,k12=v,k13=v,k14=v,k15=v,k16=v,k17=v,k18=v,k19=v,k20=v,k21=v,k22=v,k23=v,k24=v,k25=v,k26=v,k27=v1,k28=v,k29=v,k30=v,k31=v"; - Assert.True(TraceStateUtilsNew.AppendTraceState(tracestate, tracestateEntries)); + Assert.True(TraceStateUtils.AppendTraceState(tracestate, tracestateEntries)); Assert.Equal(32, tracestateEntries.Count); Assert.Equal( "k0=v,k1=v,k2=v,k3=v,k4=v,k5=v,k6=v,k7=v1,k8=v,k9=v,k10=v,k11=v,k12=v,k13=v,k14=v,k15=v,k16=v,k17=v,k18=v,k19=v,k20=v,k21=v,k22=v,k23=v,k24=v,k25=v,k26=v,k27=v1,k28=v,k29=v,k30=v,k31=v", - TraceStateUtilsNew.GetString(tracestateEntries)); + TraceStateUtils.GetString(tracestateEntries)); } [Fact] @@ -62,7 +62,7 @@ public void TooManyEntries() var tracestateEntries = new List>(); var tracestate = "k0=v,k1=v,k2=v,k3=v,k4=v,k5=v,k6=v,k7=v1,k8=v,k9=v,k10=v,k11=v,k12=v,k13=v,k14=v,k15=v,k16=v,k17=v,k18=v,k19=v,k20=v,k21=v,k22=v,k23=v,k24=v,k25=v,k26=v,k27=v1,k28=v,k29=v,k30=v,k31=v,k32=v"; - Assert.False(TraceStateUtilsNew.AppendTraceState(tracestate, tracestateEntries)); + Assert.False(TraceStateUtils.AppendTraceState(tracestate, tracestateEntries)); Assert.Empty(tracestateEntries); } @@ -79,10 +79,10 @@ public void TooManyEntries() public void ValidPair(string pair, string expectedKey, string expectedValue) { var tracestateEntries = new List>(); - Assert.True(TraceStateUtilsNew.AppendTraceState(pair, tracestateEntries)); + Assert.True(TraceStateUtils.AppendTraceState(pair, tracestateEntries)); Assert.Single(tracestateEntries); Assert.Equal(new KeyValuePair(expectedKey, expectedValue), tracestateEntries.Single()); - Assert.Equal($"{expectedKey}={expectedValue}", TraceStateUtilsNew.GetString(tracestateEntries)); + Assert.Equal($"{expectedKey}={expectedValue}", TraceStateUtils.GetString(tracestateEntries)); } [Theory] @@ -93,11 +93,11 @@ public void ValidPair(string pair, string expectedKey, string expectedValue) public void ValidPairs(string tracestate) { var tracestateEntries = new List>(); - Assert.True(TraceStateUtilsNew.AppendTraceState(tracestate, tracestateEntries)); + Assert.True(TraceStateUtils.AppendTraceState(tracestate, tracestateEntries)); Assert.Equal(2, tracestateEntries.Count); Assert.Contains(new KeyValuePair("k1", "v1"), tracestateEntries); Assert.Contains(new KeyValuePair("k2", "v2"), tracestateEntries); - Assert.Equal("k1=v1,k2=v2", TraceStateUtilsNew.GetString(tracestateEntries)); + Assert.Equal("k1=v1,k2=v2", TraceStateUtils.GetString(tracestateEntries)); } } diff --git a/test/OpenTelemetry.Api.Tests/Context/RuntimeContextTest.cs b/test/OpenTelemetry.Api.Tests/Context/RuntimeContextTests.cs similarity index 91% rename from test/OpenTelemetry.Api.Tests/Context/RuntimeContextTest.cs rename to test/OpenTelemetry.Api.Tests/Context/RuntimeContextTests.cs index ff80bdd76d1..81570185c2f 100644 --- a/test/OpenTelemetry.Api.Tests/Context/RuntimeContextTest.cs +++ b/test/OpenTelemetry.Api.Tests/Context/RuntimeContextTests.cs @@ -5,9 +5,9 @@ namespace OpenTelemetry.Context.Tests; -public class RuntimeContextTest : IDisposable +public sealed class RuntimeContextTests : IDisposable { - public RuntimeContextTest() + public RuntimeContextTests() { RuntimeContext.Clear(); } @@ -111,7 +111,7 @@ public void NullableValueTypeSlotNullableTests() [Fact] public void ReferenceTypeSlotNullableTests() { - var expectedSlot = RuntimeContext.RegisterSlot("testslot_referencetype"); + var expectedSlot = RuntimeContext.RegisterSlot("testslot_referencetype"); Assert.NotNull(expectedSlot); var slotValueAccessor = expectedSlot as IRuntimeContextSlotValueAccessor; @@ -168,9 +168,13 @@ public void Dispose() } #if NETFRAMEWORK - private class RemoteObject : ContextBoundObject +#pragma warning disable CA1812 // Avoid uninstantiated internal classes + private sealed class RemoteObject : ContextBoundObject +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { +#pragma warning disable CA1822 // Mark members as static public int GetValueFromContextSlot(string slotName) +#pragma warning restore CA1822 // Mark members as static { // Slot is not propagated across AppDomains, attempting to get // an existing slot here should throw an ArgumentException. @@ -178,7 +182,7 @@ public int GetValueFromContextSlot(string slotName) { RuntimeContext.GetSlot(slotName); - throw new Exception("Should not have found an existing slot: " + slotName); + throw new InvalidOperationException("Should not have found an existing slot: " + slotName); } catch (ArgumentException) { diff --git a/test/OpenTelemetry.Api.Tests/EventSourceTest.cs b/test/OpenTelemetry.Api.Tests/EventSourceTests.cs similarity index 92% rename from test/OpenTelemetry.Api.Tests/EventSourceTest.cs rename to test/OpenTelemetry.Api.Tests/EventSourceTests.cs index 0912a5e3f5f..4f2ee52d14c 100644 --- a/test/OpenTelemetry.Api.Tests/EventSourceTest.cs +++ b/test/OpenTelemetry.Api.Tests/EventSourceTests.cs @@ -7,7 +7,7 @@ namespace OpenTelemetry.Api.Tests; -public class EventSourceTest +public class EventSourceTests { [Fact] public void EventSourceTest_OpenTelemetryApiEventSource() diff --git a/test/OpenTelemetry.Api.Tests/Internal/GuardTest.cs b/test/OpenTelemetry.Api.Tests/Internal/GuardTests.cs similarity index 70% rename from test/OpenTelemetry.Api.Tests/Internal/GuardTest.cs rename to test/OpenTelemetry.Api.Tests/Internal/GuardTests.cs index a2af242b973..ab20d85f169 100644 --- a/test/OpenTelemetry.Api.Tests/Internal/GuardTest.cs +++ b/test/OpenTelemetry.Api.Tests/Internal/GuardTests.cs @@ -8,7 +8,7 @@ namespace OpenTelemetry.Internal.Tests; -public class GuardTest +public class GuardTests { [Fact] public void NullTest() @@ -22,17 +22,17 @@ public void NullTest() // Invalid object? potato = null; var ex1 = Assert.Throws(() => Guard.ThrowIfNull(potato)); - Assert.Contains("Must not be null", ex1.Message); + Assert.Contains("Must not be null", ex1.Message, StringComparison.OrdinalIgnoreCase); Assert.Equal("potato", ex1.ParamName); object? @event = null; var ex2 = Assert.Throws(() => Guard.ThrowIfNull(@event)); - Assert.Contains("Must not be null", ex2.Message); + Assert.Contains("Must not be null", ex2.Message, StringComparison.OrdinalIgnoreCase); Assert.Equal("@event", ex2.ParamName); Thing? thing = null; var ex3 = Assert.Throws(() => Guard.ThrowIfNull(thing?.Bar)); - Assert.Contains("Must not be null", ex3.Message); + Assert.Contains("Must not be null", ex3.Message, StringComparison.OrdinalIgnoreCase); Assert.Equal("thing?.Bar", ex3.ParamName); } @@ -45,16 +45,16 @@ public void NullOrEmptyTest() // Invalid var ex1 = Assert.Throws(() => Guard.ThrowIfNullOrEmpty(null)); - Assert.Contains("Must not be null or empty", ex1.Message); + Assert.Contains("Must not be null or empty", ex1.Message, StringComparison.OrdinalIgnoreCase); Assert.Equal("null", ex1.ParamName); var ex2 = Assert.Throws(() => Guard.ThrowIfNullOrEmpty(string.Empty)); - Assert.Contains("Must not be null or empty", ex2.Message); + Assert.Contains("Must not be null or empty", ex2.Message, StringComparison.OrdinalIgnoreCase); Assert.Equal("string.Empty", ex2.ParamName); var x = string.Empty; var ex3 = Assert.Throws(() => Guard.ThrowIfNullOrEmpty(x)); - Assert.Contains("Must not be null or empty", ex3.Message); + Assert.Contains("Must not be null or empty", ex3.Message, StringComparison.OrdinalIgnoreCase); Assert.Equal("x", ex3.ParamName); } @@ -66,15 +66,15 @@ public void NullOrWhitespaceTest() // Invalid var ex1 = Assert.Throws(() => Guard.ThrowIfNullOrWhitespace(null)); - Assert.Contains("Must not be null or whitespace", ex1.Message); + Assert.Contains("Must not be null or whitespace", ex1.Message, StringComparison.OrdinalIgnoreCase); Assert.Equal("null", ex1.ParamName); var ex2 = Assert.Throws(() => Guard.ThrowIfNullOrWhitespace(string.Empty)); - Assert.Contains("Must not be null or whitespace", ex2.Message); + Assert.Contains("Must not be null or whitespace", ex2.Message, StringComparison.OrdinalIgnoreCase); Assert.Equal("string.Empty", ex2.ParamName); var ex3 = Assert.Throws(() => Guard.ThrowIfNullOrWhitespace(" \t\n\r")); - Assert.Contains("Must not be null or whitespace", ex3.Message); + Assert.Contains("Must not be null or whitespace", ex3.Message, StringComparison.OrdinalIgnoreCase); Assert.Equal("\" \\t\\n\\r\"", ex3.ParamName); } @@ -88,7 +88,7 @@ public void InvalidTimeoutTest() // Invalid var ex1 = Assert.Throws(() => Guard.ThrowIfInvalidTimeout(-100)); - Assert.Contains("Must be non-negative or 'Timeout.Infinite'", ex1.Message); + Assert.Contains("Must be non-negative or 'Timeout.Infinite'", ex1.Message, StringComparison.OrdinalIgnoreCase); Assert.Equal("-100", ex1.ParamName); } @@ -104,10 +104,10 @@ public void RangeIntTest() // Invalid var ex1 = Assert.Throws(() => Guard.ThrowIfOutOfRange(-1, min: 0, max: 100, minName: "empty", maxName: "full")); - Assert.Contains("Must be in the range: [0: empty, 100: full]", ex1.Message); + Assert.Contains("Must be in the range: [0: empty, 100: full]", ex1.Message, StringComparison.OrdinalIgnoreCase); var ex2 = Assert.Throws(() => Guard.ThrowIfOutOfRange(-1, min: 0, max: 100, message: "error")); - Assert.Contains("error", ex2.Message); + Assert.Contains("error", ex2.Message, StringComparison.OrdinalIgnoreCase); } [Fact] @@ -120,10 +120,10 @@ public void RangeDoubleTest() // Invalid var ex3 = Assert.Throws(() => Guard.ThrowIfOutOfRange(-1.1, min: 0.1, max: 99.9, minName: "empty", maxName: "full")); - Assert.Contains("Must be in the range: [0.1: empty, 99.9: full]", ex3.Message); + Assert.Contains("Must be in the range: [0.1: empty, 99.9: full]", ex3.Message, StringComparison.OrdinalIgnoreCase); var ex4 = Assert.Throws(() => Guard.ThrowIfOutOfRange(-1.1, min: 0.0, max: 100.0)); - Assert.Contains("Must be in the range: [0, 100]", ex4.Message); + Assert.Contains("Must be in the range: [0, 100]", ex4.Message, StringComparison.OrdinalIgnoreCase); } [Fact] @@ -147,41 +147,45 @@ public void ZeroTest() // Invalid var ex1 = Assert.Throws(() => Guard.ThrowIfZero(0)); - Assert.Contains("Must not be zero", ex1.Message); + Assert.Contains("Must not be zero", ex1.Message, StringComparison.OrdinalIgnoreCase); Assert.Equal("0", ex1.ParamName); } - public class Thing +#pragma warning disable CA1812 // Avoid uninstantiated internal classes + internal sealed class Thing +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { public string? Bar { get; set; } } +} #if !NET - /// - /// Borrowed from: . - /// - public class CallerArgumentExpressionAttributeTests +/// +/// Borrowed from: . +/// +#pragma warning disable SA1402 // File may only contain a single type +public class CallerArgumentExpressionAttributeTests +#pragma warning restore SA1402 // File may only contain a single type +{ + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("paramName")] + public void Ctor_ParameterName_Roundtrip(string? value) { - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData("paramName")] - public static void Ctor_ParameterName_Roundtrip(string? value) - { - var caea = new CallerArgumentExpressionAttribute(value); - Assert.Equal(value, caea.ParameterName); - } - - [Fact] - public static void BasicTest() - { - Assert.Equal("null", GetValue(null)); - Assert.Equal("\"hello\"", GetValue("hello")); - Assert.Equal("3 + 2", GetValue(3 + 2)); - Assert.Equal("new object()", GetValue(new object())); - } - - private static string? GetValue(object? argument, [CallerArgumentExpression(nameof(argument))] string? expr = null) => expr; + var caea = new CallerArgumentExpressionAttribute(value); + Assert.Equal(value, caea.ParameterName); } -#endif + + [Fact] + public void BasicTest() + { + Assert.Equal("null", GetValue(null)); + Assert.Equal("\"hello\"", GetValue("hello")); + Assert.Equal("3 + 2", GetValue(3 + 2)); + Assert.Equal("new object()", GetValue(new object())); + } + + private static string? GetValue(object? argument, [CallerArgumentExpression(nameof(argument))] string? expr = null) => expr; } +#endif diff --git a/test/OpenTelemetry.Api.Tests/Logs/LogRecordAttributeListTests.cs b/test/OpenTelemetry.Api.Tests/Logs/LogRecordAttributeListTests.cs index 6cc8486a23c..4e0aaacb89f 100644 --- a/test/OpenTelemetry.Api.Tests/Logs/LogRecordAttributeListTests.cs +++ b/test/OpenTelemetry.Api.Tests/Logs/LogRecordAttributeListTests.cs @@ -133,4 +133,28 @@ public void ExportTest(int numberOfItems) index++; } } + + [Fact] + public void InitializerAddSyntaxTest() + { + LogRecordAttributeList list = new LogRecordAttributeList + { + { "key1", new object() }, + { "key2", 2 }, + }; + + Assert.Equal(2, list.Count); + } + + [Fact] + public void InitializerIndexesSyntaxTest() + { + LogRecordAttributeList list = new LogRecordAttributeList + { + ["key1"] = new object(), + ["key2"] = 2, + }; + + Assert.Equal(2, list.Count); + } } diff --git a/test/OpenTelemetry.Api.Tests/Logs/LoggerProviderTests.cs b/test/OpenTelemetry.Api.Tests/Logs/LoggerProviderTests.cs index 27078c6307e..24415c12fb4 100644 --- a/test/OpenTelemetry.Api.Tests/Logs/LoggerProviderTests.cs +++ b/test/OpenTelemetry.Api.Tests/Logs/LoggerProviderTests.cs @@ -1,9 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NETSTANDARD2_1_OR_GREATER || NET using System.Diagnostics.CodeAnalysis; -#endif using Xunit; namespace OpenTelemetry.Logs.Tests; @@ -66,9 +64,7 @@ protected override bool TryCreateLogger( internal override bool TryCreateLogger( #endif string? name, -#if NETSTANDARD2_1_OR_GREATER || NET [NotNullWhen(true)] -#endif out Logger? logger) { logger = new TestLogger(name); diff --git a/test/OpenTelemetry.Api.Tests/OpenTelemetry.Api.Tests.csproj b/test/OpenTelemetry.Api.Tests/OpenTelemetry.Api.Tests.csproj index 2e88e5a9587..0bb3a3d310c 100644 --- a/test/OpenTelemetry.Api.Tests/OpenTelemetry.Api.Tests.csproj +++ b/test/OpenTelemetry.Api.Tests/OpenTelemetry.Api.Tests.csproj @@ -17,12 +17,7 @@ - - - - - - + diff --git a/test/OpenTelemetry.Api.Tests/Trace/ActivityExtensionsTest.cs b/test/OpenTelemetry.Api.Tests/Trace/ActivityExtensionsTests.cs similarity index 97% rename from test/OpenTelemetry.Api.Tests/Trace/ActivityExtensionsTest.cs rename to test/OpenTelemetry.Api.Tests/Trace/ActivityExtensionsTests.cs index 5f7a6ff5493..4de20d9bc4b 100644 --- a/test/OpenTelemetry.Api.Tests/Trace/ActivityExtensionsTest.cs +++ b/test/OpenTelemetry.Api.Tests/Trace/ActivityExtensionsTests.cs @@ -7,7 +7,7 @@ namespace OpenTelemetry.Trace.Tests; -public class ActivityExtensionsTest +public class ActivityExtensionsTests { private const string ActivityName = "Test Activity"; @@ -134,7 +134,7 @@ public void LastSetStatusWins() public void CheckRecordException() { var message = "message"; - var exception = new ArgumentNullException(message, new Exception(message)); + var exception = new ArgumentNullException(message, new InvalidOperationException(message)); using var activity = new Activity("test-activity"); activity.RecordException(exception); @@ -147,7 +147,7 @@ public void CheckRecordException() public void RecordExceptionWithAdditionalTags() { var message = "message"; - var exception = new ArgumentNullException(message, new Exception(message)); + var exception = new ArgumentNullException(message, new InvalidOperationException(message)); using var activity = new Activity("test-activity"); var tags = new TagList diff --git a/test/OpenTelemetry.Api.Tests/Trace/SpanAttributesTest.cs b/test/OpenTelemetry.Api.Tests/Trace/SpanAttributesTests.cs similarity index 84% rename from test/OpenTelemetry.Api.Tests/Trace/SpanAttributesTest.cs rename to test/OpenTelemetry.Api.Tests/Trace/SpanAttributesTests.cs index abf35e39350..30a05938e1d 100644 --- a/test/OpenTelemetry.Api.Tests/Trace/SpanAttributesTest.cs +++ b/test/OpenTelemetry.Api.Tests/Trace/SpanAttributesTests.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Trace.Tests; -public class SpanAttributesTest +public class SpanAttributesTests { [Fact] public void ValidateConstructor() @@ -19,16 +19,16 @@ public void ValidateAddMethods() { var spanAttribute = new SpanAttributes(); spanAttribute.Add("key_string", "string"); - spanAttribute.Add("key_a_string", new string[] { "string" }); + spanAttribute.Add("key_a_string", ["string"]); spanAttribute.Add("key_double", 1.01); - spanAttribute.Add("key_a_double", new double[] { 1.01 }); + spanAttribute.Add("key_a_double", [1.01]); spanAttribute.Add("key_bool", true); - spanAttribute.Add("key_a_bool", new bool[] { true }); + spanAttribute.Add("key_a_bool", [true]); spanAttribute.Add("key_long", 1); - spanAttribute.Add("key_a_long", new long[] { 1 }); + spanAttribute.Add("key_a_long", [1L]); Assert.Equal(8, spanAttribute.Attributes.Count); } diff --git a/test/OpenTelemetry.Api.Tests/Trace/StatusTest.cs b/test/OpenTelemetry.Api.Tests/Trace/StatusTests.cs similarity index 99% rename from test/OpenTelemetry.Api.Tests/Trace/StatusTest.cs rename to test/OpenTelemetry.Api.Tests/Trace/StatusTests.cs index f3898d06891..3da0655cd73 100644 --- a/test/OpenTelemetry.Api.Tests/Trace/StatusTest.cs +++ b/test/OpenTelemetry.Api.Tests/Trace/StatusTests.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Trace.Tests; -public class StatusTest +public class StatusTests { [Fact] public void Status_Ok() diff --git a/test/OpenTelemetry.Api.Tests/Trace/TelemetrySpanTest.cs b/test/OpenTelemetry.Api.Tests/Trace/TelemetrySpanTests.cs similarity index 59% rename from test/OpenTelemetry.Api.Tests/Trace/TelemetrySpanTest.cs rename to test/OpenTelemetry.Api.Tests/Trace/TelemetrySpanTests.cs index a8c281deae5..53a6a4fd82b 100644 --- a/test/OpenTelemetry.Api.Tests/Trace/TelemetrySpanTest.cs +++ b/test/OpenTelemetry.Api.Tests/Trace/TelemetrySpanTests.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Trace.Tests; -public class TelemetrySpanTest +public class TelemetrySpanTests { [Fact] public void CheckRecordExceptionData() @@ -15,7 +15,7 @@ public void CheckRecordExceptionData() using Activity activity = new Activity("exception-test"); using TelemetrySpan telemetrySpan = new TelemetrySpan(activity); - telemetrySpan.RecordException(new ArgumentNullException(message, new Exception("new-exception"))); + telemetrySpan.RecordException(new ArgumentNullException(message, new InvalidOperationException("new-exception"))); Assert.Single(activity.Events); Assert.NotNull(telemetrySpan.Activity); @@ -66,9 +66,71 @@ public void ParentIds() Assert.Equal(default, parentSpan.ParentSpanId); Assert.NotNull(parentActivity.Id); - using var childActivity = new Activity("childOperation").SetParentId(parentActivity.Id); + using var childActivity = new Activity("childOperation"); + childActivity.SetParentId(parentActivity.Id); using var childSpan = new TelemetrySpan(childActivity); Assert.Equal(parentSpan.Context.SpanId, childSpan.ParentSpanId); } + + [Fact] + public void CheckAddLinkData() + { + using var activity = new Activity("test-activity"); + activity.Start(); + using var span = new TelemetrySpan(activity); + + var traceId = ActivityTraceId.CreateRandom(); + var spanId = ActivitySpanId.CreateRandom(); + var context = new SpanContext(traceId, spanId, ActivityTraceFlags.Recorded); + + span.AddLink(context); + + Assert.Single(activity.Links); + var link = activity.Links.First(); + Assert.Equal(traceId, link.Context.TraceId); + Assert.Equal(spanId, link.Context.SpanId); + Assert.Null(link.Tags); + } + + [Fact] + public void CheckAddLinkAttributes() + { + using var activity = new Activity("test-activity"); + activity.Start(); + using var span = new TelemetrySpan(activity); + + var traceId = ActivityTraceId.CreateRandom(); + var spanId = ActivitySpanId.CreateRandom(); + var context = new SpanContext(traceId, spanId, ActivityTraceFlags.Recorded); + + var attributes = new SpanAttributes(); + attributes.Add("key1", "value1"); + + span.AddLink(context, attributes); + + Assert.Single(activity.Links); + var link = activity.Links.First(); + Assert.NotNull(link.Tags); + Assert.Single(link.Tags); + var tag = link.Tags.First(); + Assert.Equal("key1", tag.Key); + Assert.Equal("value1", tag.Value); + } + + [Fact] + public void CheckAddLinkNotRecording() + { + using var activity = new Activity("test-activity"); + + // Simulate not recording + activity.IsAllDataRequested = false; + using var span = new TelemetrySpan(activity); + + var context = new SpanContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None); + + span.AddLink(context, null); + + Assert.Empty(activity.Links); + } } diff --git a/test/OpenTelemetry.Api.Tests/Trace/TracerTest.cs b/test/OpenTelemetry.Api.Tests/Trace/TracerTests.cs similarity index 69% rename from test/OpenTelemetry.Api.Tests/Trace/TracerTest.cs rename to test/OpenTelemetry.Api.Tests/Trace/TracerTests.cs index 642795202d0..b2006654a4e 100644 --- a/test/OpenTelemetry.Api.Tests/Trace/TracerTest.cs +++ b/test/OpenTelemetry.Api.Tests/Trace/TracerTests.cs @@ -10,12 +10,12 @@ namespace OpenTelemetry.Trace.Tests; -public class TracerTest : IDisposable +public sealed class TracerTests : IDisposable { private readonly ITestOutputHelper output; private readonly Tracer tracer; - public TracerTest(ITestOutputHelper output) + public TracerTests(ITestOutputHelper output) { this.output = output; this.tracer = TracerProvider.Default.GetTracer("tracername", "tracerversion"); @@ -303,8 +303,8 @@ public void TracerBecomesNoopWhenParentProviderIsDisposedTest() Tracer? tracer1; using (var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddSource("mytracer") - .Build()) + .AddSource("mytracer") + .Build()) { provider = tracerProvider; tracer1 = tracerProvider.GetTracer("mytracer"); @@ -330,7 +330,7 @@ public void TracerConcurrencyTest() .WithTestingIterations(100) .WithMemoryAccessRaceCheckingEnabled(true); - var test = TestingEngine.Create(config, InnerTest); + using var test = TestingEngine.Create(config, InnerTest); test.Run(); @@ -370,7 +370,7 @@ static void InnerTest() Thread[] getTracerThreads = new Thread[testTracerProvider.ExpectedNumberOfThreads]; for (int i = 0; i < testTracerProvider.ExpectedNumberOfThreads; i++) { - getTracerThreads[i] = new Thread((object? state) => + getTracerThreads[i] = new Thread(state => { var testTracerProvider = state as TestTracerProvider; Assert.NotNull(testTracerProvider); @@ -397,17 +397,154 @@ static void InnerTest() testTracerProvider.StartHandle.WaitOne(); - testTracerProvider.Dispose(); - foreach (var getTracerThread in getTracerThreads) { getTracerThread.Join(); } + testTracerProvider.Dispose(); + Assert.Empty(tracers); } } + [Fact] + public void GetTracer_WithSameTags_ReturnsSameInstance() + { + var tags1 = new List> { new("tag1", "value1"), new("tag2", "value2") }; + var tags2 = new List> { new("tag1", "value1"), new("tag2", "value2") }; + + using var tracerProvider = new TestTracerProvider(); + var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1); + var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2); + + Assert.Same(tracer1, tracer2); + } + + [Fact] + public void GetTracer_WithoutTags_ReturnsSameInstance() + { + using var tracerProvider = new TestTracerProvider(); + var tracer1 = tracerProvider.GetTracer("test", "1.0.0"); + var tracer2 = tracerProvider.GetTracer("test", "1.0.0"); + + Assert.Same(tracer1, tracer2); + } + + [Fact] + public void GetTracer_WithDifferentTags_ReturnsDifferentInstances() + { + var tags1 = new List> { new("tag1", "value1") }; + var tags2 = new List> { new("tag2", "value2") }; + + using var tracerProvider = new TestTracerProvider(); + var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1); + var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2); + + Assert.NotSame(tracer1, tracer2); + } + + [Fact] + public void GetTracer_WithDifferentOrderTags_ReturnsSameInstance() + { + var tags1 = new List> { new("tag2", "value2"), new("tag1", "value1"), }; + var tags2 = new List> { new("tag1", "value1"), new("tag2", "value2"), }; + + using var tracerProvider = new TestTracerProvider(); + var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1); + var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2); + + Assert.Same(tracer1, tracer2); + } + + [Fact] + public void GetTracer_TagsValuesAreIntType_ReturnsSameInstance() + { + var tags1 = new List> { new("tag2", 2), new("tag1", 1) }; + var tags2 = new List> { new("tag1", 1), new("tag2", 2) }; + + using var tracerProvider = new TestTracerProvider(); + var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1); + var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2); + + Assert.Same(tracer1, tracer2); + } + + [Fact] + public void GetTracer_TagsValuesAreSameWithDifferentOrder_ReturnsSameInstance() + { + var tags1 = new List> { new("tag3", 1), new("tag1", 1), new("tag2", 1), new("tag1", 2), new("tag2", 2) }; + var tags2 = new List> { new("tag2", 1), new("tag1", 2), new("tag1", 1), new("tag2", 2), new("tag3", 1) }; + + using var tracerProvider = new TestTracerProvider(); + var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1); + var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2); + + Assert.Same(tracer1, tracer2); + } + + [Fact] + public void GetTracer_TagsContainNullValues_ReturnsSameInstance() + { + var tags1 = new List> { new("tag3", 1), new("tag2", 3), new("tag1", null), new("tag2", null), new("tag1", 2), new("tag2", 2) }; + var tags2 = new List> { new("tag2", null), new("tag1", 2), new("tag2", 3), new("tag1", null), new("tag2", 2), new("tag3", 1) }; + + using var tracerProvider = new TestTracerProvider(); + var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1); + var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2); + + Assert.Same(tracer1, tracer2); + } + + [Fact] + public void GetTracer_WithDifferentTagsSize_ReturnsDifferentInstances() + { + var tags1 = new List> { new("tag2", 2), new("tag1", 1) }; + var tags2 = new List> { new("tag1", 1), new("tag2", 2), new("tag3", 3) }; + + using var tracerProvider = new TestTracerProvider(); + var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1); + var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2); + + Assert.NotSame(tracer1, tracer2); + } + + [Fact] + public void GetTracer_WithTagsAndWithoutTags_ReturnsDifferentInstances() + { + var tags = new List> { new("tag1", "value1") }; + + using var tracerProvider = new TestTracerProvider(); + var tracerWithTags = tracerProvider.GetTracer("test", "1.0.0", tags); + var tracerWithoutTags = tracerProvider.GetTracer("test", "1.0.0"); + + Assert.NotEqual(tracerWithTags, tracerWithoutTags); + } + + [Fact] + public void GetTracer_WithTags_AppliesTagsToActivities() + { + var exportedItems = new List(); + var tags = new List> { new("tracerTag", "tracerValue") }; + + using var tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddSource("test") + .AddInMemoryExporter(exportedItems) + .SetSampler(new AlwaysOnSampler()) + .Build(); + + var tracer = tracerProvider.GetTracer("test", "1.0.0", tags); + + using (var span = tracer.StartActiveSpan("TestSpan")) + { + // Activity started by the tracer with tags + } + + var activity = Assert.Single(exportedItems); + + Assert.Contains(activity.Source.Tags!, kvp => kvp.Key == "tracerTag" && (string)kvp.Value! == "tracerValue"); + } + public void Dispose() { Activity.Current = null; @@ -424,5 +561,15 @@ private sealed class TestTracerProvider : TracerProvider public int ExpectedNumberOfThreads; public int NumberOfThreads; public EventWaitHandle StartHandle = new ManualResetEvent(false); + + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.StartHandle.Dispose(); + } + + base.Dispose(disposing); + } } } diff --git a/test/OpenTelemetry.Exporter.Console.Tests/ConsoleActivityExporterTest.cs b/test/OpenTelemetry.Exporter.Console.Tests/ConsoleActivityExporterTests.cs similarity index 89% rename from test/OpenTelemetry.Exporter.Console.Tests/ConsoleActivityExporterTest.cs rename to test/OpenTelemetry.Exporter.Console.Tests/ConsoleActivityExporterTests.cs index e3305513d1d..31aa5f0297c 100644 --- a/test/OpenTelemetry.Exporter.Console.Tests/ConsoleActivityExporterTest.cs +++ b/test/OpenTelemetry.Exporter.Console.Tests/ConsoleActivityExporterTests.cs @@ -8,10 +8,10 @@ namespace OpenTelemetry.Exporter.Console.Tests; -public class ConsoleActivityExporterTest +public class ConsoleActivityExporterTests { [Fact] - public void VerifyConsoleActivityExporterDoesntFailWithoutActivityLinkTags() + public void VerifyConsoleActivityExporterDoesNotFailWithoutActivityLinkTags() { var activitySourceName = Utils.GetCurrentMethodName(); using var activitySource = new ActivitySource(activitySourceName); @@ -43,6 +43,6 @@ public void VerifyConsoleActivityExporterDoesntFailWithoutActivityLinkTags() // Test that the ConsoleExporter correctly handles an Activity without Tags. using var consoleExporter = new ConsoleActivityExporter(new ConsoleExporterOptions()); - Assert.Equal(ExportResult.Success, consoleExporter.Export(new Batch(new[] { activity }, 1))); + Assert.Equal(ExportResult.Success, consoleExporter.Export(new Batch([activity], 1))); } } diff --git a/test/OpenTelemetry.Exporter.Console.Tests/OpenTelemetry.Exporter.Console.Tests.csproj b/test/OpenTelemetry.Exporter.Console.Tests/OpenTelemetry.Exporter.Console.Tests.csproj index 8745db77efc..28e5579a72d 100644 --- a/test/OpenTelemetry.Exporter.Console.Tests/OpenTelemetry.Exporter.Console.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Console.Tests/OpenTelemetry.Exporter.Console.Tests.csproj @@ -5,14 +5,6 @@ $(TargetFrameworksForTests) - - - - - runtime; build; native; contentfiles; analyzers - - - diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/EventSourceTest.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/EventSourceTests.cs similarity index 97% rename from test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/EventSourceTest.cs rename to test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/EventSourceTests.cs index f786289ef49..69787cec48e 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/EventSourceTest.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/EventSourceTests.cs @@ -9,7 +9,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; -public class EventSourceTest +public class EventSourceTests { [Fact] public void EventSourceTest_OpenTelemetryProtocolExporterEventSource() diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/GrpcRetryTestCase.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/GrpcRetryTestCase.cs new file mode 100644 index 00000000000..ca659c79a4b --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/GrpcRetryTestCase.cs @@ -0,0 +1,144 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Google.Protobuf; +using Google.Protobuf.WellKnownTypes; +using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc; +using Xunit; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Tests; + +#pragma warning disable CA1515 // Consider making public types internal +public class GrpcRetryTestCase +#pragma warning restore CA1515 // Consider making public types internal +{ + private readonly string testRunnerName; + + private GrpcRetryTestCase(string testRunnerName, GrpcRetryAttempt[] retryAttempts, int expectedRetryAttempts = 1) + { + this.ExpectedRetryAttempts = expectedRetryAttempts; + this.RetryAttempts = retryAttempts; + this.testRunnerName = testRunnerName; + } + + public int ExpectedRetryAttempts { get; } + + internal GrpcRetryAttempt[] RetryAttempts { get; } + + public static TheoryData GetGrpcTestCases() + { + return + [ + new("Cancelled", [new(StatusCode.Cancelled)]), + new("DeadlineExceeded", [new(StatusCode.DeadlineExceeded)]), + new("Aborted", [new(StatusCode.Aborted)]), + new("OutOfRange", [new(StatusCode.OutOfRange)]), + new("DataLoss", [new(StatusCode.DataLoss)]), + new("Unavailable", [new(StatusCode.Unavailable)]), + + new("OK", [new(StatusCode.OK, expectedSuccess: false)]), + new("PermissionDenied", [new(StatusCode.PermissionDenied, expectedSuccess: false)]), + new("Unknown", [new(StatusCode.Unknown, expectedSuccess: false)]), + + new("ResourceExhausted w/o RetryInfo", [new(StatusCode.ResourceExhausted, expectedSuccess: false)]), + new("ResourceExhausted w/ RetryInfo", [new(StatusCode.ResourceExhausted, throttleDelay: GetThrottleDelayString(new Duration { Seconds = 2 }), expectedNextRetryDelayMilliseconds: 3000)]), + + new("Unavailable w/ RetryInfo", [new(StatusCode.Unavailable, throttleDelay: GetThrottleDelayString(Duration.FromTimeSpan(TimeSpan.FromMilliseconds(2000))), expectedNextRetryDelayMilliseconds: 3000)]), + + new("Expired deadline", [new(StatusCode.Unavailable, deadlineExceeded: true, expectedSuccess: false)]), + + new( + "Exponential backoff", + [ + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1500), + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2250), + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 3375), + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000), + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000) + ], + expectedRetryAttempts: 5), + + new( + "Retry until non-retryable status code encountered", + [ + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1500), + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2250), + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 3375), + new(StatusCode.PermissionDenied, expectedSuccess: false), + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000) + ], + expectedRetryAttempts: 4), + + // Test throttling affects exponential backoff. + new( + "Exponential backoff after throttling", + [ + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1500), + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2250), + new(StatusCode.Unavailable, throttleDelay: GetThrottleDelayString(Duration.FromTimeSpan(TimeSpan.FromMilliseconds(500))), expectedNextRetryDelayMilliseconds: 750), + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1125), + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1688), + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2532), + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 3798), + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000), + new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000) + ], + expectedRetryAttempts: 9), + ]; + } + + public override string ToString() + { + return this.testRunnerName; + } + + private static string GetThrottleDelayString(Duration throttleDelay) + { + var status = new Google.Rpc.Status + { + Code = 4, + Message = "Only nanos", + Details = + { + Any.Pack(new Google.Rpc.RetryInfo + { + RetryDelay = throttleDelay, + }), + }, + }; + + return Convert.ToBase64String(status.ToByteArray()); + } + + internal struct GrpcRetryAttempt + { + internal GrpcRetryAttempt( + StatusCode statusCode, + bool deadlineExceeded = false, + string? throttleDelay = null, + int expectedNextRetryDelayMilliseconds = 1500, + bool expectedSuccess = true) + { + var status = new Status(statusCode, "Error"); + + // Using arbitrary +1 hr for deadline for test purposes. + var deadlineUtc = deadlineExceeded ? DateTime.UtcNow.AddSeconds(-1) : DateTime.UtcNow.AddHours(1); + + this.ThrottleDelay = throttleDelay; + + this.Response = new ExportClientGrpcResponse(expectedSuccess, deadlineUtc, null, status, this.ThrottleDelay); + + this.ExpectedNextRetryDelayMilliseconds = expectedNextRetryDelayMilliseconds; + + this.ExpectedSuccess = expectedSuccess; + } + + public string? ThrottleDelay { get; } + + public int? ExpectedNextRetryDelayMilliseconds { get; } + + public bool ExpectedSuccess { get; } + + internal ExportClientGrpcResponse Response { get; } + } +} diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/HttpRetryTestCase.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/HttpRetryTestCase.cs new file mode 100644 index 00000000000..6bea0eb7d3a --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/HttpRetryTestCase.cs @@ -0,0 +1,113 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Net; +#if NETFRAMEWORK +using System.Net.Http; +#endif +using System.Net.Http.Headers; +using Xunit; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Tests; + +#pragma warning disable CA1515 // Consider making public types internal +public class HttpRetryTestCase +#pragma warning restore CA1515 // Consider making public types internal +{ + private readonly string testRunnerName; + + private HttpRetryTestCase(string testRunnerName, HttpRetryAttempt[] retryAttempts, int expectedRetryAttempts = 1) + { + this.ExpectedRetryAttempts = expectedRetryAttempts; + this.RetryAttempts = retryAttempts; + this.testRunnerName = testRunnerName; + } + + public int ExpectedRetryAttempts { get; } + + internal HttpRetryAttempt[] RetryAttempts { get; } + + public static TheoryData GetHttpTestCases() + { + return + [ + new("NetworkError", [new(statusCode: null)]), + new("GatewayTimeout", [new(statusCode: HttpStatusCode.GatewayTimeout, throttleDelay: TimeSpan.FromSeconds(1))]), +#if NETSTANDARD2_1_OR_GREATER || NET + new("ServiceUnavailable", [new(statusCode: HttpStatusCode.TooManyRequests, throttleDelay: TimeSpan.FromSeconds(1))]), +#endif + + new( + "Exponential Backoff", + [ + new(statusCode: null, expectedNextRetryDelayMilliseconds: 1500), + new(statusCode: null, expectedNextRetryDelayMilliseconds: 2250), + new(statusCode: null, expectedNextRetryDelayMilliseconds: 3375), + new(statusCode: null, expectedNextRetryDelayMilliseconds: 5000), + new(statusCode: null, expectedNextRetryDelayMilliseconds: 5000) + ], + expectedRetryAttempts: 5), + new( + "Retry until non-retryable status code encountered", + [ + new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 1500), + new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 2250), + new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 3375), + new(statusCode: HttpStatusCode.BadRequest, expectedSuccess: false), + new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 5000) + ], + expectedRetryAttempts: 4), + new( + "Expired deadline", + [ + new(statusCode: HttpStatusCode.ServiceUnavailable, isDeadlineExceeded: true, expectedSuccess: false) + ]), + ]; + + // TODO: Add more cases. + } + + public override string ToString() + { + return this.testRunnerName; + } + + internal sealed class HttpRetryAttempt + { + public ExportClientHttpResponse Response; + public TimeSpan? ThrottleDelay; + public int? ExpectedNextRetryDelayMilliseconds; + public bool ExpectedSuccess; + + internal HttpRetryAttempt( + HttpStatusCode? statusCode, + TimeSpan? throttleDelay = null, + bool isDeadlineExceeded = false, + int expectedNextRetryDelayMilliseconds = 1500, + bool expectedSuccess = true) + { + this.ThrottleDelay = throttleDelay; + + HttpResponseMessage? responseMessage = null; + if (statusCode != null) + { +#pragma warning disable CA2000 // Dispose objects before losing scope + responseMessage = new HttpResponseMessage(); +#pragma warning restore CA2000 // Dispose objects before losing scope + + if (throttleDelay != null) + { + responseMessage.Headers.RetryAfter = new RetryConditionHeaderValue(throttleDelay.Value); + } + + responseMessage.StatusCode = (HttpStatusCode)statusCode; + } + + // Using arbitrary +1 hr for deadline for test purposes. + var deadlineUtc = isDeadlineExceeded ? DateTime.UtcNow.AddMilliseconds(-1) : DateTime.UtcNow.AddHours(1); + this.Response = new ExportClientHttpResponse(expectedSuccess, deadlineUtc, responseMessage, new HttpRequestException()); + this.ExpectedNextRetryDelayMilliseconds = expectedNextRetryDelayMilliseconds; + this.ExpectedSuccess = expectedSuccess; + } + } +} diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/ExportClient/OtlpHttpTraceExportClientTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/ExportClient/OtlpHttpTraceExportClientTests.cs index 620c2a0b187..d256b4fcf79 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/ExportClient/OtlpHttpTraceExportClientTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/ExportClient/OtlpHttpTraceExportClientTests.cs @@ -15,22 +15,32 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; -public class OtlpHttpTraceExportClientTests +public sealed class OtlpHttpTraceExportClientTests : IDisposable { private static readonly SdkLimitOptions DefaultSdkLimitOptions = new(); + private readonly ActivityListener activityListener; + static OtlpHttpTraceExportClientTests() { Activity.DefaultIdFormat = ActivityIdFormat.W3C; Activity.ForceDefaultIdFormat = true; + } - var listener = new ActivityListener + public OtlpHttpTraceExportClientTests() + { + this.activityListener = new ActivityListener { ShouldListenTo = _ => true, - Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllData, + Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllDataAndRecorded, }; - ActivitySource.AddActivityListener(listener); + ActivitySource.AddActivityListener(this.activityListener); + } + + public void Dispose() + { + this.activityListener.Dispose(); } [Fact] @@ -80,11 +90,11 @@ public void SendExportRequest_ExportTraceServiceRequest_SendsCorrectHttpRequest( Headers = $"{header1.Name}={header1.Value}, {header2.Name} = {header2.Value}", }; +#pragma warning disable CA2000 // Dispose objects before losing scope var testHttpHandler = new TestHttpMessageHandler(); +#pragma warning restore CA2000 // Dispose objects before losing scope - var httpRequestContent = Array.Empty(); - - var httpClient = new HttpClient(testHttpHandler); + using var httpClient = new HttpClient(testHttpHandler); var exportClient = new OtlpHttpExportClient(options, httpClient, string.Empty); @@ -107,7 +117,9 @@ public void SendExportRequest_ExportTraceServiceRequest_SendsCorrectHttpRequest( using var openTelemetrySdk = builder.Build(); var exportedItems = new List(); +#pragma warning disable CA2000 // Dispose objects before losing scope var processor = new BatchActivityExportProcessor(new InMemoryExporter(exportedItems)); +#pragma warning restore CA2000 // Dispose objects before losing scope const int numOfSpans = 10; bool isEven; for (var i = 0; i < numOfSpans; i++) diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/Serializer/OtlpArrayTagWriterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/Serializer/OtlpArrayTagWriterTests.cs index a4aed5f155c..d8ada0f2f80 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/Serializer/OtlpArrayTagWriterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/Serializer/OtlpArrayTagWriterTests.cs @@ -11,27 +11,27 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.Implementation.Serializer; -public class OtlpArrayTagWriterTests : IDisposable +public sealed class OtlpArrayTagWriterTests : IDisposable { private readonly ProtobufOtlpTagWriter.OtlpArrayTagWriter arrayTagWriter; + private readonly ActivityListener activityListener; static OtlpArrayTagWriterTests() { Activity.DefaultIdFormat = ActivityIdFormat.W3C; Activity.ForceDefaultIdFormat = true; - - var listener = new ActivityListener - { - ShouldListenTo = _ => true, - Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllData, - }; - - ActivitySource.AddActivityListener(listener); } public OtlpArrayTagWriterTests() { this.arrayTagWriter = new ProtobufOtlpTagWriter.OtlpArrayTagWriter(); + this.activityListener = new ActivityListener + { + ShouldListenTo = _ => true, + Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllDataAndRecorded, + }; + + ActivitySource.AddActivityListener(this.activityListener); } [Fact] @@ -43,7 +43,7 @@ public void BeginWriteArray_InitializesArrayState() // Assert Assert.NotNull(arrayState.Buffer); Assert.Equal(0, arrayState.WritePosition); - Assert.True(arrayState.Buffer.Length == 2048); + Assert.Equal(2048, arrayState.Buffer.Length); } [Fact] @@ -166,10 +166,11 @@ public void SerializeLargeArrayExceeding2MB_TruncatesInOtlpSpan() lessthat1MBArray[i] = "1234"; } + string?[] stringArray = ["12345"]; var tags = new ActivityTagsCollection { new("lessthat1MBArray", lessthat1MBArray), - new("StringArray", new string?[] { "12345" }), + new("StringArray", stringArray), new("LargeArray", largeArray), }; @@ -181,7 +182,7 @@ public void SerializeLargeArrayExceeding2MB_TruncatesInOtlpSpan() var otlpSpan = ToOtlpSpanWithExtendedBuffer(new SdkLimitOptions(), activity); Assert.NotNull(otlpSpan); - Assert.True(otlpSpan.Attributes.Count == 3); + Assert.Equal(3, otlpSpan.Attributes.Count); var keyValue = otlpSpan.Attributes.FirstOrDefault(kvp => kvp.Key == "StringArray"); Assert.NotNull(keyValue); Assert.Equal("12345", keyValue.Value.ArrayValue.Values[0].StringValue); @@ -265,6 +266,7 @@ public void Dispose() { // Clean up the thread buffer after each test ProtobufOtlpTagWriter.OtlpArrayTagWriter.ThreadBuffer = null; + this.activityListener.Dispose(); } private static OtlpTrace.Span? ToOtlpSpan(SdkLimitOptions sdkOptions, Activity activity) diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/Serializer/ProtobufSerializerTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/Serializer/ProtobufSerializerTests.cs index 100081916e7..6c202b528e7 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/Serializer/ProtobufSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/Serializer/ProtobufSerializerTests.cs @@ -102,6 +102,14 @@ public void WriteStringWithTag_WritesCorrectly() [InlineData(268435455, new byte[] { 0xFF, 0xFF, 0xFF, 0x7F })] // Max 4-byte value public void WriteReservedLength_WritesCorrectly(int length, byte[] expectedBytes) { +#if NET + Assert.NotNull(expectedBytes); +#else + if (expectedBytes == null) + { + throw new ArgumentNullException(nameof(expectedBytes)); + } +#endif byte[] buffer = new byte[10]; ProtobufSerializer.WriteReservedLength(buffer, 0, length); @@ -246,7 +254,7 @@ public void WriteStringWithTag_ASCIIString_WritesCorrectly() Assert.Equal(10, buffer[0]); // Tag Assert.Equal(5, buffer[1]); // Length - byte[] expectedContent = Encoding.ASCII.GetBytes("Hello"); + byte[] expectedContent = "Hello"u8.ToArray(); byte[] actualContent = new byte[5]; Array.Copy(buffer, 2, actualContent, 0, 5); Assert.True(expectedContent.SequenceEqual(actualContent)); diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/Dockerfile b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/Dockerfile index 0685ff1181a..d878b3fa0b0 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/Dockerfile +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/Dockerfile @@ -4,8 +4,10 @@ ARG BUILD_SDK_VERSION=9.0 ARG TEST_SDK_VERSION=9.0 +FROM mcr.microsoft.com/dotnet/sdk:8.0.414@sha256:3cef19377b2ef2a0171e930a24627677447c3e41b5f2eab84ff4895f1b15d254 AS dotnet-sdk-8.0 +FROM mcr.microsoft.com/dotnet/sdk:9.0.305@sha256:bb42ae2c058609d1746baf24fe6864ecab0686711dfca1f4b7a99e367ab17162 AS dotnet-sdk-9.0 -FROM mcr.microsoft.com/dotnet/sdk:${BUILD_SDK_VERSION} AS build +FROM dotnet-sdk-${BUILD_SDK_VERSION} AS build ARG PUBLISH_CONFIGURATION=Release ARG PUBLISH_FRAMEWORK=net9.0 WORKDIR /repo @@ -13,7 +15,7 @@ COPY . ./ WORKDIR "/repo/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests" RUN dotnet publish "OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj" -c "${PUBLISH_CONFIGURATION}" -f "${PUBLISH_FRAMEWORK}" -o /drop -p:IntegrationBuild=true -FROM mcr.microsoft.com/dotnet/sdk:${TEST_SDK_VERSION} AS final +FROM dotnet-sdk-${TEST_SDK_VERSION} AS final WORKDIR /test COPY --from=build /drop . diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs index 3e40b9c6d9f..d5d69cca093 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.Metrics; using System.Diagnostics.Tracing; +using System.Globalization; using Microsoft.Extensions.Logging; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Logs; @@ -34,6 +35,7 @@ public void Dispose() this.openTelemetryEventListener.Dispose(); } +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning [InlineData(OtlpExportProtocol.Grpc, ":4317", ExportProcessorType.Batch, false)] [InlineData(OtlpExportProtocol.HttpProtobuf, ":4318/v1/traces", ExportProcessorType.Batch, false)] [InlineData(OtlpExportProtocol.Grpc, ":4317", ExportProcessorType.Batch, true)] @@ -44,6 +46,7 @@ public void Dispose() [InlineData(OtlpExportProtocol.HttpProtobuf, ":4318/v1/traces", ExportProcessorType.Simple, true)] [InlineData(OtlpExportProtocol.Grpc, ":5317", ExportProcessorType.Simple, true, "https")] [InlineData(OtlpExportProtocol.HttpProtobuf, ":5318/v1/traces", ExportProcessorType.Simple, true, "https")] +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning [Trait("CategoryName", "CollectorIntegrationTests")] [SkipUnlessEnvVarFoundTheory(CollectorHostnameEnvVarName)] public void TraceExportResultIsSuccess(OtlpExportProtocol protocol, string endpoint, ExportProcessorType exportProcessorType, bool forceFlush, string scheme = "http") @@ -118,6 +121,7 @@ public void TraceExportResultIsSuccess(OtlpExportProtocol protocol, string endpo } } +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning [InlineData(OtlpExportProtocol.Grpc, ":4317", false, false)] [InlineData(OtlpExportProtocol.HttpProtobuf, ":4318/v1/metrics", false, false)] [InlineData(OtlpExportProtocol.Grpc, ":4317", false, true)] @@ -128,6 +132,7 @@ public void TraceExportResultIsSuccess(OtlpExportProtocol protocol, string endpo [InlineData(OtlpExportProtocol.HttpProtobuf, ":4318/v1/metrics", true, true)] [InlineData(OtlpExportProtocol.Grpc, ":5317", true, true, "https")] [InlineData(OtlpExportProtocol.HttpProtobuf, ":5318/v1/metrics", true, true, "https")] +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning [Trait("CategoryName", "CollectorIntegrationTests")] [SkipUnlessEnvVarFoundTheory(CollectorHostnameEnvVarName)] public void MetricExportResultIsSuccess(OtlpExportProtocol protocol, string endpoint, bool useManualExport, bool forceFlush, string scheme = "http") @@ -202,12 +207,14 @@ public void MetricExportResultIsSuccess(OtlpExportProtocol protocol, string endp } } +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning [InlineData(OtlpExportProtocol.Grpc, ":4317", ExportProcessorType.Batch)] [InlineData(OtlpExportProtocol.HttpProtobuf, ":4318/v1/logs", ExportProcessorType.Batch)] [InlineData(OtlpExportProtocol.Grpc, ":4317", ExportProcessorType.Simple)] [InlineData(OtlpExportProtocol.HttpProtobuf, ":4318/v1/logs", ExportProcessorType.Simple)] [InlineData(OtlpExportProtocol.Grpc, ":5317", ExportProcessorType.Simple, "https")] [InlineData(OtlpExportProtocol.HttpProtobuf, ":5318/v1/logs", ExportProcessorType.Simple, "https")] +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning [Trait("CategoryName", "CollectorIntegrationTests")] [SkipUnlessEnvVarFoundTheory(CollectorHostnameEnvVarName)] public void LogExportResultIsSuccess(OtlpExportProtocol protocol, string endpoint, ExportProcessorType exportProcessorType, string scheme = "http") @@ -259,7 +266,7 @@ public void LogExportResultIsSuccess(OtlpExportProtocol protocol, string endpoin }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); - logger.LogInformation("Hello from {name} {price}.", "tomato", 2.99); + logger.HelloFrom("tomato", 2.99); switch (processorOptions.ExportProcessorType) { @@ -301,7 +308,7 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) string? message; if (eventData.Message != null && eventData.Payload != null && eventData.Payload.Count > 0) { - message = string.Format(eventData.Message, eventData.Payload.ToArray()); + message = string.Format(CultureInfo.InvariantCulture, eventData.Message, eventData.Payload.ToArray()); } else { diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/docker-compose.yml b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/docker-compose.yml index f281c47dce9..d6bc9f4aefc 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/docker-compose.yml +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/docker-compose.yml @@ -2,8 +2,6 @@ # This should be run from the root of the repo: # docker compose --file=test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/docker-compose.yml --project-directory=. up --exit-code-from=tests --build -version: '3.7' - services: init-service: image: otel-test-image @@ -21,7 +19,7 @@ services: " otel-collector: - image: otel/opentelemetry-collector + image: otel/opentelemetry-collector:0.136.0@sha256:98fd3b410ae8a939be9588f1580c4b7c3da6ebba49f5363df4259a827aabb779 volumes: - ./test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest:/cfg command: --config=/cfg/otel-collector-config.yaml diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/LoggerExtensions.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/LoggerExtensions.cs new file mode 100644 index 00000000000..70834b83810 --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/LoggerExtensions.cs @@ -0,0 +1,48 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.Logging; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; + +internal static partial class LoggerExtensions +{ + [LoggerMessage(LogLevel.Information, "Hello from {Name} {Price}.")] + public static partial void HelloFrom(this ILogger logger, string name, double price); + + [LoggerMessage("Hello from {Name} {Price}.")] + public static partial void HelloFrom(this ILogger logger, LogLevel logLevel, string name, double price); + + [LoggerMessage(LogLevel.Information, EventId = 10, Message = "Hello from {Name} {Price}.")] + public static partial void HelloFromWithEventId(this ILogger logger, string name, double price); + + [LoggerMessage(LogLevel.Information, EventId = 10, EventName = "MyEvent10", Message = "Hello from {Name} {Price}.")] + public static partial void HelloFromWithEventIdAndEventName(this ILogger logger, string name, double price); + + [LoggerMessage(LogLevel.Information, "Log message")] + public static partial void LogMessage(this ILogger logger); + + [LoggerMessage(LogLevel.Information, "Log when there is no activity.")] + public static partial void LogWhenThereIsNoActivity(this ILogger logger); + + [LoggerMessage(LogLevel.Information, "Log within an activity.")] + public static partial void LogWithinAnActivity(this ILogger logger); + + [LoggerMessage(LogLevel.Information, "OpenTelemetry {Greeting} {Subject}!")] + public static partial void OpenTelemetryGreeting(this ILogger logger, string greeting, string subject); + + [LoggerMessage(LogLevel.Information, "OpenTelemetry {AttributeOne} {AttributeTwo} {AttributeThree}!")] + public static partial void OpenTelemetryWithAttributes(this ILogger logger, string attributeOne, string attributeTwo, string attributeThree); + + [LoggerMessage(LogLevel.Information, "Some log information message.")] + public static partial void SomeLogInformation(this ILogger logger); + + [LoggerMessage(LogLevel.Information, "Hello from red-tomato")] + public static partial void HelloFromRedTomato(this ILogger logger); + + [LoggerMessage(LogLevel.Information, "Hello from green-tomato")] + public static partial void HelloFromGreenTomato(this ILogger logger); + + [LoggerMessage(LogLevel.Information, "Exception Occurred")] + public static partial void ExceptionOccured(this ILogger logger, Exception exception); +} diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/MockCollectorIntegrationTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/MockCollectorIntegrationTests.cs index 56e15422a8f..0f810a25d34 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/MockCollectorIntegrationTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/MockCollectorIntegrationTests.cs @@ -4,6 +4,7 @@ #if !NETFRAMEWORK using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Net; using Grpc.Core; using Microsoft.AspNetCore.Builder; @@ -16,7 +17,6 @@ using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Transmission; -using OpenTelemetry.Metrics; using OpenTelemetry.PersistentStorage.Abstractions; using OpenTelemetry.Proto.Collector.Trace.V1; using OpenTelemetry.Tests; @@ -59,7 +59,7 @@ public async Task TestRecoveryAfterFailedExport() "/MockCollector/SetResponseCodes/{responseCodesCsv}", (MockCollectorState collectorState, string responseCodesCsv) => { - var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x)).ToArray(); + var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x, CultureInfo.InvariantCulture)).ToArray(); collectorState.SetStatusCodes(codes); }); @@ -71,13 +71,15 @@ public async Task TestRecoveryAfterFailedExport() using var httpClient = new HttpClient() { BaseAddress = new Uri($"http://localhost:{testHttpPort}") }; var codes = new[] { Grpc.Core.StatusCode.Unimplemented, Grpc.Core.StatusCode.OK }; - await httpClient.GetAsync($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}"); + await httpClient.GetAsync(new Uri($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}", UriKind.Relative)); var exportResults = new List(); using var otlpExporter = new OtlpTraceExporter(new OtlpExporterOptions() { Endpoint = new Uri($"http://localhost:{testGrpcPort}") }); +#pragma warning disable CA2000 // Dispose objects before losing scope var delegatingExporter = new DelegatingExporter +#pragma warning disable CA2000 // Dispose objects before losing scope { - OnExportFunc = (batch) => + OnExportFunc = batch => { var result = otlpExporter.Export(batch); exportResults.Add(result); @@ -89,7 +91,9 @@ public async Task TestRecoveryAfterFailedExport() using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource(activitySourceName) +#pragma warning disable CA2000 // Dispose objects before losing scope .AddProcessor(new SimpleActivityExportProcessor(delegatingExporter)) +#pragma warning restore CA2000 // Dispose objects before losing scope .Build(); using var source = new ActivitySource(activitySourceName); @@ -159,7 +163,7 @@ public async Task GrpcRetryTests(bool useRetryTransmissionHandler, ExportResult "/MockCollector/SetResponseCodes/{responseCodesCsv}", (MockCollectorState collectorState, string responseCodesCsv) => { - var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x)).ToArray(); + var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x, CultureInfo.InvariantCulture)).ToArray(); collectorState.SetStatusCodes(codes); }); @@ -172,7 +176,7 @@ public async Task GrpcRetryTests(bool useRetryTransmissionHandler, ExportResult // First reply with failure and then Ok var codes = new[] { initialStatusCode, Grpc.Core.StatusCode.OK }; - await httpClient.GetAsync($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}"); + await httpClient.GetAsync(new Uri($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}", UriKind.Relative)); var endpoint = new Uri($"http://localhost:{testGrpcPort}"); @@ -242,7 +246,7 @@ public async Task HttpRetryTests(bool useRetryTransmissionHandler, ExportResult "/MockCollector/SetResponseCodes/{responseCodesCsv}", (MockCollectorHttpState collectorState, string responseCodesCsv) => { - var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x)).ToArray(); + var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x, CultureInfo.InvariantCulture)).ToArray(); collectorState.SetStatusCodes(codes); }); @@ -260,7 +264,7 @@ public async Task HttpRetryTests(bool useRetryTransmissionHandler, ExportResult using var httpClient = new HttpClient() { BaseAddress = new Uri($"http://localhost:{testHttpPort}") }; var codes = new[] { initialHttpStatusCode, HttpStatusCode.OK }; - await httpClient.GetAsync($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}"); + await httpClient.GetAsync(new Uri($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}", UriKind.Relative)); var endpoint = new Uri($"http://localhost:{testHttpPort}/v1/traces"); @@ -328,7 +332,7 @@ public async Task HttpPersistentStorageRetryTests(bool usePersistentStorageTrans "/MockCollector/SetResponseCodes/{responseCodesCsv}", (MockCollectorHttpState collectorState, string responseCodesCsv) => { - var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x)).ToArray(); + var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x, CultureInfo.InvariantCulture)).ToArray(); collectorState.SetStatusCodes(codes); }); @@ -346,13 +350,14 @@ public async Task HttpPersistentStorageRetryTests(bool usePersistentStorageTrans using var httpClient = new HttpClient() { BaseAddress = new Uri($"http://localhost:{testHttpPort}") }; var codes = new[] { initialHttpStatusCode, HttpStatusCode.OK }; - await httpClient.GetAsync($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}"); + await httpClient.GetAsync(new Uri($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}", UriKind.Relative)); var endpoint = new Uri($"http://localhost:{testHttpPort}/v1/traces"); var exporterOptions = new OtlpExporterOptions() { Endpoint = endpoint, TimeoutMilliseconds = 20000 }; - var exportClient = new OtlpHttpExportClient(exporterOptions, new HttpClient(), "/v1/traces"); + using var exporterHttpClient = new HttpClient(); + var exportClient = new OtlpHttpExportClient(exporterOptions, exporterHttpClient, "/v1/traces"); // TODO: update this to configure via experimental environment variable. OtlpExporterTransmissionHandler transmissionHandler; @@ -393,7 +398,7 @@ public async Task HttpPersistentStorageRetryTests(bool usePersistentStorageTrans Assert.NotNull(mockProvider); if (exportResult == ExportResult.Success) { - Assert.Single(mockProvider!.TryGetBlobs()); + Assert.Single(mockProvider.TryGetBlobs()); // Force Retry Assert.True((transmissionHandler as OtlpExporterPersistentStorageTransmissionHandler)?.InitiateAndWaitForRetryProcess(-1)); @@ -467,7 +472,7 @@ public async Task GrpcPersistentStorageRetryTests(bool usePersistentStorageTrans "/MockCollector/SetResponseCodes/{responseCodesCsv}", (MockCollectorState collectorState, string responseCodesCsv) => { - var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x)).ToArray(); + var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x, CultureInfo.InvariantCulture)).ToArray(); collectorState.SetStatusCodes(codes); }); @@ -479,13 +484,14 @@ public async Task GrpcPersistentStorageRetryTests(bool usePersistentStorageTrans using var httpClient = new HttpClient() { BaseAddress = new Uri($"http://localhost:{testHttpPort}") }; var codes = new[] { initialgrpcStatusCode, Grpc.Core.StatusCode.OK }; - await httpClient.GetAsync($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}"); + await httpClient.GetAsync(new Uri($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}", UriKind.Relative)); var endpoint = new Uri($"http://localhost:{testGrpcPort}"); var exporterOptions = new OtlpExporterOptions() { Endpoint = endpoint, TimeoutMilliseconds = 20000 }; - var exportClient = new OtlpGrpcExportClient(exporterOptions, new HttpClient(), "opentelemetry.proto.collector.trace.v1.TraceService/Export"); + using var exporterHttpClient = new HttpClient(); + var exportClient = new OtlpGrpcExportClient(exporterOptions, exporterHttpClient, "opentelemetry.proto.collector.trace.v1.TraceService/Export"); // TODO: update this to configure via experimental environment variable. OtlpExporterTransmissionHandler transmissionHandler; @@ -548,10 +554,10 @@ public async Task GrpcPersistentStorageRetryTests(bool usePersistentStorageTrans transmissionHandler.Dispose(); } - private class MockCollectorState + private sealed class MockCollectorState { - private Grpc.Core.StatusCode[] statusCodes = { }; - private int statusCodeIndex = 0; + private Grpc.Core.StatusCode[] statusCodes = []; + private int statusCodeIndex; public void SetStatusCodes(int[] statusCodes) { @@ -567,10 +573,10 @@ public Grpc.Core.StatusCode NextStatus() } } - private class MockCollectorHttpState + private sealed class MockCollectorHttpState { - private HttpStatusCode[] statusCodes = { }; - private int statusCodeIndex = 0; + private HttpStatusCode[] statusCodes = []; + private int statusCodeIndex; public void SetStatusCodes(int[] statusCodes) { @@ -586,7 +592,9 @@ public HttpStatusCode NextStatus() } } - private class MockTraceService : TraceService.TraceServiceBase +#pragma warning disable CA1812 // Avoid uninstantiated internal classes + private sealed class MockTraceService : TraceService.TraceServiceBase +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { private readonly MockCollectorState state; @@ -607,9 +615,9 @@ public override Task Export(ExportTraceServiceReques } } - private class MockFileProvider : PersistentBlobProvider + private sealed class MockFileProvider : PersistentBlobProvider { - private readonly List mockStorage = new(); + private readonly List mockStorage = []; public IEnumerable TryGetBlobs() => this.mockStorage.AsEnumerable(); @@ -638,11 +646,11 @@ protected override bool OnTryGetBlob([NotNullWhen(true)] out PersistentBlob? blo } } - private class MockFileBlob : PersistentBlob + private sealed class MockFileBlob : PersistentBlob { private readonly List mockStorage; - private byte[] buffer = Array.Empty(); + private byte[] buffer = []; public MockFileBlob(List mockStorage) { diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj index 59da00ba6aa..5541cabe5c6 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj @@ -1,7 +1,9 @@ - + $(TargetFrameworksForTests) + + false @@ -24,17 +26,14 @@ + - - - - - - - + + + @@ -47,7 +46,6 @@ - diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpAttributeTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpAttributeTests.cs index 05ffe584eb0..e260a4544d9 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpAttributeTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpAttributeTests.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics.CodeAnalysis; +using System.Globalization; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; using Xunit; using OtlpCommon = OpenTelemetry.Proto.Common.V1; @@ -14,7 +15,13 @@ public class OtlpAttributeTests public void NullValueAttribute() { var kvp = new KeyValuePair("key", null); - Assert.False(TryTransformTag(kvp, out _)); + Assert.True(TryTransformTag(kvp, out var attribute)); + Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.None, attribute.Value.ValueCase); + Assert.False(attribute.Value.HasBoolValue); + Assert.False(attribute.Value.HasBytesValue); + Assert.False(attribute.Value.HasDoubleValue); + Assert.False(attribute.Value.HasIntValue); + Assert.False(attribute.Value.HasStringValue); } [Fact] @@ -54,18 +61,27 @@ public void IntegralTypesSupported(object value) switch (value) { case Array array: - Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase); - var expectedArray = new long[array.Length]; - for (var i = 0; i < array.Length; i++) + if (value.GetType() == typeof(byte[])) + { + Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.BytesValue, attribute.Value.ValueCase); + Assert.Equal((byte[])value, attribute.Value.BytesValue.ToByteArray()); + } + else { - expectedArray[i] = Convert.ToInt64(array.GetValue(i)); + Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase); + var expectedArray = new long[array.Length]; + for (var i = 0; i < array.Length; i++) + { + expectedArray[i] = Convert.ToInt64(array.GetValue(i), CultureInfo.InvariantCulture); + } + + Assert.Equal(expectedArray, attribute.Value.ArrayValue.Values.Select(x => x.IntValue)); } - Assert.Equal(expectedArray, attribute.Value.ArrayValue.Values.Select(x => x.IntValue)); break; default: Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.IntValue, attribute.Value.ValueCase); - Assert.Equal(Convert.ToInt64(value), attribute.Value.IntValue); + Assert.Equal(Convert.ToInt64(value, CultureInfo.InvariantCulture), attribute.Value.IntValue); break; } } @@ -87,14 +103,14 @@ public void FloatingPointTypesSupported(object value) var expectedArray = new double[array.Length]; for (var i = 0; i < array.Length; i++) { - expectedArray[i] = Convert.ToDouble(array.GetValue(i)); + expectedArray[i] = Convert.ToDouble(array.GetValue(i), CultureInfo.InvariantCulture); } Assert.Equal(expectedArray, attribute.Value.ArrayValue.Values.Select(x => x.DoubleValue)); break; default: Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.DoubleValue, attribute.Value.ValueCase); - Assert.Equal(Convert.ToDouble(value), attribute.Value.DoubleValue); + Assert.Equal(Convert.ToDouble(value, CultureInfo.InvariantCulture), attribute.Value.DoubleValue); break; } } @@ -114,14 +130,14 @@ public void BooleanTypeSupported(object value) var expectedArray = new bool[array.Length]; for (var i = 0; i < array.Length; i++) { - expectedArray[i] = Convert.ToBoolean(array.GetValue(i)); + expectedArray[i] = Convert.ToBoolean(array.GetValue(i), CultureInfo.InvariantCulture); } Assert.Equal(expectedArray, attribute.Value.ArrayValue.Values.Select(x => x.BoolValue)); break; default: Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.BoolValue, attribute.Value.ValueCase); - Assert.Equal(Convert.ToBoolean(value), attribute.Value.BoolValue); + Assert.Equal(Convert.ToBoolean(value, CultureInfo.InvariantCulture), attribute.Value.BoolValue); break; } } @@ -134,7 +150,7 @@ public void StringTypesSupported(object value) var kvp = new KeyValuePair("key", value); Assert.True(TryTransformTag(kvp, out var attribute)); Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.StringValue, attribute.Value.ValueCase); - Assert.Equal(Convert.ToString(value), attribute.Value.StringValue); + Assert.Equal(Convert.ToString(value, CultureInfo.InvariantCulture), attribute.Value.StringValue); } [Fact] @@ -213,7 +229,7 @@ public void ToStringIsCalledForAllOtherTypes() (nint)int.MaxValue, (nuint)uint.MaxValue, decimal.MaxValue, - new object(), + new(), }; var testArrayValues = new object[] @@ -221,7 +237,7 @@ public void ToStringIsCalledForAllOtherTypes() new nint[] { 1, 2, 3 }, new nuint[] { 1, 2, 3 }, new decimal[] { 1, 2, 3 }, - new object?[] { new object[3], new object(), null }, + new object?[] { new object[3], new(), null }, }; foreach (var value in testValues) @@ -291,11 +307,11 @@ private static bool TryTransformTag(KeyValuePair tag, [NotNullW return false; } - private class MyToStringMethodThrowsAnException + private sealed class MyToStringMethodThrowsAnException { public override string ToString() { - throw new Exception("Nope."); + throw new InvalidOperationException("Nope."); } } } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExportProtocolParserTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExportProtocolParserTests.cs index b4b2917491b..c2fdc81eb2e 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExportProtocolParserTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExportProtocolParserTests.cs @@ -8,7 +8,9 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; public class OtlpExportProtocolParserTests { [Theory] +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning [InlineData("grpc", true, OtlpExportProtocol.Grpc)] +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning [InlineData("http/protobuf", true, OtlpExportProtocol.HttpProtobuf)] [InlineData("unsupported", false, default(OtlpExportProtocol))] public void TryParse_Protocol_MapsToCorrectValue(string protocol, bool expectedResult, OtlpExportProtocol expectedExportProtocol) diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterHelperExtensionsTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterHelperExtensionsTests.cs new file mode 100644 index 00000000000..4dcd98eb22d --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterHelperExtensionsTests.cs @@ -0,0 +1,138 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#if NET462_OR_GREATER || NETSTANDARD2_0 +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning +using System.Net.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Logs; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; +using Xunit; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; + +public class OtlpExporterHelperExtensionsTests +{ + [Fact] + public void OtlpExporter_Throws_OnGrpcWithDefaultFactory_ForTracing() + { + var services = new ServiceCollection(); + services.AddOpenTelemetry() + .WithTracing(tracing => tracing.AddOtlpExporter(options => options.Protocol = OtlpExportProtocol.Grpc)); + + using var sp = services.BuildServiceProvider(); + + Assert.Throws(() => sp.GetRequiredService()); + + var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder() + .AddOtlpExporter(o => o.Protocol = OtlpExportProtocol.Grpc); + + Assert.Throws(() => tracerProviderBuilder.Build()); + } + + [Fact] + public void OtlpExporter_Throws_OnGrpcWithDefaultFactory_ForMetrics() + { + var services = new ServiceCollection(); + + services.AddOpenTelemetry() + .WithMetrics(metrics => metrics.AddOtlpExporter(options => options.Protocol = OtlpExportProtocol.Grpc)); + + using var sp = services.BuildServiceProvider(); + + Assert.Throws(() => sp.GetRequiredService()); + + var meterProviderBuilder = Sdk.CreateMeterProviderBuilder() + .AddOtlpExporter(o => o.Protocol = OtlpExportProtocol.Grpc); + + Assert.Throws(() => meterProviderBuilder.Build()); + } + + [Fact] + public void OtlpExporter_Throws_OnGrpcWithDefaultFactory_ForLogging() + { + var services = new ServiceCollection(); + + services.AddOpenTelemetry() + .WithLogging(builder => builder.AddOtlpExporter(options => options.Protocol = OtlpExportProtocol.Grpc)); + + using var sp = services.BuildServiceProvider(); + + Assert.Throws(() => sp.GetRequiredService()); + + Assert.Throws(() => LoggerFactory.Create(builder => + { + builder.AddOpenTelemetry(logging => + { + logging.AddOtlpExporter(o => o.Protocol = OtlpExportProtocol.Grpc); + }); + })); + } + + [Fact] + public void OtlpExporter_DoesNotThrow_WhenCustomHttpClientFactoryIsSet_ForTraces() + { + var services = new ServiceCollection(); + + services.AddOpenTelemetry() + .WithTracing(builder => + { + builder.AddOtlpExporter(options => + { + options.Protocol = OtlpExportProtocol.Grpc; + options.HttpClientFactory = () => new HttpClient(); + }); + }); + + using var sp = services.BuildServiceProvider(); + + var tracerProvider = sp.GetRequiredService(); + Assert.NotNull(tracerProvider); + } + + [Fact] + public void OtlpExporter_DoesNotThrow_WhenCustomHttpClientFactoryIsSet_ForMetrics() + { + var services = new ServiceCollection(); + + services.AddOpenTelemetry() + .WithMetrics(builder => + { + builder.AddOtlpExporter(options => + { + options.Protocol = OtlpExportProtocol.Grpc; + options.HttpClientFactory = () => new HttpClient(); + }); + }); + + using var sp = services.BuildServiceProvider(); + + var meterProvider = sp.GetRequiredService(); + Assert.NotNull(meterProvider); + } + + [Fact] + public void OtlpExporter_DoesNotThrow_WhenCustomHttpClientFactoryIsSet_ForLogging() + { + var services = new ServiceCollection(); + + services.AddOpenTelemetry() + .WithLogging(builder => + { + builder.AddOtlpExporter(options => + { + options.Protocol = OtlpExportProtocol.Grpc; + options.HttpClientFactory = () => new HttpClient(); + }); + }); + + using var sp = services.BuildServiceProvider(); + + var loggerProvider = sp.GetRequiredService(); + Assert.NotNull(loggerProvider); + } +} +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning +#endif diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs index 636383f5a09..8e0c24601a4 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs @@ -35,11 +35,35 @@ public void GetHeaders_NoOptionHeaders_ReturnsStandardHeaders(string? optionHead } [Theory] -#if NET462_OR_GREATER - [InlineData(OtlpExportProtocol.Grpc, typeof(GrpcExportClient))] -#else + [InlineData(" ")] + [InlineData(",key1=value1,key2=value2,")] + [InlineData(",,key1=value1,,key2=value2,,")] + [InlineData("key1")] + public void GetHeaders_InvalidOptionHeaders_ThrowsArgumentException(string inputOptionHeaders) + { + VerifyHeaders(inputOptionHeaders, string.Empty, true); + } + + [Theory] + [InlineData("", "")] + [InlineData("key1=value1", "key1=value1")] + [InlineData("key1=value1,key2=value2", "key1=value1,key2=value2")] + [InlineData("key1=value1,key2=value2,key3=value3", "key1=value1,key2=value2,key3=value3")] + [InlineData(" key1 = value1 , key2=value2 ", "key1=value1,key2=value2")] + [InlineData("key1= value with spaces ,key2=another value", "key1=value with spaces,key2=another value")] + [InlineData("=value1", "=value1")] + [InlineData("key1=", "key1=")] + [InlineData("key1=value1%2Ckey2=value2", "key1=value1,key2=value2")] + [InlineData("key1=value1%2Ckey2=value2%2Ckey3=value3", "key1=value1,key2=value2,key3=value3")] + public void GetHeaders_ValidAndUrlEncodedHeaders_ReturnsCorrectHeaders(string inputOptionHeaders, string expectedNormalizedOptional) + { + VerifyHeaders(inputOptionHeaders, expectedNormalizedOptional); + } + + [Theory] +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning [InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcExportClient))] -#endif +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning [InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpExportClient))] public void GetTraceExportClient_SupportedProtocol_ReturnsCorrectExportClient(OtlpExportProtocol protocol, Type expectedExportClientType) { @@ -69,25 +93,21 @@ public void GetTraceExportClient_UnsupportedProtocol_Throws() [InlineData("http://test:8888/", "http://test:8888/v1/traces")] [InlineData("http://test:8888/v1/traces", "http://test:8888/v1/traces")] [InlineData("http://test:8888/v1/traces/", "http://test:8888/v1/traces/")] - public void AppendPathIfNotPresent_TracesPath_AppendsCorrectly(string inputUri, string expectedUri) + public void AppendPathIfNotPresent_TracesPath_AppendsCorrectly(string input, string expected) { - var uri = new Uri(inputUri, UriKind.Absolute); + var uri = new Uri(input, UriKind.Absolute); var resultUri = uri.AppendPathIfNotPresent("v1/traces"); - Assert.Equal(expectedUri, resultUri.AbsoluteUri); + Assert.Equal(expected, resultUri.AbsoluteUri); } [Theory] -#if NET462_OR_GREATER - [InlineData(OtlpExportProtocol.Grpc, typeof(GrpcExportClient), false, 10000, null)] - [InlineData(OtlpExportProtocol.Grpc, typeof(GrpcExportClient), false, 10000, "in_memory")] - [InlineData(OtlpExportProtocol.Grpc, typeof(GrpcExportClient), false, 10000, "disk")] -#else +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning [InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcExportClient), false, 10000, null)] [InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcExportClient), false, 10000, "in_memory")] [InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcExportClient), false, 10000, "disk")] -#endif +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning [InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpExportClient), false, 10000, null)] [InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpExportClient), true, 8000, null)] [InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpExportClient), false, 10000, "in_memory")] @@ -132,4 +152,50 @@ private static void AssertTransmissionHandler(OtlpExporterTransmissionHandler tr Assert.Equal(expectedTimeoutMilliseconds, transmissionHandler.TimeoutMilliseconds); } + + /// + /// Validates whether the `Headers` property in `OtlpExporterOptions` is correctly processed and parsed. + /// It also verifies that the extracted headers match the expected values and checks for expected exceptions. + /// + /// The raw header string assigned to `OtlpExporterOptions`. + /// The format should be "key1=value1,key2=value2" (comma-separated key-value pairs). + /// A string representing expected additional headers. + /// This will be parsed into a dictionary and compared with the actual extracted headers. + /// If `true`, the method expects `GetHeaders` to throw an `ArgumentException` + /// when processing `inputOptionHeaders`. + private static void VerifyHeaders(string inputOptionHeaders, string expectedNormalizedOptional, bool expectException = false) + { + var options = new OtlpExporterOptions { Headers = inputOptionHeaders }; + + if (expectException) + { + Assert.Throws(() => + options.GetHeaders>((d, k, v) => d.Add(k, v))); + return; + } + + var headers = options.GetHeaders>((d, k, v) => d.Add(k, v)); + var expectedOptional = new Dictionary(); + + if (!string.IsNullOrEmpty(expectedNormalizedOptional)) + { + foreach (var segment in expectedNormalizedOptional.Split([','], StringSplitOptions.RemoveEmptyEntries)) + { + var parts = segment.Split(['='], 2); + expectedOptional.Add(parts[0].Trim(), parts[1].Trim()); + } + } + + Assert.Equal(OtlpExporterOptions.StandardHeaders.Length + expectedOptional.Count, headers.Count); + + foreach (var kvp in expectedOptional) + { + Assert.Contains(headers, h => h.Key == kvp.Key && h.Value == kvp.Value); + } + + foreach (var std in OtlpExporterOptions.StandardHeaders) + { + Assert.Contains(headers, h => h.Key == std.Key && h.Value == std.Value); + } + } } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs index c22f0e14863..9536d283cf5 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs @@ -7,7 +7,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; [Collection("EnvVars")] -public class OtlpExporterOptionsTests : IDisposable +public sealed class OtlpExporterOptionsTests : IDisposable { public OtlpExporterOptionsTests() { @@ -24,7 +24,12 @@ public void OtlpExporterOptions_Defaults() { var options = new OtlpExporterOptions(); +#if NET462_OR_GREATER || NETSTANDARD2_0 + Assert.Equal(new Uri(OtlpExporterOptions.DefaultHttpEndpoint), options.Endpoint); +#else Assert.Equal(new Uri(OtlpExporterOptions.DefaultGrpcEndpoint), options.Endpoint); +#endif + Assert.Null(options.Headers); Assert.Equal(10000, options.TimeoutMilliseconds); Assert.Equal(OtlpExporterOptions.DefaultOtlpExportProtocol, options.Protocol); @@ -47,6 +52,14 @@ public void OtlpExporterOptions_DefaultsForHttpProtobuf() [ClassData(typeof(OtlpSpecConfigDefinitionTests))] public void OtlpExporterOptions_EnvironmentVariableOverride(object testDataObject) { +#if NET + Assert.NotNull(testDataObject); +#else + if (testDataObject == null) + { + throw new ArgumentNullException(nameof(testDataObject)); + } +#endif var testData = testDataObject as OtlpSpecConfigDefinitionTests.TestData; Assert.NotNull(testData); @@ -61,6 +74,14 @@ public void OtlpExporterOptions_EnvironmentVariableOverride(object testDataObjec [ClassData(typeof(OtlpSpecConfigDefinitionTests))] public void OtlpExporterOptions_UsingIConfiguration(object testDataObject) { +#if NET + Assert.NotNull(testDataObject); +#else + if (testDataObject == null) + { + throw new ArgumentNullException(nameof(testDataObject)); + } +#endif var testData = testDataObject as OtlpSpecConfigDefinitionTests.TestData; Assert.NotNull(testData); @@ -95,7 +116,12 @@ public void OtlpExporterOptions_InvalidEnvironmentVariableOverride() "NoopHeaders", "TimeoutWithInvalidValue"); +#if NET462_OR_GREATER || NETSTANDARD2_0 + Assert.Equal(new Uri(OtlpExporterOptions.DefaultHttpEndpoint), options.Endpoint); +#else Assert.Equal(new Uri(OtlpExporterOptions.DefaultGrpcEndpoint), options.Endpoint); +#endif + Assert.Equal(10000, options.TimeoutMilliseconds); Assert.Equal(OtlpExporterOptions.DefaultOtlpExportProtocol, options.Protocol); Assert.Null(options.Headers); @@ -143,14 +169,20 @@ public void OtlpExporterOptions_EndpointGetterUsesProtocolWhenNull() { var options = new OtlpExporterOptions(); +#if NET462_OR_GREATER || NETSTANDARD2_0 + Assert.Equal(new Uri(OtlpExporterOptions.DefaultHttpEndpoint), options.Endpoint); +#else Assert.Equal(new Uri(OtlpExporterOptions.DefaultGrpcEndpoint), options.Endpoint); +#endif + Assert.Equal(OtlpExporterOptions.DefaultOtlpExportProtocol, options.Protocol); options.Protocol = OtlpExportProtocol.HttpProtobuf; Assert.Equal(new Uri(OtlpExporterOptions.DefaultHttpEndpoint), options.Endpoint); - +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning options.Protocol = OtlpExportProtocol.Grpc; +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning Assert.Equal(new Uri(OtlpExporterOptions.DefaultGrpcEndpoint), options.Endpoint); } @@ -158,10 +190,12 @@ public void OtlpExporterOptions_EndpointGetterUsesProtocolWhenNull() [Fact] public void OtlpExporterOptions_EndpointThrowsWhenSetToNull() { +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning var options = new OtlpExporterOptions { Endpoint = new Uri("http://test:8888"), Protocol = OtlpExportProtocol.Grpc }; Assert.Equal(new Uri("http://test:8888"), options.Endpoint); Assert.Equal(OtlpExportProtocol.Grpc, options.Protocol); +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning } [Fact] @@ -210,7 +244,9 @@ public void OtlpExporterOptions_ApplyDefaultsTest() var targetOptionsWithData = new OtlpExporterOptions { Endpoint = new Uri("http://metrics_endpoint/"), +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning Protocol = OtlpExportProtocol.Grpc, +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning Headers = "key2=value2", TimeoutMilliseconds = 1800, HttpClientFactory = () => throw new NotImplementedException(), diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpHttpExportClientTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpHttpExportClientTests.cs index c21dbd8c8a0..5de79944839 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpHttpExportClientTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpHttpExportClientTests.cs @@ -30,7 +30,8 @@ public void ValidateOtlpHttpExportClientEndpoint(string? optionEndpoint, string? options.Endpoint = new Uri(optionEndpoint); } - var exporterClient = new OtlpHttpExportClient(options, new HttpClient(), "signal/path"); + using var httpClient = new HttpClient(); + var exporterClient = new OtlpHttpExportClient(options, httpClient, "signal/path"); Assert.Equal(new Uri(expectedExporterEndpoint), exporterClient.Endpoint); } finally diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs index 9d14aafefe4..c6f7f32f6d0 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs @@ -3,6 +3,7 @@ using System.Collections.ObjectModel; using System.Diagnostics; +using System.Globalization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -148,7 +149,7 @@ public void AddOtlpLogExporterReceivesAttributesWithParseStateValueSetToFalse(bo Assert.True(optionsValidated); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); - logger.LogInformation("Hello from {name} {price}.", "tomato", 2.99); + logger.HelloFrom("tomato", 2.99); Assert.Single(logRecords); var logRecord = logRecords[0]; #pragma warning disable CS0618 // Type or member is obsolete @@ -261,8 +262,7 @@ public void OtlpLogRecordTestWhenStateValuesArePopulated() }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); - logger.LogInformation("Hello from {name} {price}.", "tomato", 2.99); - + logger.HelloFrom("tomato", 2.99); Assert.Single(logRecords); var logRecord = logRecords[0]; @@ -274,16 +274,16 @@ public void OtlpLogRecordTestWhenStateValuesArePopulated() var index = 0; var attribute = otlpLogRecord.Attributes[index]; - Assert.Equal("name", attribute.Key); + Assert.Equal("Name", attribute.Key); Assert.Equal("tomato", attribute.Value.StringValue); attribute = otlpLogRecord.Attributes[++index]; - Assert.Equal("price", attribute.Key); + Assert.Equal("Price", attribute.Key); Assert.Equal(2.99, attribute.Value.DoubleValue); attribute = otlpLogRecord.Attributes[++index]; Assert.Equal("{OriginalFormat}", attribute.Key); - Assert.Equal("Hello from {name} {price}.", attribute.Value.StringValue); + Assert.Equal("Hello from {Name} {Price}.", attribute.Value.StringValue); } [Theory] @@ -305,7 +305,7 @@ public void CheckToOtlpLogRecordEventId(string? emitLogEventAttributes) }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); - logger.LogInformation(new EventId(10, null), "Hello from {name} {price}.", "tomato", 2.99); + logger.HelloFromWithEventId("tomato", 2.99); Assert.Single(logRecords); var configuration = new ConfigurationBuilder() @@ -313,7 +313,7 @@ public void CheckToOtlpLogRecordEventId(string? emitLogEventAttributes) .Build(); var logRecord = logRecords[0]; - OtlpLogs.LogRecord? otlpLogRecord = otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new(configuration), logRecord); + OtlpLogs.LogRecord? otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new(configuration), logRecord); Assert.NotNull(otlpLogRecord); Assert.Equal("Hello from tomato 2.99.", otlpLogRecord.Body.StringValue); @@ -322,17 +322,17 @@ public void CheckToOtlpLogRecordEventId(string? emitLogEventAttributes) var otlpLogRecordAttributes = otlpLogRecord.Attributes.ToString(); if (emitLogEventAttributes == "true") { - Assert.Contains(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes); - Assert.Contains("10", otlpLogRecordAttributes); + Assert.Contains(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes, StringComparison.Ordinal); + Assert.Contains("10", otlpLogRecordAttributes, StringComparison.Ordinal); } else { - Assert.DoesNotContain(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes); + Assert.DoesNotContain(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes, StringComparison.Ordinal); } logRecords.Clear(); - logger.LogInformation(new EventId(10, "MyEvent10"), "Hello from {name} {price}.", "tomato", 2.99); + logger.HelloFromWithEventIdAndEventName("tomato", 2.99); Assert.Single(logRecords); logRecord = logRecords[0]; @@ -342,19 +342,18 @@ public void CheckToOtlpLogRecordEventId(string? emitLogEventAttributes) Assert.NotNull(otlpLogRecord); Assert.Equal("Hello from tomato 2.99.", otlpLogRecord.Body.StringValue); + Assert.Equal("MyEvent10", otlpLogRecord.EventName); + // Event otlpLogRecordAttributes = otlpLogRecord.Attributes.ToString(); if (emitLogEventAttributes == "true") { - Assert.Contains(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes); - Assert.Contains("10", otlpLogRecordAttributes); - Assert.Contains(ExperimentalOptions.LogRecordEventNameAttribute, otlpLogRecordAttributes); - Assert.Contains("MyEvent10", otlpLogRecordAttributes); + Assert.Contains(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes, StringComparison.Ordinal); + Assert.Contains("10", otlpLogRecordAttributes, StringComparison.Ordinal); } else { - Assert.DoesNotContain(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes); - Assert.DoesNotContain(ExperimentalOptions.LogRecordEventNameAttribute, otlpLogRecordAttributes); + Assert.DoesNotContain(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes, StringComparison.Ordinal); } } @@ -368,7 +367,7 @@ public void CheckToOtlpLogRecordTimestamps() }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); - logger.LogInformation("Log message"); + logger.LogMessage(); var logRecord = logRecords[0]; OtlpLogs.LogRecord? otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord); @@ -388,7 +387,7 @@ public void CheckToOtlpLogRecordTraceIdSpanIdFlagWithNoActivity() }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); - logger.LogInformation("Log when there is no activity."); + logger.LogWhenThereIsNoActivity(); var logRecord = logRecords[0]; OtlpLogs.LogRecord? otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord); @@ -413,9 +412,10 @@ public void CheckToOtlpLogRecordSpanIdTraceIdAndFlag() ActivityTraceId expectedTraceId = default; ActivitySpanId expectedSpanId = default; - using (var activity = new Activity(Utils.GetCurrentMethodName()).Start()) + using (var activity = new Activity(Utils.GetCurrentMethodName())) { - logger.LogInformation("Log within an activity."); + activity.Start(); + logger.LogWithinAnActivity(); expectedTraceId = activity.TraceId; expectedSpanId = activity.SpanId; @@ -451,7 +451,7 @@ public void CheckToOtlpLogRecordSeverityLevelAndText(LogLevel logLevel) }); var logger = loggerFactory.CreateLogger("CheckToOtlpLogRecordSeverityLevelAndText"); - logger.Log(logLevel, "Hello from {name} {price}.", "tomato", 2.99); + logger.HelloFrom(logLevel, "tomato", 2.99); Assert.Single(logRecords); var logRecord = logRecords[0]; @@ -506,7 +506,7 @@ public void CheckToOtlpLogRecordBodyIsPopulated(bool includeFormattedMessage) var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); // Scenario 1 - Using ExtensionMethods on ILogger.Log - logger.LogInformation("OpenTelemetry {Greeting} {Subject}!", "Hello", "World"); + logger.OpenTelemetryGreeting("Hello", "World"); Assert.Single(logRecords); var logRecord = logRecords[0]; @@ -620,7 +620,7 @@ public void CheckToOtlpLogRecordExceptionAttributes() }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); - logger.LogInformation(new Exception("Exception Message"), "Exception Occurred"); + logger.ExceptionOccured(new InvalidOperationException("Exception Message")); var logRecord = logRecords[0]; var loggedException = logRecord.Exception; @@ -630,15 +630,15 @@ public void CheckToOtlpLogRecordExceptionAttributes() Assert.NotNull(otlpLogRecord); var otlpLogRecordAttributes = otlpLogRecord.Attributes.ToString(); - Assert.Contains(SemanticConventions.AttributeExceptionType, otlpLogRecordAttributes); + Assert.Contains(SemanticConventions.AttributeExceptionType, otlpLogRecordAttributes, StringComparison.Ordinal); Assert.NotNull(logRecord.Exception); - Assert.Contains(logRecord.Exception.GetType().Name, otlpLogRecordAttributes); + Assert.Contains(logRecord.Exception.GetType().Name, otlpLogRecordAttributes, StringComparison.Ordinal); - Assert.Contains(SemanticConventions.AttributeExceptionMessage, otlpLogRecordAttributes); - Assert.Contains(logRecord.Exception.Message, otlpLogRecordAttributes); + Assert.Contains(SemanticConventions.AttributeExceptionMessage, otlpLogRecordAttributes, StringComparison.Ordinal); + Assert.Contains(logRecord.Exception.Message, otlpLogRecordAttributes, StringComparison.Ordinal); - Assert.Contains(SemanticConventions.AttributeExceptionStacktrace, otlpLogRecordAttributes); - Assert.Contains(logRecord.Exception.ToInvariantString(), otlpLogRecordAttributes); + Assert.Contains(SemanticConventions.AttributeExceptionStacktrace, otlpLogRecordAttributes, StringComparison.Ordinal); + Assert.Contains(logRecord.Exception.ToInvariantString(), otlpLogRecordAttributes, StringComparison.Ordinal); } [Fact] @@ -659,7 +659,7 @@ public void CheckToOtlpLogRecordRespectsAttributeLimits() }); var logger = loggerFactory.CreateLogger(string.Empty); - logger.LogInformation("OpenTelemetry {AttributeOne} {AttributeTwo} {AttributeThree}!", "I'm an attribute", "I too am an attribute", "I get dropped :("); + logger.OpenTelemetryWithAttributes("I'm an attribute", "I too am an attribute", "I get dropped :("); var logRecord = logRecords[0]; OtlpLogs.LogRecord? otlpLogRecord = ToOtlpLogs(sdkLimitOptions, new(), logRecord); @@ -688,10 +688,10 @@ public void Export_WhenExportClientIsProvidedInCtor_UsesProvidedExportClient() // Arrange. var testExportClient = new TestExportClient(); var exporterOptions = new OtlpExporterOptions(); - var transmissionHandler = new OtlpExporterTransmissionHandler(testExportClient, exporterOptions.TimeoutMilliseconds); + using var transmissionHandler = new OtlpExporterTransmissionHandler(testExportClient, exporterOptions.TimeoutMilliseconds); var emptyLogRecords = Array.Empty(); var emptyBatch = new Batch(emptyLogRecords, emptyLogRecords.Length); - var sut = new OtlpLogExporter( + using var sut = new OtlpLogExporter( exporterOptions, new SdkLimitOptions(), new ExperimentalOptions(), @@ -710,10 +710,10 @@ public void Export_WhenExportClientThrowsException_ReturnsExportResultFailure() // Arrange. var testExportClient = new TestExportClient(throwException: true); var exporterOptions = new OtlpExporterOptions(); - var transmissionHandler = new OtlpExporterTransmissionHandler(testExportClient, exporterOptions.TimeoutMilliseconds); + using var transmissionHandler = new OtlpExporterTransmissionHandler(testExportClient, exporterOptions.TimeoutMilliseconds); var emptyLogRecords = Array.Empty(); var emptyBatch = new Batch(emptyLogRecords, emptyLogRecords.Length); - var sut = new OtlpLogExporter( + using var sut = new OtlpLogExporter( exporterOptions, new SdkLimitOptions(), new ExperimentalOptions(), @@ -732,10 +732,10 @@ public void Export_WhenExportIsSuccessful_ReturnsExportResultSuccess() // Arrange. var testExportClient = new TestExportClient(); var exporterOptions = new OtlpExporterOptions(); - var transmissionHandler = new OtlpExporterTransmissionHandler(testExportClient, exporterOptions.TimeoutMilliseconds); + using var transmissionHandler = new OtlpExporterTransmissionHandler(testExportClient, exporterOptions.TimeoutMilliseconds); var emptyLogRecords = Array.Empty(); var emptyBatch = new Batch(emptyLogRecords, emptyLogRecords.Length); - var sut = new OtlpLogExporter( + using var sut = new OtlpLogExporter( exporterOptions, new SdkLimitOptions(), new ExperimentalOptions(), @@ -767,10 +767,10 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsFalse_DoesNotContainScopeAttribu // Act. using (logger.BeginScope(new List> { - new KeyValuePair(expectedScopeKey, expectedScopeValue), + new(expectedScopeKey, expectedScopeValue), })) { - logger.LogInformation("Some log information message."); + logger.SomeLogInformation(); } // Assert. @@ -787,6 +787,15 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsFalse_DoesNotContainScopeAttribu [InlineData('a')] public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeStringValue(object scopeValue) { +#if NET + Assert.NotNull(scopeValue); +#else + if (scopeValue == null) + { + throw new ArgumentNullException(nameof(scopeValue)); + } +#endif + // Arrange. var logRecords = new List(1); using var loggerFactory = LoggerFactory.Create(builder => @@ -802,10 +811,10 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeStrin // Act. using (logger.BeginScope(new List> { - new KeyValuePair(scopeKey, scopeValue), + new(scopeKey, scopeValue), })) { - logger.LogInformation("Some log information message."); + logger.SomeLogInformation(); } // Assert. @@ -841,10 +850,10 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeBoolV // Act. using (logger.BeginScope(new List> { - new KeyValuePair(scopeKey, scopeValue), + new(scopeKey, scopeValue), })) { - logger.LogInformation("Some log information message."); + logger.SomeLogInformation(); } // Assert. @@ -877,6 +886,15 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeBoolV [InlineData(long.MaxValue)] public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeIntValue(object scopeValue) { +#if NET + Assert.NotNull(scopeValue); +#else + if (scopeValue == null) + { + throw new ArgumentNullException(nameof(scopeValue)); + } +#endif + // Arrange. var logRecords = new List(1); using var loggerFactory = LoggerFactory.Create(builder => @@ -892,10 +910,10 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeIntVa // Act. using (logger.BeginScope(new List> { - new KeyValuePair(scopeKey, scopeValue), + new(scopeKey, scopeValue), })) { - logger.LogInformation("Some log information message."); + logger.SomeLogInformation(); } // Assert. @@ -908,7 +926,7 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeIntVa Assert.NotNull(actualScope); Assert.Equal(scopeKey, actualScope.Key); Assert.Equal(ValueOneofCase.IntValue, actualScope.Value.ValueCase); - Assert.Equal(scopeValue.ToString(), actualScope.Value.IntValue.ToString()); + Assert.Equal(scopeValue.ToString(), actualScope.Value.IntValue.ToString(CultureInfo.InvariantCulture)); } [Theory] @@ -934,7 +952,7 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubl new(scopeKey, scopeValue), })) { - logger.LogInformation("Some log information message."); + logger.SomeLogInformation(); } // Assert. @@ -948,7 +966,7 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubl Assert.NotNull(actualScope); Assert.Equal(scopeKey, actualScope.Key); Assert.Equal(ValueOneofCase.DoubleValue, actualScope.Value.ValueCase); - Assert.Equal(((double)scopeValue).ToString(), actualScope.Value.DoubleValue.ToString()); + Assert.Equal(scopeValue, actualScope.Value.DoubleValue); } [Theory] @@ -974,7 +992,7 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubl new(scopeKey, scopeValue), })) { - logger.LogInformation("Some log information message."); + logger.SomeLogInformation(); } // Assert. @@ -986,7 +1004,7 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubl var actualScope = TryGetAttribute(otlpLogRecord, scopeKey); Assert.NotNull(actualScope); Assert.Equal(scopeKey, actualScope.Key); - Assert.Equal(scopeValue.ToString(), actualScope.Value.DoubleValue.ToString()); + Assert.Equal(scopeValue, actualScope.Value.DoubleValue); } [Fact] @@ -1007,7 +1025,7 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfTypeString // Act. using (logger.BeginScope(scopeState)) { - logger.LogInformation("Some log information message."); + logger.SomeLogInformation(); } // Assert. @@ -1042,7 +1060,7 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfPrimitiveT // Act. using (logger.BeginScope(scopeState)) { - logger.LogInformation("Some log information message."); + logger.SomeLogInformation(); } // Assert. @@ -1073,7 +1091,7 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfDictionary // Act. using (logger.BeginScope(scopeState)) { - logger.LogInformation("Some log information message."); + logger.SomeLogInformation(); } // Assert. @@ -1113,7 +1131,7 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfEnumerable Assert.NotNull(scopeState); using (logger.BeginScope(scopeState)) { - logger.LogInformation("Some log information message."); + logger.SomeLogInformation(); } // Assert. @@ -1149,11 +1167,11 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndMultipleScopesAreAdded_C // Act. using (logger.BeginScope(new List> { - new KeyValuePair(scopeKey1, scopeValue1), - new KeyValuePair(scopeKey2, scopeValue2), + new(scopeKey1, scopeValue1), + new(scopeKey2, scopeValue2), })) { - logger.LogInformation("Some log information message."); + logger.SomeLogInformation(); } // Assert. @@ -1189,11 +1207,11 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndMultipleScopeLevelsAreAd const string scopeValue2 = "Some other scope value"; // Act. - using (logger.BeginScope(new List> { new KeyValuePair(scopeKey1, scopeValue1) })) + using (logger.BeginScope(new List> { new(scopeKey1, scopeValue1) })) { - using (logger.BeginScope(new List> { new KeyValuePair(scopeKey2, scopeValue2) })) + using (logger.BeginScope(new List> { new(scopeKey2, scopeValue2) })) { - logger.LogInformation("Some log information message."); + logger.SomeLogInformation(); } } @@ -1232,14 +1250,14 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeIsUsedInLogMethod_C // Act. using (logger.BeginScope(new List> { - new KeyValuePair(scopeKey1, scopeValue1), + new(scopeKey1, scopeValue1), })) { logger.Log( LogLevel.Error, new EventId(1), - new List> { new KeyValuePair(scopeKey2, scopeValue2) }, - exception: new Exception("Some exception message"), + new List> { new(scopeKey2, scopeValue2) }, + exception: new InvalidOperationException("Some exception message"), formatter: (s, e) => string.Empty); } @@ -1336,10 +1354,10 @@ public void ValidateInstrumentationScope() }); var logger1 = loggerFactory.CreateLogger("OtlpLogExporterTests-A"); - logger1.LogInformation("Hello from red-tomato"); + logger1.HelloFromRedTomato(); var logger2 = loggerFactory.CreateLogger("OtlpLogExporterTests-B"); - logger2.LogInformation("Hello from green-tomato"); + logger2.HelloFromGreenTomato(); Assert.Equal(2, logRecords.Count); @@ -1449,7 +1467,7 @@ public void LogRecordLoggerNameIsExportedWhenUsingBridgeApi(string? loggerName, Assert.Single(logRecords); - var batch = new Batch(new[] { logRecords[0] }, 1); + var batch = new Batch([logRecords[0]], 1); OtlpCollector.ExportLogsServiceRequest request = CreateLogsExportRequest(DefaultSdkLimitOptions, new ExperimentalOptions(), batch, ResourceBuilder.CreateEmpty().Build()); Assert.NotNull(request); @@ -1459,6 +1477,49 @@ public void LogRecordLoggerNameIsExportedWhenUsingBridgeApi(string? loggerName, Assert.Equal(expectedScopeName, request.ResourceLogs[0].ScopeLogs[0].Scope?.Name); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void LogRecordEventNameIsExportedWhenUsingBridgeApi(bool emitEventName) + { + LogRecordAttributeList attributes = default; + attributes.Add("name", "tomato"); + attributes.Add("price", 2.99); + attributes.Add("{OriginalFormat}", "Hello from {name} {price}."); + + var logRecords = new List(); + + using (var loggerProvider = Sdk.CreateLoggerProviderBuilder() + .AddInMemoryExporter(logRecords) + .Build()) + { + var logger = loggerProvider.GetLogger(); + + logger.EmitLog(new LogRecordData + { + Body = "test body", + EventName = emitEventName ? "test event" : null, + }); + } + + Assert.Single(logRecords); + var logRecord = logRecords[0]; + + OtlpLogs.LogRecord? otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord); + + Assert.NotNull(otlpLogRecord); + Assert.Equal("test body", otlpLogRecord.Body.StringValue); + + if (!emitEventName) + { + Assert.Empty(otlpLogRecord.EventName); + } + else + { + Assert.Equal("test event", otlpLogRecord.EventName); + } + } + [Fact] public void LogSerialization_ExpandsBufferForLogsAndSerializes() { @@ -1480,7 +1541,7 @@ public void LogSerialization_ExpandsBufferForLogsAndSerializes() Assert.Single(logRecords); - var batch = new Batch(new[] { logRecords[0] }, 1); + var batch = new Batch([logRecords[0]], 1); var buffer = new byte[50]; var writePosition = ProtobufOtlpLogSerializer.WriteLogsData(ref buffer, 0, DefaultSdkLimitOptions, new(), ResourceBuilder.CreateEmpty().Build(), batch); diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs index 78c2d033383..52499046070 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs @@ -19,7 +19,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; [Collection("EnvVars")] -public class OtlpMetricsExporterTests : IDisposable +public sealed class OtlpMetricsExporterTests : IDisposable { private static readonly KeyValuePair[] KeyValues = [ @@ -917,7 +917,7 @@ public void MetricsSerialization_ExpandsBufferForMetricsAndSerializes() new("key2", "value2"), }; - using var meter = new Meter(name: $"{Utils.GetCurrentMethodName()}", version: "0.0.1", tags: meterTags); + using var meter = new Meter(name: Utils.GetCurrentMethodName(), version: "0.0.1", tags: meterTags); using var provider = Sdk.CreateMeterProviderBuilder() .AddMeter(meter.Name) .AddInMemoryExporter(metrics) diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpRetryTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpRetryTests.cs index b380d92d6b0..9e3a266a749 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpRetryTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpRetryTests.cs @@ -1,13 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -using System.Net; -using System.Net.Http.Headers; -#if NETFRAMEWORK -using System.Net.Http; -#endif -using Google.Protobuf; -using Google.Protobuf.WellKnownTypes; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc; using Xunit; @@ -15,14 +8,22 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClie public class OtlpRetryTests { - public static IEnumerable GrpcRetryTestData => GrpcRetryTestCase.GetGrpcTestCases(); + public static TheoryData GrpcRetryTestData => GrpcRetryTestCase.GetGrpcTestCases(); - public static IEnumerable HttpRetryTestData => HttpRetryTestCase.GetHttpTestCases(); + public static TheoryData HttpRetryTestData => HttpRetryTestCase.GetHttpTestCases(); [Theory] [MemberData(nameof(GrpcRetryTestData))] public void TryGetGrpcRetryResultTest(GrpcRetryTestCase testCase) { +#if NET + Assert.NotNull(testCase); +#else + if (testCase == null) + { + throw new ArgumentNullException(nameof(testCase)); + } +#endif var attempts = 0; var nextRetryDelayMilliseconds = OtlpRetry.InitialBackoffMilliseconds; @@ -65,6 +66,14 @@ public void TryGetGrpcRetryResultTest(GrpcRetryTestCase testCase) [MemberData(nameof(HttpRetryTestData))] public void TryGetHttpRetryResultTest(HttpRetryTestCase testCase) { +#if NET + Assert.NotNull(testCase); +#else + if (testCase == null) + { + throw new ArgumentNullException(nameof(testCase)); + } +#endif var attempts = 0; var nextRetryDelayMilliseconds = OtlpRetry.InitialBackoffMilliseconds; @@ -101,242 +110,4 @@ public void TryGetHttpRetryResultTest(HttpRetryTestCase testCase) Assert.Equal(testCase.ExpectedRetryAttempts, attempts); } - - public class GrpcRetryTestCase - { - public int ExpectedRetryAttempts; - public GrpcRetryAttempt[] RetryAttempts; - - private string testRunnerName; - - private GrpcRetryTestCase(string testRunnerName, GrpcRetryAttempt[] retryAttempts, int expectedRetryAttempts = 1) - { - this.ExpectedRetryAttempts = expectedRetryAttempts; - this.RetryAttempts = retryAttempts; - this.testRunnerName = testRunnerName; - } - - public static IEnumerable GetGrpcTestCases() - { - yield return new[] { new GrpcRetryTestCase("Cancelled", new GrpcRetryAttempt[] { new(StatusCode.Cancelled) }) }; - yield return new[] { new GrpcRetryTestCase("DeadlineExceeded", new GrpcRetryAttempt[] { new(StatusCode.DeadlineExceeded) }) }; - yield return new[] { new GrpcRetryTestCase("Aborted", new GrpcRetryAttempt[] { new(StatusCode.Aborted) }) }; - yield return new[] { new GrpcRetryTestCase("OutOfRange", new GrpcRetryAttempt[] { new(StatusCode.OutOfRange) }) }; - yield return new[] { new GrpcRetryTestCase("DataLoss", new GrpcRetryAttempt[] { new(StatusCode.DataLoss) }) }; - yield return new[] { new GrpcRetryTestCase("Unavailable", new GrpcRetryAttempt[] { new(StatusCode.Unavailable) }) }; - - yield return new[] { new GrpcRetryTestCase("OK", new GrpcRetryAttempt[] { new(StatusCode.OK, expectedSuccess: false) }) }; - yield return new[] { new GrpcRetryTestCase("PermissionDenied", new GrpcRetryAttempt[] { new(StatusCode.PermissionDenied, expectedSuccess: false) }) }; - yield return new[] { new GrpcRetryTestCase("Unknown", new GrpcRetryAttempt[] { new(StatusCode.Unknown, expectedSuccess: false) }) }; - - yield return new[] { new GrpcRetryTestCase("ResourceExhausted w/o RetryInfo", new GrpcRetryAttempt[] { new(StatusCode.ResourceExhausted, expectedSuccess: false) }) }; - yield return new[] { new GrpcRetryTestCase("ResourceExhausted w/ RetryInfo", new GrpcRetryAttempt[] { new(StatusCode.ResourceExhausted, throttleDelay: GetThrottleDelayString(new Duration { Seconds = 2 }), expectedNextRetryDelayMilliseconds: 3000) }) }; - - yield return new[] { new GrpcRetryTestCase("Unavailable w/ RetryInfo", new GrpcRetryAttempt[] { new(StatusCode.Unavailable, throttleDelay: GetThrottleDelayString(Duration.FromTimeSpan(TimeSpan.FromMilliseconds(2000))), expectedNextRetryDelayMilliseconds: 3000) }) }; - - yield return new[] { new GrpcRetryTestCase("Expired deadline", new GrpcRetryAttempt[] { new(StatusCode.Unavailable, deadlineExceeded: true, expectedSuccess: false) }) }; - - yield return new[] - { - new GrpcRetryTestCase( - "Exponential backoff", - new GrpcRetryAttempt[] - { - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1500), - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2250), - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 3375), - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000), - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000), - }, - expectedRetryAttempts: 5), - }; - - yield return new[] - { - new GrpcRetryTestCase( - "Retry until non-retryable status code encountered", - new GrpcRetryAttempt[] - { - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1500), - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2250), - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 3375), - new(StatusCode.PermissionDenied, expectedSuccess: false), - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000), - }, - expectedRetryAttempts: 4), - }; - - // Test throttling affects exponential backoff. - yield return new[] - { - new GrpcRetryTestCase( - "Exponential backoff after throttling", - new GrpcRetryAttempt[] - { - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1500), - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2250), - new(StatusCode.Unavailable, throttleDelay: GetThrottleDelayString(Duration.FromTimeSpan(TimeSpan.FromMilliseconds(500))), expectedNextRetryDelayMilliseconds: 750), - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1125), - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1688), - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2532), - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 3798), - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000), - new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000), - }, - expectedRetryAttempts: 9), - }; - } - - public override string ToString() - { - return this.testRunnerName; - } - - private static string GetThrottleDelayString(Duration throttleDelay) - { - var status = new Google.Rpc.Status - { - Code = 4, - Message = "Only nanos", - Details = - { - Any.Pack(new Google.Rpc.RetryInfo - { - RetryDelay = throttleDelay, - }), - }, - }; - - return Convert.ToBase64String(status.ToByteArray()); - } - - public struct GrpcRetryAttempt - { - public string? ThrottleDelay; - public int? ExpectedNextRetryDelayMilliseconds; - public bool ExpectedSuccess; - internal ExportClientGrpcResponse Response; - - internal GrpcRetryAttempt( - StatusCode statusCode, - bool deadlineExceeded = false, - string? throttleDelay = null, - int expectedNextRetryDelayMilliseconds = 1500, - bool expectedSuccess = true) - { - var status = new Status(statusCode, "Error"); - - // Using arbitrary +1 hr for deadline for test purposes. - var deadlineUtc = deadlineExceeded ? DateTime.UtcNow.AddSeconds(-1) : DateTime.UtcNow.AddHours(1); - - this.ThrottleDelay = throttleDelay; - - this.Response = new ExportClientGrpcResponse(expectedSuccess, deadlineUtc, null, status, this.ThrottleDelay); - - this.ExpectedNextRetryDelayMilliseconds = expectedNextRetryDelayMilliseconds; - - this.ExpectedSuccess = expectedSuccess; - } - } - } - - public class HttpRetryTestCase - { - public int ExpectedRetryAttempts; - internal HttpRetryAttempt[] RetryAttempts; - - private string testRunnerName; - - private HttpRetryTestCase(string testRunnerName, HttpRetryAttempt[] retryAttempts, int expectedRetryAttempts = 1) - { - this.ExpectedRetryAttempts = expectedRetryAttempts; - this.RetryAttempts = retryAttempts; - this.testRunnerName = testRunnerName; - } - - public static IEnumerable GetHttpTestCases() - { - yield return new[] { new HttpRetryTestCase("NetworkError", [new(statusCode: null)]) }; - yield return new[] { new HttpRetryTestCase("GatewayTimeout", [new(statusCode: HttpStatusCode.GatewayTimeout, throttleDelay: TimeSpan.FromSeconds(1))]) }; -#if NETSTANDARD2_1_OR_GREATER || NET - yield return new[] { new HttpRetryTestCase("ServiceUnavailable", [new(statusCode: HttpStatusCode.TooManyRequests, throttleDelay: TimeSpan.FromSeconds(1))]) }; -#endif - - yield return new[] - { - new HttpRetryTestCase( - "Exponential Backoff", - new HttpRetryAttempt[] - { - new(statusCode: null, expectedNextRetryDelayMilliseconds: 1500), - new(statusCode: null, expectedNextRetryDelayMilliseconds: 2250), - new(statusCode: null, expectedNextRetryDelayMilliseconds: 3375), - new(statusCode: null, expectedNextRetryDelayMilliseconds: 5000), - new(statusCode: null, expectedNextRetryDelayMilliseconds: 5000), - }, - expectedRetryAttempts: 5), - }; - - yield return new[] - { - new HttpRetryTestCase( - "Retry until non-retryable status code encountered", - new HttpRetryAttempt[] - { - new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 1500), - new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 2250), - new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 3375), - new(statusCode: HttpStatusCode.BadRequest, expectedSuccess: false), - new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 5000), - }, - expectedRetryAttempts: 4), - }; - - yield return new[] { new HttpRetryTestCase("Expired deadline", new HttpRetryAttempt[] { new(statusCode: HttpStatusCode.ServiceUnavailable, isDeadlineExceeded: true, expectedSuccess: false) }) }; - - // TODO: Add more cases. - } - - public override string ToString() - { - return this.testRunnerName; - } - - internal class HttpRetryAttempt - { - public ExportClientHttpResponse Response; - public TimeSpan? ThrottleDelay; - public int? ExpectedNextRetryDelayMilliseconds; - public bool ExpectedSuccess; - - internal HttpRetryAttempt( - HttpStatusCode? statusCode, - TimeSpan? throttleDelay = null, - bool isDeadlineExceeded = false, - int expectedNextRetryDelayMilliseconds = 1500, - bool expectedSuccess = true) - { - this.ThrottleDelay = throttleDelay; - - HttpResponseMessage? responseMessage = null; - if (statusCode != null) - { - responseMessage = new HttpResponseMessage(); - - if (throttleDelay != null) - { - responseMessage.Headers.RetryAfter = new RetryConditionHeaderValue(throttleDelay.Value); - } - - responseMessage.StatusCode = (HttpStatusCode)statusCode; - } - - // Using arbitrary +1 hr for deadline for test purposes. - var deadlineUtc = isDeadlineExceeded ? DateTime.UtcNow.AddMilliseconds(-1) : DateTime.UtcNow.AddHours(1); - this.Response = new ExportClientHttpResponse(expectedSuccess, deadlineUtc, responseMessage, new HttpRequestException()); - this.ExpectedNextRetryDelayMilliseconds = expectedNextRetryDelayMilliseconds; - this.ExpectedSuccess = expectedSuccess; - } - } - } } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpSpecConfigDefinitionTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpSpecConfigDefinitionTests.cs index 7ad28e67529..c5d1b0717a4 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpSpecConfigDefinitionTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpSpecConfigDefinitionTests.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Collections; +using System.Globalization; using Microsoft.Extensions.Configuration; using OpenTelemetry.Metrics; using Xunit; @@ -190,12 +191,13 @@ public IConfiguration ToConfiguration() public ConfigurationBuilder AddToConfiguration(ConfigurationBuilder configurationBuilder) { - Dictionary dictionary = new(); - - dictionary[this.EndpointKeyName] = this.EndpointValue; - dictionary[this.HeadersKeyName] = this.HeadersValue; - dictionary[this.TimeoutKeyName] = this.TimeoutValue; - dictionary[this.ProtocolKeyName] = this.ProtocolValue; + Dictionary dictionary = new() + { + [this.EndpointKeyName] = this.EndpointValue, + [this.HeadersKeyName] = this.HeadersValue, + [this.TimeoutKeyName] = this.TimeoutValue, + [this.ProtocolKeyName] = this.ProtocolValue, + }; this.OnAddToDictionary(dictionary); @@ -228,7 +230,7 @@ public void AssertMatches(IOtlpExporterOptions otlpExporterOptions) { Assert.Equal(new Uri(this.EndpointValue), otlpExporterOptions.Endpoint); Assert.Equal(this.HeadersValue, otlpExporterOptions.Headers); - Assert.Equal(int.Parse(this.TimeoutValue), otlpExporterOptions.TimeoutMilliseconds); + Assert.Equal(int.Parse(this.TimeoutValue, CultureInfo.InvariantCulture), otlpExporterOptions.TimeoutMilliseconds); if (!OtlpExportProtocolParser.TryParse(this.ProtocolValue, out var protocol)) { @@ -291,7 +293,11 @@ public MetricsTestData( public void AssertMatches(MetricReaderOptions metricReaderOptions) { +#if NET + Assert.Equal(Enum.Parse(this.TemporalityValue), metricReaderOptions.TemporalityPreference); +#else Assert.Equal(Enum.Parse(typeof(MetricReaderTemporalityPreference), this.TemporalityValue), metricReaderOptions.TemporalityPreference); +#endif } protected override void OnSetEnvVars() diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs index e96b5d43f44..d9e1fdb9a39 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs @@ -15,29 +15,39 @@ using OtlpCollector = OpenTelemetry.Proto.Collector.Trace.V1; using OtlpCommon = OpenTelemetry.Proto.Common.V1; using OtlpTrace = OpenTelemetry.Proto.Trace.V1; -using Status = OpenTelemetry.Trace.Status; namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; [Collection("xUnitCollectionPreventingTestsThatDependOnSdkConfigurationFromRunningInParallel")] -public class OtlpTraceExporterTests +public sealed class OtlpTraceExporterTests : IDisposable { private static readonly SdkLimitOptions DefaultSdkLimitOptions = new(); - private static readonly ExperimentalOptions DefaultExperimentalOptions = new(); + private readonly ActivityListener activityListener; + static OtlpTraceExporterTests() { Activity.DefaultIdFormat = ActivityIdFormat.W3C; Activity.ForceDefaultIdFormat = true; + } - var listener = new ActivityListener + public OtlpTraceExporterTests() + { + this.activityListener = new ActivityListener { ShouldListenTo = _ => true, - Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllData, + Sample = (ref ActivityCreationOptions options) => options.Parent.TraceFlags.HasFlag(ActivityTraceFlags.Recorded) + ? ActivitySamplingResult.AllDataAndRecorded + : ActivitySamplingResult.AllData, }; - ActivitySource.AddActivityListener(listener); + ActivitySource.AddActivityListener(this.activityListener); + } + + public void Dispose() + { + this.activityListener.Dispose(); } [Fact] @@ -151,7 +161,9 @@ public void ToOtlpResourceSpansTest(bool includeServiceNameInResource) .SetResourceBuilder(resourceBuilder) .AddSource(sources[0].Name) .AddSource(sources[1].Name) +#pragma warning disable CA2000 // Dispose objects before losing scope .AddProcessor(new SimpleActivityExportProcessor(new InMemoryExporter(exportedItems))); +#pragma warning restore CA2000 // Dispose objects before losing scope using var openTelemetrySdk = builder.Build(); @@ -238,7 +250,9 @@ public void ScopeAttributesRemainConsistentAcrossMultipleBatches() .SetResourceBuilder(resourceBuilder) .AddSource(activitySourceWithTags.Name) .AddSource(activitySourceWithoutTags.Name) +#pragma warning disable CA2000 // Dispose objects before losing scope .AddProcessor(new SimpleActivityExportProcessor(new InMemoryExporter(exportedItems))); +#pragma warning restore CA2000 // Dispose objects before losing scope using var openTelemetrySdk = builder.Build(); @@ -326,7 +340,9 @@ public void ScopeAttributesLimitsTest() var builder = Sdk.CreateTracerProviderBuilder() .SetResourceBuilder(resourceBuilder) .AddSource(activitySource.Name) +#pragma warning disable CA2000 // Dispose objects before losing scope .AddProcessor(new SimpleActivityExportProcessor(new InMemoryExporter(exportedItems))); +#pragma warning restore CA2000 // Dispose objects before losing scope using var openTelemetrySdk = builder.Build(); @@ -353,7 +369,7 @@ void RunTest(SdkLimitOptions sdkOptions, Batch batch) Assert.Equal(3, scope.Attributes.Count); Assert.Equal(1u, scope.DroppedAttributesCount); Assert.Equal("1234", scope.Attributes[0].Value.StringValue); - this.ArrayValueAsserts(scope.Attributes[1].Value.ArrayValue.Values); + ArrayValueAsserts(scope.Attributes[1].Value.ArrayValue.Values); Assert.Equal(new object().ToString()!.Substring(0, 4), scope.Attributes[2].Value.StringValue); } } @@ -400,7 +416,7 @@ public void SpanLimitsTest() Assert.Equal(3, otlpSpan.Attributes.Count); Assert.Equal(1u, otlpSpan.DroppedAttributesCount); Assert.Equal("1234", otlpSpan.Attributes[0].Value.StringValue); - this.ArrayValueAsserts(otlpSpan.Attributes[1].Value.ArrayValue.Values); + ArrayValueAsserts(otlpSpan.Attributes[1].Value.ArrayValue.Values); Assert.Equal(new object().ToString()!.Substring(0, 4), otlpSpan.Attributes[2].Value.StringValue); Assert.Single(otlpSpan.Events); @@ -408,7 +424,7 @@ public void SpanLimitsTest() Assert.Equal(3, otlpSpan.Events[0].Attributes.Count); Assert.Equal(1u, otlpSpan.Events[0].DroppedAttributesCount); Assert.Equal("1234", otlpSpan.Events[0].Attributes[0].Value.StringValue); - this.ArrayValueAsserts(otlpSpan.Events[0].Attributes[1].Value.ArrayValue.Values); + ArrayValueAsserts(otlpSpan.Events[0].Attributes[1].Value.ArrayValue.Values); Assert.Equal(new object().ToString()!.Substring(0, 4), otlpSpan.Events[0].Attributes[2].Value.StringValue); Assert.Single(otlpSpan.Links); @@ -416,7 +432,7 @@ public void SpanLimitsTest() Assert.Equal(3, otlpSpan.Links[0].Attributes.Count); Assert.Equal(1u, otlpSpan.Links[0].DroppedAttributesCount); Assert.Equal("1234", otlpSpan.Links[0].Attributes[0].Value.StringValue); - this.ArrayValueAsserts(otlpSpan.Links[0].Attributes[1].Value.ArrayValue.Values); + ArrayValueAsserts(otlpSpan.Links[0].Attributes[1].Value.ArrayValue.Values); Assert.Equal(new object().ToString()!.Substring(0, 4), otlpSpan.Links[0].Attributes[2].Value.StringValue); } @@ -427,6 +443,11 @@ public void ToOtlpSpanTest() using var rootActivity = activitySource.StartActivity("root", ActivityKind.Producer); + bool[] boolArray = [true, false]; + int[] intArray = [1, 2]; + double[] doubleArray = [1.0, 2.09]; + string[] stringArray = ["a", "b"]; + var attributes = new List> { new("bool", true), @@ -435,10 +456,10 @@ public void ToOtlpSpanTest() new("double", 3.14), new("int", 1), new("datetime", DateTime.UtcNow), - new("bool_array", new bool[] { true, false }), - new("int_array", new int[] { 1, 2 }), - new("double_array", new double[] { 1.0, 2.09 }), - new("string_array", new string[] { "a", "b" }), + new("bool_array", boolArray), + new("int_array", intArray), + new("double_array", doubleArray), + new("string_array", stringArray), new("datetime_array", new DateTime[] { DateTime.UtcNow, DateTime.Now }), }; @@ -489,7 +510,7 @@ public void ToOtlpSpanTest() Assert.NotNull(childActivity); - childActivity.SetStatus(ActivityStatusCode.Error); + childActivity.SetStatus(ActivityStatusCode.Error, new string('a', 150)); var childEvents = new List { new("e0"), new("e1", default, new ActivityTagsCollection(attributes)) }; childActivity.AddEvent(childEvents[0]); @@ -510,7 +531,7 @@ public void ToOtlpSpanTest() Assert.NotNull(otlpSpan.Status); Assert.Equal(OtlpTrace.Status.Types.StatusCode.Error, otlpSpan.Status.Code); - Assert.Equal(Status.Error.Description ?? string.Empty, otlpSpan.Status.Message); + Assert.Equal(childActivity.StatusDescription ?? string.Empty, otlpSpan.Status.Message); Assert.Empty(otlpSpan.Attributes); Assert.Equal(childEvents.Count, otlpSpan.Events.Count); @@ -561,6 +582,7 @@ public void ToOtlpSpanActivitiesWithNullArrayTest() [InlineData(ActivityStatusCode.Unset, "Description will be ignored if status is Unset.")] [InlineData(ActivityStatusCode.Ok, "Description will be ignored if status is Okay.")] [InlineData(ActivityStatusCode.Error, "Description will be kept if status is Error.")] + [InlineData(ActivityStatusCode.Error, "150 Character String - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")] public void ToOtlpSpanNativeActivityStatusTest(ActivityStatusCode expectedStatusCode, string statusDescription) { using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest)); @@ -634,6 +656,7 @@ void RunTest(SdkLimitOptions sdkOptions, Batch batch) [InlineData(StatusCode.Unset, "Unset", "Description will be ignored if status is Unset.")] [InlineData(StatusCode.Ok, "Ok", "Description must only be used with the Error StatusCode.")] [InlineData(StatusCode.Error, "Error", "Error description.")] + [InlineData(StatusCode.Error, "Error", "150 Character String - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")] [Obsolete("Remove when ActivityExtensions status APIs are removed")] public void ToOtlpSpanStatusTagTest(StatusCode expectedStatusCode, string statusCodeTagValue, string statusDescription) { @@ -749,7 +772,9 @@ public void ToOtlpSpanTraceStateTest(bool traceStateWasSet) public void UseOpenTelemetryProtocolActivityExporterWithCustomActivityProcessor() { const string ActivitySourceName = "otlp.test"; +#pragma warning disable CA2000 // Dispose objects before losing scope TestActivityProcessor testActivityProcessor = new TestActivityProcessor(); +#pragma warning restore CA2000 // Dispose objects before losing scope bool startCalled = false; bool endCalled = false; @@ -786,7 +811,7 @@ public void Shutdown_ClientShutdownIsCalled() var exportClientMock = new TestExportClient(); var exporterOptions = new OtlpExporterOptions(); - var transmissionHandler = new OtlpExporterTransmissionHandler(exportClientMock, exporterOptions.TimeoutMilliseconds); + using var transmissionHandler = new OtlpExporterTransmissionHandler(exportClientMock, exporterOptions.TimeoutMilliseconds); using var exporter = new OtlpTraceExporter(new OtlpExporterOptions(), DefaultSdkLimitOptions, DefaultExperimentalOptions, transmissionHandler); exporter.Shutdown(); @@ -1018,7 +1043,7 @@ private static OtlpCollector.ExportTraceServiceRequest CreateTraceExportRequest( return request; } - private void ArrayValueAsserts(RepeatedField values) + private static void ArrayValueAsserts(RepeatedField values) { var expectedStringArray = new string?[] { "1234", "1234", string.Empty, null }; for (var i = 0; i < expectedStringArray.Length; ++i) diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestExportClient.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestExportClient.cs index 8879f540210..7141ffd8c7d 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestExportClient.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestExportClient.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; -internal class TestExportClient(bool throwException = false) : IExportClient +internal sealed class TestExportClient(bool throwException = false) : IExportClient { public bool SendExportRequestCalled { get; private set; } @@ -17,7 +17,7 @@ public ExportClientResponse SendExportRequest(byte[] buffer, int contentLength, { if (this.ThrowException) { - throw new Exception("Exception thrown from SendExportRequest"); + throw new InvalidOperationException("Exception thrown from SendExportRequest"); } this.SendExportRequestCalled = true; @@ -30,7 +30,7 @@ public bool Shutdown(int timeoutMilliseconds) return true; } - private class TestExportClientResponse : ExportClientResponse + private sealed class TestExportClientResponse : ExportClientResponse { public TestExportClientResponse(bool success, DateTime deadline, Exception? exception) : base(success, deadline, exception) diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestHttpMessageHandler.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestHttpMessageHandler.cs index 2e5d6ede2b0..980d17bad14 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestHttpMessageHandler.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestHttpMessageHandler.cs @@ -7,16 +7,20 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; -internal class TestHttpMessageHandler : HttpMessageHandler +internal sealed class TestHttpMessageHandler : HttpMessageHandler { public HttpRequestMessage? HttpRequestMessage { get; private set; } public byte[]? HttpRequestContent { get; private set; } - public virtual HttpResponseMessage InternalSend(HttpRequestMessage request, CancellationToken cancellationToken) + public HttpResponseMessage InternalSend(HttpRequestMessage request, CancellationToken cancellationToken) { this.HttpRequestMessage = request; +#if NET + this.HttpRequestContent = request.Content!.ReadAsByteArrayAsync(cancellationToken).Result; +#else this.HttpRequestContent = request.Content!.ReadAsByteArrayAsync().Result; +#endif return new HttpResponseMessage(); } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/UseOtlpExporterExtensionTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/UseOtlpExporterExtensionTests.cs index 467ed6c0c25..2bb8245b657 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/UseOtlpExporterExtensionTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/UseOtlpExporterExtensionTests.cs @@ -14,7 +14,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; [Collection("EnvVars")] -public class UseOtlpExporterExtensionTests : IDisposable +public sealed class UseOtlpExporterExtensionTests : IDisposable { public UseOtlpExporterExtensionTests() { @@ -38,7 +38,12 @@ public void UseOtlpExporterDefaultTest() var exporterOptions = sp.GetRequiredService>().CurrentValue; +#if NET462_OR_GREATER || NETSTANDARD2_0 + Assert.Equal(new Uri(OtlpExporterOptions.DefaultHttpEndpoint), exporterOptions.DefaultOptions.Endpoint); +#else Assert.Equal(new Uri(OtlpExporterOptions.DefaultGrpcEndpoint), exporterOptions.DefaultOptions.Endpoint); +#endif + Assert.Equal(OtlpExporterOptions.DefaultOtlpExportProtocol, exporterOptions.DefaultOptions.Protocol); Assert.False(((OtlpExporterOptions)exporterOptions.DefaultOptions).HasData); @@ -48,7 +53,9 @@ public void UseOtlpExporterDefaultTest() } [Theory] +#pragma warning disable CS0618 // Suppressing gRPC obsolete warning [InlineData(OtlpExportProtocol.Grpc)] +#pragma warning restore CS0618 // Suppressing gRPC obsolete warning [InlineData(OtlpExportProtocol.HttpProtobuf)] public void UseOtlpExporterSetEndpointAndProtocolTest(OtlpExportProtocol protocol) { diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj index 6c4ea8cb626..e5d97abe21b 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj @@ -8,9 +8,6 @@ - - - @@ -28,7 +25,7 @@ - + diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs index c34dba29582..98d503e8e92 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs @@ -3,6 +3,7 @@ #if !NETFRAMEWORK using System.Diagnostics.Metrics; +using System.Globalization; using System.Net; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Builder; @@ -112,7 +113,7 @@ public Task PrometheusExporterMiddlewareIntegration_MixedPredicateAndPath() { if (!rsp.Headers.TryGetValues("X-MiddlewareExecuted", out IEnumerable? headers)) { - headers = Array.Empty(); + headers = []; } Assert.Equal("true", headers.FirstOrDefault()); @@ -139,7 +140,7 @@ public Task PrometheusExporterMiddlewareIntegration_MixedPath() { if (!rsp.Headers.TryGetValues("X-MiddlewareExecuted", out IEnumerable? headers)) { - headers = Array.Empty(); + headers = []; } Assert.Equal("true", headers.FirstOrDefault()); @@ -314,7 +315,7 @@ public async Task PrometheusExporterMiddlewareIntegration_TestBufferSizeIncrease using var client = host.GetTestClient(); - using var response = await client.GetAsync("/metrics"); + using var response = await client.GetAsync(new Uri("/metrics", UriKind.Relative)); var text = await response.Content.ReadAsStringAsync(); Assert.NotEmpty(text); @@ -372,7 +373,7 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( string acceptHeader = "application/openmetrics-text", KeyValuePair[]? meterTags = null) { - var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text"); + var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text", StringComparison.Ordinal); using var host = await StartTestHostAsync(configure, configureServices, registerMeterProvider, configureOptions); @@ -400,7 +401,7 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( client.DefaultRequestHeaders.Add("Accept", acceptHeader); } - using var response = await client.GetAsync(path); + using var response = await client.GetAsync(new Uri(path, UriKind.Relative)); var endTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); @@ -432,7 +433,7 @@ private static async Task VerifyAsync(long beginTimestamp, long endTimestamp, Ht Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType!.ToString()); } - var additionalTags = meterTags != null && meterTags.Any() + var additionalTags = meterTags is { Length: > 0 } ? $"{string.Join(",", meterTags.Select(x => $"{x.Key}=\"{x.Value}\""))}," : string.Empty; @@ -464,7 +465,7 @@ private static async Task VerifyAsync(long beginTimestamp, long endTimestamp, Ht Assert.True(matches.Count == 1, content); - var timestamp = long.Parse(matches[0].Groups[1].Value.Replace(".", string.Empty)); + var timestamp = long.Parse(matches[0].Groups[1].Value.Replace(".", string.Empty, StringComparison.Ordinal), CultureInfo.InvariantCulture); Assert.True(beginTimestamp <= timestamp && timestamp <= endTimestamp, $"{beginTimestamp} {timestamp} {endTimestamp}"); } diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/EventSourceTest.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/EventSourceTests.cs similarity index 92% rename from test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/EventSourceTest.cs rename to test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/EventSourceTests.cs index baf8dc43377..b1aed407d63 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/EventSourceTest.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/EventSourceTests.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Exporter.Prometheus.Tests; -public class EventSourceTest +public class EventSourceTests { [Fact] public void EventSourceTest_PrometheusExporterEventSource() diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj index 6d6c38ff489..55d40b0afe9 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj @@ -4,14 +4,10 @@ Unit test project for Prometheus Exporter HttpListener for OpenTelemetry $(TargetFrameworksForTests) $(DefineConstants);PROMETHEUS_HTTP_LISTENER + + false - - - - - - diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs index 36f5e124e67..75e3261b71a 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs @@ -31,7 +31,9 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon #endif .Build()) { +#pragma warning disable CA2000 // Dispose objects before losing scope if (!provider.TryFindExporter(out PrometheusExporter? exporter)) +#pragma warning restore CA2000 // Dispose objects before losing scope { throw new InvalidOperationException("PrometheusExporter could not be found on MeterProvider."); } @@ -60,7 +62,7 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon return new Response { CollectionResponse = response, - ViewPayload = openMetricsRequested ? response.OpenMetricsView.ToArray() : response.PlainTextView.ToArray(), + ViewPayload = openMetricsRequested ? [.. response.OpenMetricsView] : [.. response.PlainTextView], }; } finally @@ -110,7 +112,10 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon exporter.CollectionManager.ExitCollect(); } +#pragma warning disable CA1849 // 'Thread.Sleep(int)' synchronously blocks. Use await instead. + // Changing to await Task.Delay leads to test instability. Thread.Sleep(exporter.ScrapeResponseCacheDurationMilliseconds); +#pragma warning restore CA1849 // 'Thread.Sleep(int)' synchronously blocks. Use await instead. counter.Add(100); @@ -118,13 +123,13 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon { collectTasks[i] = Task.Run(async () => { - var response = await exporter.CollectionManager.EnterCollect(openMetricsRequested); + var collectionResponse = await exporter.CollectionManager.EnterCollect(openMetricsRequested); try { return new Response { - CollectionResponse = response, - ViewPayload = openMetricsRequested ? response.OpenMetricsView.ToArray() : response.PlainTextView.ToArray(), + CollectionResponse = collectionResponse, + ViewPayload = openMetricsRequested ? [.. collectionResponse.OpenMetricsView] : [.. collectionResponse.PlainTextView], }; } finally @@ -152,7 +157,7 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon } } - private class Response + private sealed class Response { public PrometheusCollectionManager.CollectionResponse CollectionResponse; diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs index b9b6201183e..7ff58940692 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics.Metrics; +using System.Globalization; using System.Net; #if NETFRAMEWORK using System.Net.Http; @@ -47,7 +48,7 @@ public void UriPrefixesEmptyList() { Assert.Throws(() => { - TestPrometheusHttpListenerUriPrefixOptions(new string[] { }); + TestPrometheusHttpListenerUriPrefixOptions([]); }); } @@ -56,32 +57,32 @@ public void UriPrefixesInvalid() { Assert.Throws(() => { - TestPrometheusHttpListenerUriPrefixOptions(new string[] { "ftp://example.com" }); + TestPrometheusHttpListenerUriPrefixOptions(["ftp://example.com"]); }); } [Fact] public async Task PrometheusExporterHttpServerIntegration() { - await this.RunPrometheusExporterHttpServerIntegrationTest(); + await RunPrometheusExporterHttpServerIntegrationTest(); } [Fact] public async Task PrometheusExporterHttpServerIntegration_NoMetrics() { - await this.RunPrometheusExporterHttpServerIntegrationTest(skipMetrics: true); + await RunPrometheusExporterHttpServerIntegrationTest(skipMetrics: true); } [Fact] public async Task PrometheusExporterHttpServerIntegration_NoOpenMetrics() { - await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty); + await RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty); } [Fact] public async Task PrometheusExporterHttpServerIntegration_UseOpenMetricsVersionHeader() { - await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: "application/openmetrics-text; version=1.0.0"); + await RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: "application/openmetrics-text; version=1.0.0"); } [Fact] @@ -93,7 +94,7 @@ public async Task PrometheusExporterHttpServerIntegration_NoOpenMetrics_WithMete new("meter2", "value2"), }; - await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty, meterTags: tags); + await RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty, meterTags: tags); } [Fact] @@ -105,7 +106,7 @@ public async Task PrometheusExporterHttpServerIntegration_OpenMetrics_WithMeterT new("meter2", "value2"), }; - await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: "application/openmetrics-text; version=1.0.0", meterTags: tags); + await RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: "application/openmetrics-text; version=1.0.0", meterTags: tags); } [Fact] @@ -113,7 +114,6 @@ public void PrometheusHttpListenerThrowsOnStart() { Random random = new Random(); int retryAttempts = 5; - int port = 0; string? address = null; PrometheusExporter? exporter = null; @@ -122,7 +122,9 @@ public void PrometheusHttpListenerThrowsOnStart() // Step 1: Start a listener on a random port. while (retryAttempts-- != 0) { - port = random.Next(2000, 5000); +#pragma warning disable CA5394 // Do not use insecure randomness + int port = random.Next(2000, 5000); +#pragma warning restore CA5394 // Do not use insecure randomness address = $"http://localhost:{port}/"; try @@ -132,7 +134,7 @@ public void PrometheusHttpListenerThrowsOnStart() exporter, new() { - UriPrefixes = new string[] { address }, + UriPrefixes = [address], }); listener.Start(); @@ -158,7 +160,7 @@ public void PrometheusHttpListenerThrowsOnStart() exporter, new() { - UriPrefixes = new string[] { address! }, + UriPrefixes = [address!], }); listener.Start(); @@ -179,7 +181,7 @@ public async Task PrometheusExporterHttpServerIntegration_TestBufferSizeIncrease var oneKb = new string('A', 1024); for (var x = 0; x < 8500; x++) { - attributes.Add(new KeyValuePair(x.ToString(), oneKb)); + attributes.Add(new KeyValuePair(x.ToString(CultureInfo.InvariantCulture), oneKb)); } var provider = BuildMeterProvider(meter, attributes, out var address); @@ -197,11 +199,11 @@ public async Task PrometheusExporterHttpServerIntegration_TestBufferSizeIncrease client.DefaultRequestHeaders.Add("Accept", acceptHeader); } - using var response = await client.GetAsync($"{address}metrics"); + using var response = await client.GetAsync(new Uri($"{address}metrics")); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var content = await response.Content.ReadAsStringAsync(); - Assert.Contains("counter_double_999", content); + Assert.Contains("counter_double_999", content, StringComparison.Ordinal); Assert.DoesNotContain('\0', content); provider.Dispose(); @@ -222,13 +224,14 @@ private static MeterProvider BuildMeterProvider(Meter meter, IEnumerable x.Clear().AddService("my_service", serviceInstanceId: "id1").AddAttributes(attributes)) .AddPrometheusHttpListener(options => { - options.UriPrefixes = new string[] { generatedAddress }; + options.UriPrefixes = [generatedAddress]; }) .Build(); @@ -252,17 +255,12 @@ private static MeterProvider BuildMeterProvider(Meter meter, IEnumerable[]? meterTags = null) + private static async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text", KeyValuePair[]? meterTags = null) { - var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text"); + var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text", StringComparison.Ordinal); using var meter = new Meter(MeterName, MeterVersion, meterTags); @@ -288,7 +286,7 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri client.DefaultRequestHeaders.Add("Accept", acceptHeader); } - using var response = await client.GetAsync($"{address}metrics"); + using var response = await client.GetAsync(new Uri($"{address}metrics")); if (!skipMetrics) { @@ -304,7 +302,7 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType!.ToString()); } - var additionalTags = meterTags != null && meterTags.Any() + var additionalTags = meterTags is { Length: > 0 } ? $"{string.Join(",", meterTags.Select(x => $"{x.Key}='{x.Value}'"))}," : string.Empty; diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/EventSourceTest.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/EventSourceTests.cs similarity index 93% rename from test/OpenTelemetry.Exporter.Zipkin.Tests/EventSourceTest.cs rename to test/OpenTelemetry.Exporter.Zipkin.Tests/EventSourceTests.cs index 0394d1a142d..2e7ef5fab46 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/EventSourceTest.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/EventSourceTests.cs @@ -7,7 +7,7 @@ namespace OpenTelemetry.Exporter.Zipkin.Tests; -public class EventSourceTest +public class EventSourceTests { [Fact] public void EventSourceTest_ZipkinExporterEventSource() diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/RemoteEndpointPriorityTestCase.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/RemoteEndpointPriorityTestCase.cs new file mode 100644 index 00000000000..527961bc816 --- /dev/null +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/RemoteEndpointPriorityTestCase.cs @@ -0,0 +1,160 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.Trace; +using Xunit; + +namespace OpenTelemetry.Exporter.Zipkin.Implementation.Tests; + +#pragma warning disable CA1515 // Consider making public types internal +public class RemoteEndpointPriorityTestCase +#pragma warning restore CA1515 // Consider making public types internal +{ + public static TheoryData TestCases => + [ + new() + { + Name = "Rank 1: Only peer.service provided", + ExpectedResult = "PeerService", + RemoteEndpointAttributes = new Dictionary + { + [SemanticConventions.AttributePeerService] = "PeerService", + }, + }, + new() + { + Name = "Rank 2: Only server.address provided", + ExpectedResult = "ServerAddress", + RemoteEndpointAttributes = new Dictionary + { + [SemanticConventions.AttributeServerAddress] = "ServerAddress", + }, + }, + new() + { + Name = "Rank 3: Only net.peer.name provided", + ExpectedResult = "NetPeerName", + RemoteEndpointAttributes = new Dictionary + { + [SemanticConventions.AttributeNetPeerName] = "NetPeerName", + }, + }, + new() + { + Name = "Rank 4: network.peer.address and network.peer.port provided", + ExpectedResult = "1.2.3.4:5678", + RemoteEndpointAttributes = new Dictionary + { + [SemanticConventions.AttributeNetworkPeerAddress] = "1.2.3.4", + [SemanticConventions.AttributeNetworkPeerPort] = "5678", + }, + }, + new() + { + Name = "Rank 4: Only network.peer.address provided", + ExpectedResult = "1.2.3.4", + RemoteEndpointAttributes = new Dictionary + { + [SemanticConventions.AttributeNetworkPeerAddress] = "1.2.3.4", + }, + }, + new() + { + Name = "Rank 5: Only server.socket.domain provided", + ExpectedResult = "SocketDomain", + RemoteEndpointAttributes = new Dictionary + { + [SemanticConventions.AttributeServerSocketDomain] = "SocketDomain", + }, + }, + new() + { + Name = "Rank 6: server.socket.address and server.socket.port provided", + ExpectedResult = "SocketAddress:4321", + RemoteEndpointAttributes = new Dictionary + { + [SemanticConventions.AttributeServerSocketAddress] = "SocketAddress", + [SemanticConventions.AttributeServerSocketPort] = "4321", + }, + }, + new() + { + Name = "Rank 7: Only net.sock.peer.name provided", + ExpectedResult = "NetSockPeerName", + RemoteEndpointAttributes = new Dictionary + { + [SemanticConventions.AttributeNetSockPeerName] = "NetSockPeerName", + }, + }, + new() + { + Name = "Rank 8: net.sock.peer.addr and net.sock.peer.port provided", + ExpectedResult = "5.6.7.8:8765", + RemoteEndpointAttributes = new Dictionary + { + [SemanticConventions.AttributeNetSockPeerAddr] = "5.6.7.8", + [SemanticConventions.AttributeNetSockPeerPort] = "8765", + }, + }, + new() + { + Name = "Rank 9: Only peer.hostname provided", + ExpectedResult = "PeerHostname", + RemoteEndpointAttributes = new Dictionary + { + [SemanticConventions.AttributePeerHostname] = "PeerHostname", + }, + }, + new() + { + Name = "Rank 10: Only peer.address provided", + ExpectedResult = "PeerAddress", + RemoteEndpointAttributes = new Dictionary + { + [SemanticConventions.AttributePeerAddress] = "PeerAddress", + }, + }, + new() + { + Name = "Rank 11: Only db.name provided", + ExpectedResult = "DbName", + RemoteEndpointAttributes = new Dictionary + { + [SemanticConventions.AttributeDbName] = "DbName", + }, + }, + new() + { + Name = "Multiple attributes: highest rank wins", + ExpectedResult = "PeerService", + RemoteEndpointAttributes = new Dictionary + { + [SemanticConventions.AttributeDbName] = "DbName", + [SemanticConventions.AttributePeerAddress] = "PeerAddress", + [SemanticConventions.AttributePeerHostname] = "PeerHostname", + [SemanticConventions.AttributeNetSockPeerAddr] = "5.6.7.8", + [SemanticConventions.AttributeNetSockPeerPort] = "8765", + [SemanticConventions.AttributeNetSockPeerName] = "NetSockPeerName", + [SemanticConventions.AttributeServerSocketAddress] = "SocketAddress", + [SemanticConventions.AttributeServerSocketPort] = "4321", + [SemanticConventions.AttributeServerSocketDomain] = "SocketDomain", + [SemanticConventions.AttributeNetworkPeerAddress] = "1.2.3.4", + [SemanticConventions.AttributeNetworkPeerPort] = "5678", + [SemanticConventions.AttributeNetPeerName] = "NetPeerName", + [SemanticConventions.AttributeServerAddress] = "ServerAddress", + [SemanticConventions.AttributePeerService] = "PeerService", + }, + }, + ]; + + public string? Name { get; private set; } + + public string? ExpectedResult { get; private set; } + + public Dictionary? RemoteEndpointAttributes { get; private set; } + + public override string? ToString() + { + return this.Name; + } +} diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionExtensionsTest.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionExtensionsTests.cs similarity index 51% rename from test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionExtensionsTest.cs rename to test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionExtensionsTests.cs index 14f28474176..fbb958ceb74 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionExtensionsTest.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionExtensionsTests.cs @@ -4,11 +4,10 @@ using System.Diagnostics; using OpenTelemetry.Internal; using Xunit; -using static OpenTelemetry.Exporter.Zipkin.Implementation.ZipkinActivityConversionExtensions; namespace OpenTelemetry.Exporter.Zipkin.Implementation.Tests; -public class ZipkinActivityConversionExtensionsTest +public class ZipkinActivityConversionExtensionsTests { [Theory] [InlineData("int", 1)] @@ -17,18 +16,15 @@ public class ZipkinActivityConversionExtensionsTest [InlineData("double", 1.0)] public void CheckProcessTag(string key, object value) { - var attributeEnumerationState = new TagEnumerationState - { - Tags = PooledList>.Create(), - }; - using var activity = new Activity("TestActivity"); activity.SetTag(key, value); - attributeEnumerationState.EnumerateTags(activity); + var tags = PooledList>.Create(); + ExtractTags(activity, ref tags); - Assert.Equal(key, attributeEnumerationState.Tags[0].Key); - Assert.Equal(value, attributeEnumerationState.Tags[0].Value); + var tag = Assert.Single(tags); + Assert.Equal(key, tag.Key); + Assert.Equal(value, tag.Value); } [Theory] @@ -38,16 +34,23 @@ public void CheckProcessTag(string key, object value) [InlineData("double", null)] public void CheckNullValueProcessTag(string key, object? value) { - var attributeEnumerationState = new TagEnumerationState - { - Tags = PooledList>.Create(), - }; - using var activity = new Activity("TestActivity"); activity.SetTag(key, value); - attributeEnumerationState.EnumerateTags(activity); + var tags = PooledList>.Create(); + ExtractTags(activity, ref tags); - Assert.Empty(attributeEnumerationState.Tags); + Assert.Empty(tags); + } + + private static void ExtractTags(Activity activity, ref PooledList> tags) + { + foreach (var tag in activity.TagObjects) + { + if (tag.Value != null) + { + PooledList>.Add(ref tags, new KeyValuePair(tag.Key, tag.Value)); + } + } } } diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTests.cs similarity index 81% rename from test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs rename to test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTests.cs index 9a23ba329d4..723e113da71 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTests.cs @@ -9,7 +9,7 @@ namespace OpenTelemetry.Exporter.Zipkin.Implementation.Tests; -public class ZipkinActivityConversionTest +public class ZipkinActivityConversionTests { private const string ZipkinSpanName = "Name"; private static readonly ZipkinEndpoint DefaultZipkinEndpoint = new("TestService"); @@ -18,7 +18,7 @@ public class ZipkinActivityConversionTest public void ToZipkinSpan_AllPropertiesSet() { // Arrange - using var activity = ZipkinExporterTests.CreateTestActivity(); + using var activity = ZipkinActivitySource.CreateTestActivity(); // Act & Assert var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint); @@ -51,7 +51,7 @@ public void ToZipkinSpan_AllPropertiesSet() public void ToZipkinSpan_NoEvents() { // Arrange - using var activity = ZipkinExporterTests.CreateTestActivity(addEvents: false); + using var activity = ZipkinActivitySource.CreateTestActivity(addEvents: false); // Act & Assert var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint); @@ -76,14 +76,14 @@ public void ToZipkinSpan_NoEvents() [Theory] [InlineData(StatusCode.Unset, "unset")] - [InlineData(StatusCode.Ok, "Ok")] + [InlineData(StatusCode.Ok, "OK")] [InlineData(StatusCode.Error, "ERROR")] [InlineData(StatusCode.Unset, "iNvAlId")] [Obsolete("Remove when ActivityExtensions status APIs are removed")] public void ToZipkinSpan_Status_ErrorFlagTest(StatusCode expectedStatusCode, string statusCodeTagValue) { // Arrange - using var activity = ZipkinExporterTests.CreateTestActivity(); + using var activity = ZipkinActivitySource.CreateTestActivity(); activity.SetTag(SpanAttributeConstants.StatusCodeKey, statusCodeTagValue); // Act @@ -106,11 +106,11 @@ public void ToZipkinSpan_Status_ErrorFlagTest(StatusCode expectedStatusCode, str if (expectedStatusCode == StatusCode.Error) { - Assert.Contains(zipkinSpan.Tags, t => t.Key == "error" && (string?)t.Value == string.Empty); + Assert.Contains(zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName && ((string?)t.Value)?.Length == 0); } else { - Assert.DoesNotContain(zipkinSpan.Tags, t => t.Key == "error"); + Assert.DoesNotContain(zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName); } } @@ -122,7 +122,7 @@ public void ToZipkinSpan_Activity_Status_And_StatusDescription_is_Set(ActivitySt { // Arrange. const string description = "Description when ActivityStatusCode is Error."; - using var activity = ZipkinExporterTests.CreateTestActivity(); + using var activity = ZipkinActivitySource.CreateTestActivity(); activity.SetStatus(expectedStatusCode, description); // Act. @@ -159,12 +159,51 @@ public void ToZipkinSpan_Activity_Status_And_StatusDescription_is_Set(ActivitySt } } + [Theory] + [InlineData("FromStatusDescription", null, null, "FromStatusDescription")] + [InlineData(null, "FromStatusDescriptionKeyTag", null, "FromStatusDescriptionKeyTag")] + [InlineData(null, null, "FromZipkinErrorFlagTag", "FromZipkinErrorFlagTag")] + [InlineData(null, null, null, "")] + public void ToZipkinSpan_StatusDescription_ErrorTagTest( + string? statusDescription, + string? statusDescriptionTag, + string? zipkinErrorTag, + string expectedErrorTagValue) + { + // Arrange + using var activity = ZipkinActivitySource.CreateTestActivity(); + activity.SetStatus(ActivityStatusCode.Error); + + if (statusDescription != null) + { + activity.SetStatus(ActivityStatusCode.Error, statusDescription); + } + + if (statusDescriptionTag != null) + { + activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, statusDescriptionTag); + } + + if (zipkinErrorTag != null) + { + activity.SetTag(ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName, zipkinErrorTag); + } + + // Act + var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint); + + // Assert + var errorTag = zipkinSpan.Tags.FirstOrDefault(t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName); + + Assert.Equal(expectedErrorTagValue, errorTag.Value); + } + [Fact] [Obsolete("Remove when ActivityExtensions status APIs are removed")] public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeIsOk() { // Arrange. - using var activity = ZipkinExporterTests.CreateTestActivity(); + using var activity = ZipkinActivitySource.CreateTestActivity(); activity.SetStatus(ActivityStatusCode.Ok); activity.SetTag(SpanAttributeConstants.StatusCodeKey, "ERROR"); @@ -190,7 +229,7 @@ public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeI public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeIsError() { // Arrange. - using var activity = ZipkinExporterTests.CreateTestActivity(); + using var activity = ZipkinActivitySource.CreateTestActivity(); const string StatusDescriptionOnError = "Description when ActivityStatusCode is Error."; const string TagDescriptionOnError = "Description when TagStatusCode is Error."; @@ -226,7 +265,7 @@ public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeI public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeIsError_SettingTagFirst() { // Arrange. - using var activity = ZipkinExporterTests.CreateTestActivity(); + using var activity = ZipkinActivitySource.CreateTestActivity(); const string StatusDescriptionOnError = "Description when ActivityStatusCode is Error."; const string TagDescriptionOnError = "Description when TagStatusCode is Error."; diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs index 0f7c9e1bdb2..06b17f954f0 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 using OpenTelemetry.Exporter.Zipkin.Tests; -using OpenTelemetry.Trace; using Xunit; namespace OpenTelemetry.Exporter.Zipkin.Implementation.Tests; @@ -11,41 +10,22 @@ public class ZipkinActivityExporterRemoteEndpointTests { private static readonly ZipkinEndpoint DefaultZipkinEndpoint = new("TestService"); - [Fact] - public void GenerateActivity_RemoteEndpointOmittedByDefault() - { - // Arrange - using var activity = ZipkinExporterTests.CreateTestActivity(); - - // Act & Assert - var zipkinSpan = ZipkinActivityConversionExtensions.ToZipkinSpan(activity, DefaultZipkinEndpoint); - - Assert.NotNull(zipkinSpan.RemoteEndpoint); - } - - [Fact] - public void GenerateActivity_RemoteEndpointResolution() - { - // Arrange - using var activity = ZipkinExporterTests.CreateTestActivity( - additionalAttributes: new Dictionary - { - ["net.peer.name"] = "RemoteServiceName", - }); - - // Act & Assert - var zipkinSpan = ZipkinActivityConversionExtensions.ToZipkinSpan(activity, DefaultZipkinEndpoint); - - Assert.NotNull(zipkinSpan.RemoteEndpoint); - Assert.Equal("RemoteServiceName", zipkinSpan.RemoteEndpoint.ServiceName); - } - [Theory] - [MemberData(nameof(RemoteEndpointPriorityTestCase.GetTestCases), MemberType = typeof(RemoteEndpointPriorityTestCase))] + [MemberData(nameof(RemoteEndpointPriorityTestCase.TestCases), MemberType = typeof(RemoteEndpointPriorityTestCase))] public void GenerateActivity_RemoteEndpointResolutionPriority(RemoteEndpointPriorityTestCase testCase) { +#if NET + Assert.NotNull(testCase); +#else + if (testCase == null) + { + throw new ArgumentNullException(nameof(testCase)); + } +#endif + // Arrange - using var activity = ZipkinExporterTests.CreateTestActivity(additionalAttributes: testCase.RemoteEndpointAttributes!); + using var activity = + ZipkinActivitySource.CreateTestActivity(additionalAttributes: testCase.RemoteEndpointAttributes!); // Act & Assert var zipkinSpan = ZipkinActivityConversionExtensions.ToZipkinSpan(activity, DefaultZipkinEndpoint); @@ -53,130 +33,4 @@ public void GenerateActivity_RemoteEndpointResolutionPriority(RemoteEndpointPrio Assert.NotNull(zipkinSpan.RemoteEndpoint); Assert.Equal(testCase.ExpectedResult, zipkinSpan.RemoteEndpoint.ServiceName); } - - public class RemoteEndpointPriorityTestCase - { - public string? Name { get; set; } - - public string? ExpectedResult { get; set; } - - public Dictionary? RemoteEndpointAttributes { get; set; } - - public static IEnumerable GetTestCases() - { - yield return new object[] - { - new RemoteEndpointPriorityTestCase - { - Name = "Highest priority name = net.peer.name", - ExpectedResult = "RemoteServiceName", - RemoteEndpointAttributes = new Dictionary - { - ["http.host"] = "DiscardedRemoteServiceName", - ["net.peer.name"] = "RemoteServiceName", - ["peer.hostname"] = "DiscardedRemoteServiceName", - }, - }, - }; - - yield return new object[] - { - new RemoteEndpointPriorityTestCase - { - Name = "Highest priority name = SemanticConventions.AttributePeerService", - ExpectedResult = "RemoteServiceName", - RemoteEndpointAttributes = new Dictionary - { - [SemanticConventions.AttributePeerService] = "RemoteServiceName", - ["http.host"] = "DiscardedRemoteServiceName", - ["net.peer.name"] = "DiscardedRemoteServiceName", - ["net.peer.port"] = "1234", - ["peer.hostname"] = "DiscardedRemoteServiceName", - }, - }, - }; - - yield return new object[] - { - new RemoteEndpointPriorityTestCase - { - Name = "Only has net.peer.name and net.peer.port", - ExpectedResult = "RemoteServiceName:1234", - RemoteEndpointAttributes = new Dictionary - { - ["net.peer.name"] = "RemoteServiceName", - ["net.peer.port"] = "1234", - }, - }, - }; - - yield return new object[] - { - new RemoteEndpointPriorityTestCase - { - Name = "net.peer.port is an int", - ExpectedResult = "RemoteServiceName:1234", - RemoteEndpointAttributes = new Dictionary - { - ["net.peer.name"] = "RemoteServiceName", - ["net.peer.port"] = 1234, - }, - }, - }; - - yield return new object[] - { - new RemoteEndpointPriorityTestCase - { - Name = "Has net.peer.name and net.peer.port", - ExpectedResult = "RemoteServiceName:1234", - RemoteEndpointAttributes = new Dictionary - { - ["http.host"] = "DiscardedRemoteServiceName", - ["net.peer.name"] = "RemoteServiceName", - ["net.peer.port"] = "1234", - ["peer.hostname"] = "DiscardedRemoteServiceName", - }, - }, - }; - - yield return new object[] - { - new RemoteEndpointPriorityTestCase - { - Name = "Has net.peer.ip and net.peer.port", - ExpectedResult = "1.2.3.4:1234", - RemoteEndpointAttributes = new Dictionary - { - ["http.host"] = "DiscardedRemoteServiceName", - ["net.peer.ip"] = "1.2.3.4", - ["net.peer.port"] = "1234", - ["peer.hostname"] = "DiscardedRemoteServiceName", - }, - }, - }; - - yield return new object[] - { - new RemoteEndpointPriorityTestCase - { - Name = "Has net.peer.name, net.peer.ip, and net.peer.port", - ExpectedResult = "RemoteServiceName:1234", - RemoteEndpointAttributes = new Dictionary - { - ["http.host"] = "DiscardedRemoteServiceName", - ["net.peer.name"] = "RemoteServiceName", - ["net.peer.ip"] = "1.2.3.4", - ["net.peer.port"] = "1234", - ["peer.hostname"] = "DiscardedRemoteServiceName", - }, - }, - }; - } - - public override string? ToString() - { - return this.Name; - } - } } diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj b/test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj index a5494a2f920..7970f2d56ee 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj @@ -3,6 +3,8 @@ Unit test project for Zipkin Exporter for OpenTelemetry $(TargetFrameworksForTests) + + false @@ -13,12 +15,9 @@ - - + - - diff --git a/test/OpenTelemetry.Tests/Internal/PooledListTest.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/PooledListTests.cs similarity index 98% rename from test/OpenTelemetry.Tests/Internal/PooledListTest.cs rename to test/OpenTelemetry.Exporter.Zipkin.Tests/PooledListTests.cs index 6db06b6fadc..19b7e3f0378 100644 --- a/test/OpenTelemetry.Tests/Internal/PooledListTest.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/PooledListTests.cs @@ -7,7 +7,7 @@ namespace OpenTelemetry.Internal.Tests; -public class PooledListTest +public class PooledListTests { [Fact] public void Verify_ICollectionExplicitProperties() diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivitySource.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivitySource.cs new file mode 100644 index 00000000000..f638a99f52b --- /dev/null +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivitySource.cs @@ -0,0 +1,128 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Diagnostics; +using Xunit; + +namespace OpenTelemetry.Exporter.Zipkin.Tests; + +internal static class ZipkinActivitySource +{ + private static readonly ActivitySource ActivitySource = new(nameof(ZipkinActivitySource)); + + private static readonly bool[] BoolArray = [true, false]; + + internal static Activity CreateTestActivity( + bool isRootSpan = false, + bool setAttributes = true, + Dictionary? additionalAttributes = null, + bool addEvents = true, + bool addLinks = true, + ActivityKind kind = ActivityKind.Client, + ActivityStatusCode statusCode = ActivityStatusCode.Unset, + string? statusDescription = null, + DateTime? dateTime = null) + { + using var activityListener = new ActivityListener + { + ShouldListenTo = _ => true, + Sample = (ref ActivityCreationOptions options) => options.Parent.TraceFlags.HasFlag(ActivityTraceFlags.Recorded) + ? ActivitySamplingResult.AllDataAndRecorded + : ActivitySamplingResult.AllData, + }; + + ActivitySource.AddActivityListener(activityListener); + + var startTimestamp = DateTime.UtcNow; + var endTimestamp = startTimestamp.AddSeconds(60); + var eventTimestamp = DateTime.UtcNow; + var traceId = ActivityTraceId.CreateFromString("e8ea7e9ac72de94e91fabc613f9686b2".AsSpan()); + + dateTime ??= DateTime.UtcNow; + + var parentSpanId = isRootSpan ? default : ActivitySpanId.CreateFromBytes([12, 23, 34, 45, 56, 67, 78, 89]); + + var attributes = new Dictionary + { + { "stringKey", "value" }, + { "longKey", 1L }, + { "longKey2", 1 }, + { "doubleKey", 1D }, + { "doubleKey2", 1F }, + { "longArrayKey", new long[] { 1, 2 } }, + { "boolKey", true }, + { "boolArrayKey", BoolArray }, + { "http.host", "http://localhost:44312/" }, // simulating instrumentation tag adding http.host + { "dateTimeKey", dateTime.Value }, + { "dateTimeArrayKey", new DateTime[] { dateTime.Value } }, + }; + if (additionalAttributes != null) + { + foreach (var attribute in additionalAttributes) + { + if (!attributes.ContainsKey(attribute.Key)) + { + attributes.Add(attribute.Key, attribute.Value); + } + } + } + + var events = new List + { + new( + "Event1", + eventTimestamp, + new(new Dictionary + { + { "key", "value" }, + })), + new( + "Event2", + eventTimestamp, + new(new Dictionary + { + { "key", "value" }, + })), + }; + + var linkedSpanId = ActivitySpanId.CreateFromString("888915b6286b9c41".AsSpan()); + + var tags = setAttributes ? + attributes.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value)) + : null; + var links = addLinks ? + new[] + { + new ActivityLink(new( + traceId, + linkedSpanId, + ActivityTraceFlags.Recorded)), + } + : null; + + var activity = ActivitySource.StartActivity( + "Name", + kind, + parentContext: new(traceId, parentSpanId, ActivityTraceFlags.Recorded), + tags, + links, + startTime: startTimestamp)!; + + Assert.NotNull(activity); + + if (addEvents) + { + foreach (var evnt in events) + { + activity.AddEvent(evnt); + } + } + + activity.SetStatus(statusCode, statusDescription); + + activity.SetEndTime(endTimestamp); + activity.Stop(); + + return activity; + } +} diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs index 97b90a5ec68..f4b3f113ac7 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs @@ -19,7 +19,7 @@ namespace OpenTelemetry.Exporter.Zipkin.Tests; -public class ZipkinExporterTests : IDisposable +public sealed class ZipkinExporterTests : IDisposable { private const string TraceId = "e8ea7e9ac72de94e91fabc613f9686b2"; private static readonly ConcurrentDictionary Responses = new(); @@ -32,14 +32,6 @@ static ZipkinExporterTests() { Activity.DefaultIdFormat = ActivityIdFormat.W3C; Activity.ForceDefaultIdFormat = true; - - var listener = new ActivityListener - { - ShouldListenTo = _ => true, - Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllData, - }; - - ActivitySource.AddActivityListener(listener); } public ZipkinExporterTests() @@ -53,7 +45,7 @@ static void ProcessServerRequest(HttpListenerContext context) { context.Response.StatusCode = 200; - using StreamReader readStream = new StreamReader(context.Request.InputStream); + using StreamReader readStream = new(context.Request.InputStream); string requestContent = readStream.ReadToEnd(); @@ -68,7 +60,6 @@ static void ProcessServerRequest(HttpListenerContext context) public void Dispose() { this.testServer.Dispose(); - GC.SuppressFinalize(this); } [Fact] @@ -102,9 +93,11 @@ public void BadArgs() [Fact] public void SuppressesInstrumentation() { - const string ActivitySourceName = "zipkin.test"; + const string activitySourceName = "zipkin.test"; Guid requestId = Guid.NewGuid(); - TestActivityProcessor testActivityProcessor = new TestActivityProcessor(); +#pragma warning disable CA2000 // Dispose objects before losing scope + TestActivityProcessor testActivityProcessor = new(); +#pragma warning restore CA2000 // Dispose objects before losing scope int endCalledCount = 0; @@ -116,19 +109,19 @@ public void SuppressesInstrumentation() var exporterOptions = new ZipkinExporterOptions { - Endpoint = new Uri($"http://{this.testServerHost}:{this.testServerPort}/api/v2/spans?requestId={requestId}"), + Endpoint = new($"http://{this.testServerHost}:{this.testServerPort}/api/v2/spans?requestId={requestId}"), }; using var zipkinExporter = new ZipkinExporter(exporterOptions); using var exportActivityProcessor = new BatchActivityExportProcessor(zipkinExporter); - var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddSource(ActivitySourceName) + using var tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddSource(activitySourceName) .AddProcessor(testActivityProcessor) .AddProcessor(exportActivityProcessor) .AddHttpClientInstrumentation() .Build(); - using var source = new ActivitySource(ActivitySourceName); + using var source = new ActivitySource(activitySourceName); using var activity = source.StartActivity("Test Zipkin Activity"); activity?.Stop(); @@ -168,7 +161,7 @@ public void IncodeEndpointConfigTakesPrecedenceOverEnvironmentVariable() var exporterOptions = new ZipkinExporterOptions { - Endpoint = new Uri("http://urifromcode"), + Endpoint = new("http://urifromcode"), }; Assert.Equal(new Uri("http://urifromcode").AbsoluteUri, exporterOptions.Endpoint.AbsoluteUri); @@ -179,7 +172,7 @@ public void IncodeEndpointConfigTakesPrecedenceOverEnvironmentVariable() } } - [Fact(Skip = "https://github.com/open-telemetry/opentelemetry-dotnet/issues/3690")] + [Fact] public void ErrorGettingUriFromEnvVarSetsDefaultEndpointValue() { try @@ -188,7 +181,7 @@ public void ErrorGettingUriFromEnvVarSetsDefaultEndpointValue() var options = new ZipkinExporterOptions(); - Assert.Equal(new Uri(ZipkinExporterOptions.DefaultZipkinEndpoint), options.Endpoint); + Assert.Equal(new(ZipkinExporterOptions.DefaultZipkinEndpoint), options.Endpoint); } finally { @@ -210,13 +203,13 @@ public void EndpointConfigurationUsingIConfiguration() var options = new ZipkinExporterOptions(configuration, new()); - Assert.Equal(new Uri("http://custom-endpoint:12345"), options.Endpoint); + Assert.Equal(new("http://custom-endpoint:12345"), options.Endpoint); } [Fact] public void UserHttpFactoryCalled() { - ZipkinExporterOptions options = new ZipkinExporterOptions(); + ZipkinExporterOptions options = new(); var defaultFactory = options.HttpClientFactory; @@ -284,11 +277,11 @@ public void ServiceProviderHttpClientFactoryInvoked() [Fact] public void UpdatesServiceNameFromDefaultResource() { - var zipkinExporter = new ZipkinExporter(new ZipkinExporterOptions()); + using var zipkinExporter = new ZipkinExporter(new()); zipkinExporter.SetLocalEndpointFromResource(Resource.Empty); - Assert.StartsWith("unknown_service:", zipkinExporter.LocalEndpoint!.ServiceName); + Assert.StartsWith("unknown_service:", zipkinExporter.LocalEndpoint!.ServiceName, StringComparison.Ordinal); } [Fact] @@ -306,9 +299,11 @@ public void UpdatesServiceNameFromIConfiguration() new ConfigurationBuilder().AddInMemoryCollection(configuration!).Build()); }); - var zipkinExporter = new ZipkinExporter(new ZipkinExporterOptions()); +#pragma warning disable CA2000 // Dispose objects before losing scope + var zipkinExporter = new ZipkinExporter(new()); tracerProviderBuilder.AddProcessor(new BatchActivityExportProcessor(zipkinExporter)); +#pragma warning restore CA2000 // Dispose objects before losing scope using var provider = tracerProviderBuilder.Build(); @@ -336,18 +331,19 @@ public void IntegrationTest( { Guid requestId = Guid.NewGuid(); - ZipkinExporter exporter = new ZipkinExporter( - new ZipkinExporterOptions +#pragma warning disable CA2000 // Dispose objects before losing scope + ZipkinExporter exporter = new( + new() { - Endpoint = new Uri($"http://{this.testServerHost}:{this.testServerPort}/api/v2/spans?requestId={requestId}"), + Endpoint = new($"http://{this.testServerHost}:{this.testServerPort}/api/v2/spans?requestId={requestId}"), UseShortTraceIds = useShortTraceIds, }); +#pragma warning restore CA2000 // Dispose objects before losing scope - var serviceName = (string)exporter.ParentProvider.GetDefaultResource().Attributes - .Where(pair => pair.Key == ResourceSemanticConventions.AttributeServiceName).FirstOrDefault().Value; + var serviceName = (string)exporter.ParentProvider.GetDefaultResource().Attributes.FirstOrDefault(pair => pair.Key == ResourceSemanticConventions.AttributeServiceName).Value; var resourceTags = string.Empty; var dateTime = DateTime.UtcNow; - var activity = CreateTestActivity(isRootSpan: isRootSpan, statusCode: statusCode, statusDescription: statusDescription, dateTime: dateTime); + using var activity = ZipkinActivitySource.CreateTestActivity(isRootSpan: isRootSpan, statusCode: statusCode, statusDescription: statusDescription, dateTime: dateTime); if (useTestResource) { serviceName = "MyService"; @@ -363,12 +359,14 @@ public void IntegrationTest( exporter.SetLocalEndpointFromResource(Resource.Empty); } + activity.SetTag(SemanticConventions.AttributePeerService, "http://localhost:44312/"); + if (addErrorTag) { activity.SetTag(ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName, "This should be removed."); } - var processor = new SimpleActivityExportProcessor(exporter); + using var processor = new SimpleActivityExportProcessor(exporter); processor.OnEnd(activity); @@ -377,15 +375,23 @@ public void IntegrationTest( var timestamp = activity.StartTimeUtc.ToEpochMicroseconds(); var eventTimestamp = activity.Events.First().Timestamp.ToEpochMicroseconds(); - StringBuilder ipInformation = new StringBuilder(); + StringBuilder ipInformation = new(); if (!string.IsNullOrEmpty(exporter.LocalEndpoint!.Ipv4)) { +#if NET + ipInformation.Append(CultureInfo.InvariantCulture, $@",""ipv4"":""{exporter.LocalEndpoint.Ipv4}"""); +#else ipInformation.Append($@",""ipv4"":""{exporter.LocalEndpoint.Ipv4}"""); +#endif } if (!string.IsNullOrEmpty(exporter.LocalEndpoint.Ipv6)) { +#if NET + ipInformation.Append(CultureInfo.InvariantCulture, $@",""ipv6"":""{exporter.LocalEndpoint.Ipv6}"""); +#else ipInformation.Append($@",""ipv6"":""{exporter.LocalEndpoint.Ipv6}"""); +#endif } var parentId = isRootSpan ? string.Empty : $@"""parentId"":""{ZipkinActivityConversionExtensions.EncodeSpanId(activity.ParentSpanId)}"","; @@ -434,117 +440,12 @@ public void IntegrationTest( + @"""http.host"":""http://localhost:44312/""," + $@"""dateTimeKey"":""{Convert.ToString(dateTime, CultureInfo.InvariantCulture)}""," + $@"""dateTimeArrayKey"":""[\u0022{Convert.ToString(dateTime, CultureInfo.InvariantCulture)}\u0022]""," + + $@"""peer.service"":""http://localhost:44312/""," + statusTag + errorTag - + @"""otel.scope.name"":""CreateTestActivity""," - + @"""otel.library.name"":""CreateTestActivity""," - + @"""peer.service"":""http://localhost:44312/""" + + @"""otel.scope.name"":""ZipkinActivitySource""," + + @"""otel.library.name"":""ZipkinActivitySource""" + "}}]", Responses[requestId]); } - - internal static Activity CreateTestActivity( - bool isRootSpan = false, - bool setAttributes = true, - Dictionary? additionalAttributes = null, - bool addEvents = true, - bool addLinks = true, - Resource? resource = null, - ActivityKind kind = ActivityKind.Client, - ActivityStatusCode statusCode = ActivityStatusCode.Unset, - string? statusDescription = null, - DateTime? dateTime = null) - { - var startTimestamp = DateTime.UtcNow; - var endTimestamp = startTimestamp.AddSeconds(60); - var eventTimestamp = DateTime.UtcNow; - var traceId = ActivityTraceId.CreateFromString("e8ea7e9ac72de94e91fabc613f9686b2".AsSpan()); - - dateTime ??= DateTime.UtcNow; - - var parentSpanId = isRootSpan ? default : ActivitySpanId.CreateFromBytes(new byte[] { 12, 23, 34, 45, 56, 67, 78, 89 }); - - var attributes = new Dictionary - { - { "stringKey", "value" }, - { "longKey", 1L }, - { "longKey2", 1 }, - { "doubleKey", 1D }, - { "doubleKey2", 1F }, - { "longArrayKey", new long[] { 1, 2 } }, - { "boolKey", true }, - { "boolArrayKey", new bool[] { true, false } }, - { "http.host", "http://localhost:44312/" }, // simulating instrumentation tag adding http.host - { "dateTimeKey", dateTime.Value }, - { "dateTimeArrayKey", new DateTime[] { dateTime.Value } }, - }; - if (additionalAttributes != null) - { - foreach (var attribute in additionalAttributes) - { - if (!attributes.ContainsKey(attribute.Key)) - { - attributes.Add(attribute.Key, attribute.Value); - } - } - } - - var events = new List - { - new ActivityEvent( - "Event1", - eventTimestamp, - new ActivityTagsCollection(new Dictionary - { - { "key", "value" }, - })), - new ActivityEvent( - "Event2", - eventTimestamp, - new ActivityTagsCollection(new Dictionary - { - { "key", "value" }, - })), - }; - - var linkedSpanId = ActivitySpanId.CreateFromString("888915b6286b9c41".AsSpan()); - - var activitySource = new ActivitySource(nameof(CreateTestActivity)); - - var tags = setAttributes ? - attributes.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value)) - : null; - var links = addLinks ? - new[] - { - new ActivityLink(new ActivityContext( - traceId, - linkedSpanId, - ActivityTraceFlags.Recorded)), - } - : null; - - var activity = activitySource.StartActivity( - "Name", - kind, - parentContext: new ActivityContext(traceId, parentSpanId, ActivityTraceFlags.Recorded), - tags, - links, - startTime: startTimestamp)!; - - if (addEvents) - { - foreach (var evnt in events) - { - activity.AddEvent(evnt); - } - } - - activity.SetStatus(statusCode, statusDescription); - - activity.SetEndTime(endTimestamp); - activity.Stop(); - - return activity; - } } diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/EventSourceTest.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/EventSourceTests.cs similarity index 93% rename from test/OpenTelemetry.Extensions.Hosting.Tests/EventSourceTest.cs rename to test/OpenTelemetry.Extensions.Hosting.Tests/EventSourceTests.cs index 83d475bfec0..ee622b185e1 100644 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/EventSourceTest.cs +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/EventSourceTests.cs @@ -7,7 +7,7 @@ namespace OpenTelemetry.Extensions.Hosting.Tests; -public class EventSourceTest +public class EventSourceTests { [Fact] public void EventSourceTest_HostingExtensionsEventSource() diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs index 3957ffd5b19..62b5078d79a 100644 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs @@ -85,7 +85,7 @@ private static async Task RunMetricsTest(Action configure, }))) .StartAsync(); - using var response = await host.GetTestClient().GetAsync($"/{nameof(RunMetricsTest)}"); + using var response = await host.GetTestClient().GetAsync(new Uri($"/{nameof(RunMetricsTest)}", UriKind.Relative)); Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj index 63220e5512a..57ec3b2fbb5 100644 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj @@ -1,4 +1,4 @@ - + Unit test project for OpenTelemetry .NET Core hosting library @@ -13,7 +13,6 @@ - @@ -23,6 +22,8 @@ IMetricsBuilder/IMetricsListener API added at the host level in .NET 8 instead of the direct lower-level MeterListener API added in .NET 6. --> + + @@ -35,9 +36,6 @@ - - - diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryBuilderTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryBuilderTests.cs index e5160e3b79e..7c0bfae5711 100644 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryBuilderTests.cs +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryBuilderTests.cs @@ -91,7 +91,9 @@ public void ConfigureResourceServiceProviderTest() Assert.Single(loggerProvider.Resource.Attributes, kvp => kvp.Key == "key1" && (string)kvp.Value == "value1"); } +#pragma warning disable CA1812 // Avoid uninstantiated internal classes private sealed class TestResourceDetector : IResourceDetector +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { public Resource Detect() => ResourceBuilder.CreateEmpty().AddAttributes( new Dictionary diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryMetricsBuilderExtensionsTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryMetricsBuilderExtensionsTests.cs index d34dc060f1e..c990d14a336 100644 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryMetricsBuilderExtensionsTests.cs +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryMetricsBuilderExtensionsTests.cs @@ -25,7 +25,7 @@ public class OpenTelemetryMetricsBuilderExtensionsTests public void EnableMetricsTest(bool useWithMetricsStyle) { using var meter = new Meter(Utils.GetCurrentMethodName()); - List exportedItems = new(); + List exportedItems = []; using (var host = MetricTestsBase.BuildHost( useWithMetricsStyle, @@ -45,7 +45,7 @@ public void EnableMetricsTest(bool useWithMetricsStyle) public void EnableMetricsWithAddMeterTest(bool useWithMetricsStyle) { using var meter = new Meter(Utils.GetCurrentMethodName()); - List exportedItems = new(); + List exportedItems = []; using (var host = MetricTestsBase.BuildHost( useWithMetricsStyle, @@ -71,11 +71,11 @@ public void ReloadOfMetricsViaIConfigurationWithExportCleanupTest(bool useWithMe using var inMemoryEventListener = new InMemoryEventListener(OpenTelemetrySdkEventSource.Log); using var meter = new Meter(Utils.GetCurrentMethodName()); - List exportedItems = new(); + List exportedItems = []; var source = new MemoryConfigurationSource(); var memory = new MemoryConfigurationProvider(source); - var configuration = new ConfigurationRoot(new[] { memory }); + using var configuration = new ConfigurationRoot([memory]); using var host = MetricTestsBase.BuildHost( useWithMetricsStyle, @@ -162,12 +162,12 @@ public void ReloadOfMetricsViaIConfigurationWithoutExportCleanupTest(bool useWit using var inMemoryEventListener = new InMemoryEventListener(OpenTelemetrySdkEventSource.Log); using var meter = new Meter(Utils.GetCurrentMethodName()); - List exportedItems = new(); + List exportedItems = []; var source = new MemoryConfigurationSource(); var memory = new MemoryConfigurationProvider(source); memory.Set($"Metrics:EnabledMetrics:{meter.Name}:Default", "true"); - var configuration = new ConfigurationRoot(new[] { memory }); + using var configuration = new ConfigurationRoot([memory]); using var host = MetricTestsBase.BuildHost( useWithMetricsStyle, @@ -232,7 +232,7 @@ private static void AssertSingleMetricWithLongSum(List exportedItems, lo private static void AssertMetricWithLongSum(Metric metric, long expectedValue = 1) { - List metricPoints = new(); + List metricPoints = []; foreach (ref readonly var mp in metric.GetMetricPoints()) { metricPoints.Add(mp); diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs index 16ee08af2c5..85b85dfd998 100644 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs @@ -457,15 +457,25 @@ public async Task AddOpenTelemetry_HostedServiceOrder_DoesNotMatter() Assert.Single(exportedItems); } +#pragma warning disable CA1812 // Avoid uninstantiated internal classes private sealed class MySampler : Sampler +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { public override SamplingResult ShouldSample(in SamplingParameters samplingParameters) => new(SamplingDecision.RecordAndSample); } +#pragma warning disable CA1812 // Avoid uninstantiated internal classes private sealed class TestHostedService : BackgroundService +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { - private readonly ActivitySource activitySource = new ActivitySource(nameof(TestHostedService)); + private readonly ActivitySource activitySource = new(nameof(TestHostedService)); + + public override void Dispose() + { + this.activitySource.Dispose(); + base.Dispose(); + } protected override Task ExecuteAsync(CancellationToken stoppingToken) { diff --git a/test/OpenTelemetry.Extensions.Propagators.Tests/B3PropagatorTest.cs b/test/OpenTelemetry.Extensions.Propagators.Tests/B3PropagatorTests.cs similarity index 97% rename from test/OpenTelemetry.Extensions.Propagators.Tests/B3PropagatorTest.cs rename to test/OpenTelemetry.Extensions.Propagators.Tests/B3PropagatorTests.cs index 15ed68c96da..b390e7d1d36 100644 --- a/test/OpenTelemetry.Extensions.Propagators.Tests/B3PropagatorTest.cs +++ b/test/OpenTelemetry.Extensions.Propagators.Tests/B3PropagatorTests.cs @@ -8,7 +8,7 @@ namespace OpenTelemetry.Extensions.Propagators.Tests; -public class B3PropagatorTest +public class B3PropagatorTests { private const string TraceIdBase16 = "ff000000000000000000000000000041"; private const string TraceIdBase16EightBytes = "0000000000000041"; @@ -38,7 +38,7 @@ public class B3PropagatorTest private readonly ITestOutputHelper output; - public B3PropagatorTest(ITestOutputHelper output) + public B3PropagatorTests(ITestOutputHelper output) { this.output = output; } @@ -361,7 +361,7 @@ public void Fields_list() Assert.Equivalent(this.b3propagator.Fields, new List { B3Propagator.XB3TraceId, B3Propagator.XB3SpanId, B3Propagator.XB3ParentSpanId, B3Propagator.XB3Sampled, B3Propagator.XB3Flags, B3Propagator.XB3Flags }); ContainsExactly( this.b3propagator.Fields, - new List { B3Propagator.XB3TraceId, B3Propagator.XB3SpanId, B3Propagator.XB3ParentSpanId, B3Propagator.XB3Sampled, B3Propagator.XB3Flags }); + [B3Propagator.XB3TraceId, B3Propagator.XB3SpanId, B3Propagator.XB3ParentSpanId, B3Propagator.XB3Sampled, B3Propagator.XB3Flags]); } private static void ContainsExactly(ISet list, List items) @@ -373,7 +373,7 @@ private static void ContainsExactly(ISet list, List items) } } - private void ContainsExactly(IDictionary dict, IDictionary items) + private void ContainsExactly(Dictionary dict, Dictionary items) { foreach (var d in dict) { diff --git a/test/OpenTelemetry.Extensions.Propagators.Tests/EventSourceTest.cs b/test/OpenTelemetry.Extensions.Propagators.Tests/EventSourceTests.cs similarity index 93% rename from test/OpenTelemetry.Extensions.Propagators.Tests/EventSourceTest.cs rename to test/OpenTelemetry.Extensions.Propagators.Tests/EventSourceTests.cs index ee3b570c437..5f8c0c9978c 100644 --- a/test/OpenTelemetry.Extensions.Propagators.Tests/EventSourceTest.cs +++ b/test/OpenTelemetry.Extensions.Propagators.Tests/EventSourceTests.cs @@ -7,7 +7,7 @@ namespace OpenTelemetry.Extensions.Propagators.Tests; -public class EventSourceTest +public class EventSourceTests { [Fact] public void EventSourceTest_PropagatorsEventSource() diff --git a/test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTest.cs b/test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTests.cs similarity index 94% rename from test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTest.cs rename to test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTests.cs index 4e2b0486f6e..765ea3b3db5 100644 --- a/test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTest.cs +++ b/test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTests.cs @@ -7,7 +7,7 @@ namespace OpenTelemetry.Extensions.Propagators.Tests; -public class JaegerPropagatorTest +public class JaegerPropagatorTests { private const string JaegerHeader = "uber-trace-id"; private const string JaegerDelimiter = ":"; @@ -23,12 +23,7 @@ public class JaegerPropagatorTest private static readonly Func, string, IEnumerable> Getter = (headers, name) => { - if (headers.TryGetValue(name, out var value)) - { - return value; - } - - return Array.Empty(); + return headers.TryGetValue(name, out var value) ? value : []; }; private static readonly Action, string, string> Setter = (carrier, name, value) => @@ -104,7 +99,7 @@ public void ExtractReturnsOriginalContextIfHeaderIsNotValid(string traceId, stri parentSpanId, flags); - var headers = new Dictionary { { JaegerHeader, new[] { formattedHeader } } }; + var headers = new Dictionary { { JaegerHeader, [formattedHeader] } }; // act var result = new JaegerPropagator().Extract(propagationContext, headers, Getter); @@ -118,6 +113,21 @@ public void ExtractReturnsOriginalContextIfHeaderIsNotValid(string traceId, stri [InlineData(TraceIdShort, SpanIdShort, ParentSpanId, FlagNotSampled, JaegerDelimiterEncoded)] public void ExtractReturnsNewContextIfHeaderIsValid(string traceId, string spanId, string parentSpanId, string flags, string delimiter) { +#if NET + Assert.NotNull(traceId); + Assert.NotNull(spanId); +#else + if (traceId == null) + { + throw new ArgumentNullException(nameof(traceId)); + } + + if (spanId == null) + { + throw new ArgumentNullException(nameof(traceId)); + } +#endif + // arrange var propagationContext = default(PropagationContext); @@ -128,7 +138,7 @@ public void ExtractReturnsNewContextIfHeaderIsValid(string traceId, string spanI parentSpanId, flags); - var headers = new Dictionary { { JaegerHeader, new[] { formattedHeader } } }; + var headers = new Dictionary { { JaegerHeader, [formattedHeader] } }; // act var result = new JaegerPropagator().Extract(propagationContext, headers, Getter); diff --git a/test/OpenTelemetry.Extensions.Propagators.Tests/OpenTelemetry.Extensions.Propagators.Tests.csproj b/test/OpenTelemetry.Extensions.Propagators.Tests/OpenTelemetry.Extensions.Propagators.Tests.csproj index 757c1d61ec6..86e28192880 100644 --- a/test/OpenTelemetry.Extensions.Propagators.Tests/OpenTelemetry.Extensions.Propagators.Tests.csproj +++ b/test/OpenTelemetry.Extensions.Propagators.Tests/OpenTelemetry.Extensions.Propagators.Tests.csproj @@ -9,14 +9,6 @@ - - - - - runtime; build; native; contentfiles; analyzers - - - diff --git a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/Dockerfile b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/Dockerfile index fc50a196ecf..b52fb55795e 100644 --- a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/Dockerfile +++ b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/Dockerfile @@ -5,13 +5,16 @@ ARG BUILD_SDK_VERSION=9.0 ARG TEST_SDK_VERSION=9.0 -FROM ubuntu AS w3c +FROM ubuntu:24.04@sha256:353675e2a41babd526e2b837d7ec780c2a05bca0164f7ea5dbbd433d21d166fc AS w3c #Install git WORKDIR /w3c RUN apt-get update && apt-get install -y git RUN git clone --branch level-1 https://github.com/w3c/trace-context.git -FROM mcr.microsoft.com/dotnet/sdk:${BUILD_SDK_VERSION} AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0.414@sha256:3cef19377b2ef2a0171e930a24627677447c3e41b5f2eab84ff4895f1b15d254 AS dotnet-sdk-8.0 +FROM mcr.microsoft.com/dotnet/sdk:9.0.305@sha256:bb42ae2c058609d1746baf24fe6864ecab0686711dfca1f4b7a99e367ab17162 AS dotnet-sdk-9.0 + +FROM dotnet-sdk-${BUILD_SDK_VERSION} AS build ARG PUBLISH_CONFIGURATION=Release ARG PUBLISH_FRAMEWORK=net9.0 WORKDIR /repo @@ -19,8 +22,9 @@ COPY . ./ WORKDIR "/repo/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests" RUN dotnet publish "OpenTelemetry.Instrumentation.W3cTraceContext.Tests.csproj" -c "${PUBLISH_CONFIGURATION}" -f "${PUBLISH_FRAMEWORK}" -o /drop -p:IntegrationBuild=true -FROM mcr.microsoft.com/dotnet/sdk:${TEST_SDK_VERSION} AS final +FROM dotnet-sdk-${TEST_SDK_VERSION} AS final WORKDIR /test +COPY /test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/requirements.txt . COPY --from=build /drop . COPY --from=w3c /w3c . RUN apt-get update \ @@ -28,7 +32,5 @@ RUN apt-get update \ && cd /usr/local/bin \ && ln -s /usr/bin/python3 python -# net6.0 image uses Python 3.9, which doesn't have `--break-system-packages` option. -RUN pip3 install --upgrade pip --break-system-packages || pip3 install --upgrade pip -RUN pip3 install aiohttp --break-system-packages +RUN pip3 install --requirement requirements.txt --break-system-packages ENTRYPOINT ["dotnet", "vstest", "OpenTelemetry.Instrumentation.W3cTraceContext.Tests.dll", "--logger:console;verbosity=detailed"] diff --git a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/OpenTelemetry.Instrumentation.W3cTraceContext.Tests.csproj b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/OpenTelemetry.Instrumentation.W3cTraceContext.Tests.csproj index fb9e640731f..300d53feacb 100644 --- a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/OpenTelemetry.Instrumentation.W3cTraceContext.Tests.csproj +++ b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/OpenTelemetry.Instrumentation.W3cTraceContext.Tests.csproj @@ -6,12 +6,7 @@ - - - - runtime; build; native; contentfiles; analyzers - diff --git a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/W3CTraceContextTests.cs b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/W3CTraceContextTests.cs index 95f8d9b5517..16a45886aaa 100644 --- a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/W3CTraceContextTests.cs +++ b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/W3CTraceContextTests.cs @@ -14,7 +14,7 @@ namespace OpenTelemetry.Instrumentation.W3cTraceContext.Tests; -public class W3CTraceContextTests : IDisposable +public sealed class W3CTraceContextTests : IDisposable { /* To run the tests, invoke docker-compose.yml from the root of the repo: @@ -33,12 +33,12 @@ public W3CTraceContextTests(ITestOutputHelper output) [SkipUnlessEnvVarFoundTheory(W3CTraceContextEnvVarName)] [InlineData("placeholder")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters", Justification = "Need to use SkipUnlessEnvVarFoundTheory")] - public void W3CTraceContextTestSuiteAsync(string value) + public async Task W3CTraceContextTestSuiteAsync(string value) { // configure SDK using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddAspNetCoreInstrumentation() - .Build(); + .AddAspNetCoreInstrumentation() + .Build(); var builder = WebApplication.CreateBuilder(); using var app = builder.Build(); @@ -66,17 +66,19 @@ public void W3CTraceContextTestSuiteAsync(string value) return result; }); - app.RunAsync("http://localhost:5000/"); + _ = app.RunAsync("http://localhost:5000/"); - string result = RunCommand("python", "trace-context/test/test.py http://localhost:5000/"); + (var stdout, var stderr) = await RunCommand("python", "-W ignore trace-context/test/test.py http://localhost:5000/"); // Assert - string lastLine = ParseLastLine(result); + // TODO: after W3C Trace Context test suite passes, it might go in standard output + string lastLine = ParseLastLine(stderr); - this.output.WriteLine("result:" + result); + this.output.WriteLine("[stderr]" + stderr); + this.output.WriteLine("[stdout]" + stdout); // Assert on the last line - Assert.StartsWith("OK", lastLine); + Assert.StartsWith("OK", lastLine, StringComparison.Ordinal); } public void Dispose() @@ -84,9 +86,9 @@ public void Dispose() this.httpClient.Dispose(); } - private static string RunCommand(string command, string args) + private static async Task<(string StdOut, string StdErr)> RunCommand(string command, string args) { - using var proc = new Process + using var process = new Process { StartInfo = new ProcessStartInfo { @@ -99,12 +101,43 @@ private static string RunCommand(string command, string args) WorkingDirectory = ".", }, }; - proc.Start(); + process.Start(); - // TODO: after W3C Trace Context test suite passes, it might go in standard output - var results = proc.StandardError.ReadToEnd(); - proc.WaitForExit(); - return results; + // See https://stackoverflow.com/a/16326426/1064169 and + // https://learn.microsoft.com/dotnet/api/system.diagnostics.processstartinfo.redirectstandardoutput. + using var outputTokenSource = new CancellationTokenSource(); + + var readOutput = ReadOutputAsync(process, outputTokenSource.Token); + + try + { + await process.WaitForExitAsync(); + } + catch (OperationCanceledException) + { + try + { + process.Kill(entireProcessTree: true); + } + catch (Exception) + { + // Ignore + } + } + finally + { + await outputTokenSource.CancelAsync(); + } + + try + { + return await readOutput; + } + finally + { + process.Dispose(); + outputTokenSource.Dispose(); + } } private static string ParseLastLine(string output) @@ -119,7 +152,62 @@ private static string ParseLastLine(string output) return output.Substring(lastNewLineCharacterPos + 1); } - public class Data + private static async Task<(string Output, string Error)> ReadOutputAsync( + Process process, + CancellationToken cancellationToken) + { + var processErrors = ConsumeStreamAsync(process.StandardError, process.StartInfo.RedirectStandardError, cancellationToken); + var processOutput = ConsumeStreamAsync(process.StandardOutput, process.StartInfo.RedirectStandardOutput, cancellationToken); + + await Task.WhenAll(processErrors, processOutput); + + string error = string.Empty; + string output = string.Empty; + + if (processErrors.Status == TaskStatus.RanToCompletion) + { + error = (await processErrors).ToString(); + } + + if (processOutput.Status == TaskStatus.RanToCompletion) + { + output = (await processOutput).ToString(); + } + + return (output, error); + } + + private static Task ConsumeStreamAsync( + StreamReader reader, + bool isRedirected, + CancellationToken cancellationToken) + { + return isRedirected ? + Task.Run(() => ProcessStream(reader, cancellationToken), cancellationToken) : + Task.FromResult(new StringBuilder(0)); + + static async Task ProcessStream( + StreamReader reader, + CancellationToken cancellationToken) + { + var builder = new StringBuilder(); + + try + { + builder.Append(await reader.ReadToEndAsync(cancellationToken)); + } + catch (OperationCanceledException) + { + // Ignore + } + + return builder; + } + } + +#pragma warning disable CA1812 // Avoid uninstantiated internal classes + internal sealed class Data +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { [JsonPropertyName("url")] public string? Url { get; set; } diff --git a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/docker-compose.yml b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/docker-compose.yml index b777fc67447..4f59a64c6aa 100644 --- a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/docker-compose.yml +++ b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/docker-compose.yml @@ -1,7 +1,6 @@ # Start a container and then run OpenTelemetry W3C Trace Context tests. # This should be run from the root of the repo: # opentelemetry>docker compose --file=test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/docker-compose.yml --project-directory=. up --exit-code-from=tests --build -version: '3.7' services: tests: diff --git a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/requirements.txt b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/requirements.txt new file mode 100644 index 00000000000..8a0383d4197 --- /dev/null +++ b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/requirements.txt @@ -0,0 +1 @@ +aiohttp == 3.12.15 diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/IntegrationTests.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/IntegrationTests.cs index cb592e8906e..a5ad1d33244 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/IntegrationTests.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/IntegrationTests.cs @@ -55,7 +55,7 @@ public void WithActivities( b => b.AddSource(ChildActivitySource)) .Build(); - ITracer otTracer = new TracerShim( + var otTracer = new TracerShim( tracerProvider, Propagators.DefaultTextMapPropagator); @@ -99,7 +99,7 @@ public void WithActivities( } } - private class TestSampler : Sampler + private sealed class TestSampler : Sampler { private readonly Func shouldSampleDelegate; diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/ListenAndSampleAllActivitySources.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/ListenAndSampleAllActivitySources.cs index eccc55b95eb..fee708dcf00 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/ListenAndSampleAllActivitySources.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/ListenAndSampleAllActivitySources.cs @@ -1,35 +1,11 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -using System.Diagnostics; using Xunit; namespace OpenTelemetry.Shims.OpenTracing.Tests; [CollectionDefinition(nameof(ListenAndSampleAllActivitySources))] -public sealed class ListenAndSampleAllActivitySources : ICollectionFixture -{ - public sealed class Fixture : IDisposable - { - private readonly ActivityListener listener; - - public Fixture() - { - Activity.DefaultIdFormat = ActivityIdFormat.W3C; - Activity.ForceDefaultIdFormat = true; - - this.listener = new ActivityListener - { - ShouldListenTo = _ => true, - Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllData, - }; - - ActivitySource.AddActivityListener(this.listener); - } - - public void Dispose() - { - this.listener.Dispose(); - } - } -} +#pragma warning disable CA1515 // Consider making public types internal +public sealed class ListenAndSampleAllActivitySources : ICollectionFixture; +#pragma warning restore CA1515 // Consider making public types internal diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/ListenAndSampleAllActivitySourcesFixture.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/ListenAndSampleAllActivitySourcesFixture.cs new file mode 100644 index 00000000000..5240385d83b --- /dev/null +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/ListenAndSampleAllActivitySourcesFixture.cs @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Diagnostics; + +namespace OpenTelemetry.Shims.OpenTracing.Tests; + +#pragma warning disable CA1515 // Consider making public types internal +public sealed class ListenAndSampleAllActivitySourcesFixture : IDisposable +#pragma warning restore CA1515 // Consider making public types internal +{ + private readonly ActivityListener listener; + + public ListenAndSampleAllActivitySourcesFixture() + { + Activity.DefaultIdFormat = ActivityIdFormat.W3C; + Activity.ForceDefaultIdFormat = true; + + this.listener = new ActivityListener + { + ShouldListenTo = _ => true, + Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllDataAndRecorded, + }; + + ActivitySource.AddActivityListener(this.listener); + } + + public void Dispose() + { + this.listener.Dispose(); + } +} diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/OpenTelemetry.Shims.OpenTracing.Tests.csproj b/test/OpenTelemetry.Shims.OpenTracing.Tests/OpenTelemetry.Shims.OpenTracing.Tests.csproj index d21c157d1b9..eae8ca329de 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/OpenTelemetry.Shims.OpenTracing.Tests.csproj +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/OpenTelemetry.Shims.OpenTracing.Tests.csproj @@ -8,9 +8,6 @@ - - - diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanBuilderShimTests.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanBuilderShimTests.cs index b99bcb2f890..5e8b2adc011 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanBuilderShimTests.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanBuilderShimTests.cs @@ -94,8 +94,8 @@ public void AsChildOf_WithSpan() public void Start_ActivityOperationRootSpanChecks() { // Create an activity - using var activity = new Activity("foo") - .SetIdFormat(ActivityIdFormat.W3C) + using var activity = new Activity("foo"); + activity.SetIdFormat(ActivityIdFormat.W3C) .Start(); // matching root operation name @@ -132,7 +132,7 @@ public void AsChildOf_MultipleCallsWithSpan() Assert.NotNull(spanShim.Span.Activity); Assert.Equal("foo", spanShim.Span.Activity.OperationName); - Assert.Contains(spanShim.Context.TraceId, spanShim.Span.Activity.TraceId.ToHexString()); + Assert.Contains(spanShim.Context.TraceId, spanShim.Span.Activity.TraceId.ToHexString(), StringComparison.Ordinal); // TODO: Check for multi level parenting } @@ -191,7 +191,7 @@ public void AsChildOf_MultipleCallsWithSpanContext() var spanShim = (SpanShim)shim.Start(); Assert.NotNull(spanShim.Span.Activity); Assert.Equal("foo", spanShim.Span.Activity.OperationName); - Assert.Contains(spanContext1.TraceId, spanShim.Span.Activity.ParentId); + Assert.Contains(spanContext1.TraceId, spanShim.Span.Activity.ParentId, StringComparison.Ordinal); Assert.Equal(spanContext2.SpanId, spanShim.Span.Activity.Links.First().Context.SpanId.ToHexString()); } diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/TestFormatTextMap.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestFormatTextMap.cs index 83d04095313..c1f7c7e3d08 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/TestFormatTextMap.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestFormatTextMap.cs @@ -5,6 +5,6 @@ namespace OpenTelemetry.Shims.OpenTracing.Tests; -internal class TestFormatTextMap : IFormat +internal sealed class TestFormatTextMap : IFormat { } diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpan.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpan.cs index f1e836f76b7..8cfb51c457a 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpan.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpan.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Shims.OpenTracing.Tests; -internal class TestSpan : ISpan +internal sealed class TestSpan : ISpan { public ISpanContext Context => throw new NotImplementedException(); diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpanContext.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpanContext.cs index d0b5af69991..80f207e4a94 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpanContext.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpanContext.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Shims.OpenTracing.Tests; -internal class TestSpanContext : ISpanContext +internal sealed class TestSpanContext : ISpanContext { public string TraceId => throw new NotImplementedException(); diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/TestTextMap.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestTextMap.cs index 7396b5543e6..39e5893fbe3 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/TestTextMap.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestTextMap.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Shims.OpenTracing.Tests; -internal class TestTextMap : ITextMap +internal sealed class TestTextMap : ITextMap { public bool GetEnumeratorCalled { get; private set; } diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs index 64f08959424..0fb2c90b1ba 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs @@ -132,7 +132,7 @@ public void InjectExtract_TextMap_Ok() AssertOpenTracerSpanContextEqual(spanContextShim, extractedSpanContext!); } - private static void AssertOpenTracerSpanContextEqual(ISpanContext source, ISpanContext target) + private static void AssertOpenTracerSpanContextEqual(SpanContextShim source, ISpanContext target) { Assert.Equal(source.TraceId, target.TraceId); Assert.Equal(source.SpanId, target.SpanId); @@ -144,7 +144,7 @@ private static void AssertOpenTracerSpanContextEqual(ISpanContext source, ISpanC /// Simple ITextMap implementation used for the inject/extract tests. /// /// - private class TextMapCarrier : ITextMap + private sealed class TextMapCarrier : ITextMap { private readonly Dictionary map = new(); @@ -159,22 +159,4 @@ public void Set(string key, string value) IEnumerator IEnumerable.GetEnumerator() => this.map.GetEnumerator(); } - - /// - /// Simple IBinary implementation used for the inject/extract tests. - /// - /// - private class BinaryCarrier : IBinary - { - private readonly MemoryStream carrierStream = new(); - - public MemoryStream Get() => this.carrierStream; - - public void Set(MemoryStream stream) - { - this.carrierStream.SetLength(stream.Length); - this.carrierStream.Seek(0, SeekOrigin.Begin); - stream.CopyTo(this.carrierStream, (int)this.carrierStream.Length); - } - } } diff --git a/test/OpenTelemetry.Tests.Stress.Logs/DummyProcessor.cs b/test/OpenTelemetry.Tests.Stress.Logs/DummyProcessor.cs index aeb1963ec74..56e0629be10 100644 --- a/test/OpenTelemetry.Tests.Stress.Logs/DummyProcessor.cs +++ b/test/OpenTelemetry.Tests.Stress.Logs/DummyProcessor.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Tests.Stress; -internal class DummyProcessor : BaseProcessor +internal sealed class DummyProcessor : BaseProcessor { public override void OnEnd(LogRecord record) { diff --git a/test/OpenTelemetry.Tests.Stress.Logs/Payload.cs b/test/OpenTelemetry.Tests.Stress.Logs/Payload.cs deleted file mode 100644 index fa8041eeb56..00000000000 --- a/test/OpenTelemetry.Tests.Stress.Logs/Payload.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -namespace OpenTelemetry.Tests.Stress; - -internal class Payload -{ - public int Field001 = 0; - public float Field002 = 2.718281828f; - public double Field003 = 3.141592653589793d; - public string Field004 = "Hello, World!"; - public bool Field005 = true; - public int Field006 = 6; - public int Field007 = 7; - public int Field008 = 8; - public int Field009 = 9; - public int Field010 = 10; - public int Field011 = 11; - public int Field012 = 12; - public int Field013 = 13; - public int Field014 = 14; - public int Field015 = 15; - public int Field016 = 16; - public int Field017 = 17; - public int Field018 = 18; - public int Field019 = 19; - public int Field020 = 20; - public int Field021 = 21; - public int Field022 = 22; - public int Field023 = 23; - public int Field024 = 24; - public int Field025 = 25; - public int Field026 = 26; - public int Field027 = 27; - public int Field028 = 28; - public int Field029 = 29; - public int Field030 = 30; - public int Field031 = 31; - public int Field032 = 32; - public int Field033 = 33; - public int Field034 = 34; - public int Field035 = 35; - public int Field036 = 36; - public int Field037 = 37; - public int Field038 = 38; - public int Field039 = 39; - public int Field040 = 40; - public int Field041 = 41; - public int Field042 = 42; - public int Field043 = 43; - public int Field044 = 44; - public int Field045 = 45; - public int Field046 = 46; - public int Field047 = 47; - public int Field048 = 48; - public int Field049 = 49; - public int Field050 = 50; - public int Field051 = 51; - public int Field052 = 52; - public int Field053 = 53; - public int Field054 = 54; - public int Field055 = 55; - public int Field056 = 56; - public int Field057 = 57; - public int Field058 = 58; - public int Field059 = 59; - public int Field060 = 60; - public int Field061 = 61; - public int Field062 = 62; - public int Field063 = 63; - public int Field064 = 64; - public int Field065 = 65; - public int Field066 = 66; - public int Field067 = 67; - public int Field068 = 68; - public int Field069 = 69; - public int Field070 = 70; - public int Field071 = 71; - public int Field072 = 72; - public int Field073 = 73; - public int Field074 = 74; - public int Field075 = 75; - public int Field076 = 76; - public int Field077 = 77; - public int Field078 = 78; - public int Field079 = 79; - public int Field080 = 80; - public int Field081 = 81; - public int Field082 = 82; - public int Field083 = 83; - public int Field084 = 84; - public int Field085 = 85; - public int Field086 = 86; - public int Field087 = 87; - public int Field088 = 88; - public int Field089 = 89; - public int Field090 = 90; - public int Field091 = 91; - public int Field092 = 92; - public int Field093 = 93; - public int Field094 = 94; - public int Field095 = 95; - public int Field096 = 96; - public int Field097 = 97; - public int Field098 = 98; - public int Field099 = 99; -} diff --git a/test/OpenTelemetry.Tests.Stress.Logs/Program.cs b/test/OpenTelemetry.Tests.Stress.Logs/Program.cs index f8ecff50648..773dac95ca7 100644 --- a/test/OpenTelemetry.Tests.Stress.Logs/Program.cs +++ b/test/OpenTelemetry.Tests.Stress.Logs/Program.cs @@ -5,16 +5,17 @@ namespace OpenTelemetry.Tests.Stress; -public static class Program +internal static class Program { public static int Main(string[] args) { return StressTestFactory.RunSynchronously(args); } - private sealed class LogsStressTest : StressTest +#pragma warning disable CA1812 // Avoid uninstantiated internal classes + private sealed class LogsStressTest : StressTests +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { - private static readonly Payload Payload = new(); private readonly ILoggerFactory loggerFactory; private readonly ILogger logger; @@ -32,6 +33,16 @@ public LogsStressTest(StressTestOptions options) this.logger = this.loggerFactory.CreateLogger(); } + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.loggerFactory.Dispose(); + } + + base.Dispose(disposing); + } + protected override void RunWorkItemInParallel() { this.logger.FoodRecallNotice( @@ -41,15 +52,5 @@ protected override void RunWorkItemInParallel() recallReasonDescription: "due to a possible health risk from Listeria monocytogenes", companyName: "Contoso Fresh Vegetables, Inc."); } - - protected override void Dispose(bool isDisposing) - { - if (isDisposing) - { - this.loggerFactory.Dispose(); - } - - base.Dispose(isDisposing); - } } } diff --git a/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs b/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs index 17360444c8e..dd46a5aa853 100644 --- a/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs +++ b/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs @@ -8,7 +8,7 @@ namespace OpenTelemetry.Tests.Stress; -public static class Program +internal static class Program { private enum MetricsStressTestType { @@ -24,7 +24,9 @@ public static int Main(string[] args) return StressTestFactory.RunSynchronously(args); } - private sealed class MetricsStressTest : StressTest +#pragma warning disable CA1812 // Avoid uninstantiated internal classes + private sealed class MetricsStressTest : StressTests +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { private const int ArraySize = 10; private const int MaxHistogramMeasurement = 1000; @@ -51,7 +53,7 @@ public MetricsStressTest(MetricsStressTestOptions options) if (options.PrometheusTestMetricsPort != 0) { - builder.AddPrometheusHttpListener(o => o.UriPrefixes = new string[] { $"http://localhost:{options.PrometheusTestMetricsPort}/" }); + builder.AddPrometheusHttpListener(o => o.UriPrefixes = [$"http://localhost:{options.PrometheusTestMetricsPort}/"]); } if (options.EnableExemplars) @@ -62,8 +64,8 @@ public MetricsStressTest(MetricsStressTestOptions options) if (options.AddViewToFilterTags) { builder - .AddView("TestCounter", new MetricStreamConfiguration { TagKeys = new string[] { "DimName1" } }) - .AddView("TestHistogram", new MetricStreamConfiguration { TagKeys = new string[] { "DimName1" } }); + .AddView("TestCounter", new MetricStreamConfiguration { TagKeys = ["DimName1"] }) + .AddView("TestHistogram", new MetricStreamConfiguration { TagKeys = ["DimName1"] }); } if (options.AddOtlpExporter) @@ -77,6 +79,16 @@ public MetricsStressTest(MetricsStressTestOptions options) this.meterProvider = builder.Build(); } + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.meterProvider.Dispose(); + } + + base.Dispose(disposing); + } + protected override void WriteRunInformationToConsole() { if (this.Options.PrometheusTestMetricsPort != 0) @@ -91,6 +103,7 @@ protected override void RunWorkItemInParallel() if (this.Options.TestType == MetricsStressTestType.Histogram) { TestHistogram.Record( +#pragma warning disable CA5394 // Do not use random number generators in secure applications random.Next(MaxHistogramMeasurement), new("DimName1", DimensionValues[random.Next(0, ArraySize)]), new("DimName2", DimensionValues[random.Next(0, ArraySize)]), @@ -103,21 +116,14 @@ protected override void RunWorkItemInParallel() new("DimName1", DimensionValues[random.Next(0, ArraySize)]), new("DimName2", DimensionValues[random.Next(0, ArraySize)]), new("DimName3", DimensionValues[random.Next(0, ArraySize)])); +#pragma warning restore CA5394 // Do not use random number generators in secure applications } } - - protected override void Dispose(bool isDisposing) - { - if (isDisposing) - { - this.meterProvider.Dispose(); - } - - base.Dispose(isDisposing); - } } +#pragma warning disable CA1812 // Avoid uninstantiated internal classes private sealed class MetricsStressTestOptions : StressTestOptions +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { [JsonConverter(typeof(JsonStringEnumConverter))] [Option('t', "type", HelpText = "The metrics stress test type to run. Valid values: [Histogram, Counter]. Default value: Histogram.", Required = false)] diff --git a/test/OpenTelemetry.Tests.Stress.Traces/Program.cs b/test/OpenTelemetry.Tests.Stress.Traces/Program.cs index 422a44a99ef..4861e39aef7 100644 --- a/test/OpenTelemetry.Tests.Stress.Traces/Program.cs +++ b/test/OpenTelemetry.Tests.Stress.Traces/Program.cs @@ -6,14 +6,16 @@ namespace OpenTelemetry.Tests.Stress; -public static class Program +internal static class Program { public static int Main(string[] args) { return StressTestFactory.RunSynchronously(args); } - private sealed class TracesStressTest : StressTest +#pragma warning disable CA1812 // Avoid uninstantiated internal classes + private sealed class TracesStressTest : StressTests +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { private static readonly ActivitySource ActivitySource = new("OpenTelemetry.Tests.Stress"); private readonly TracerProvider tracerProvider; @@ -26,21 +28,21 @@ public TracesStressTest(StressTestOptions options) .Build(); } - protected override void RunWorkItemInParallel() + protected override void Dispose(bool disposing) { - using var activity = ActivitySource.StartActivity("test"); + if (disposing) + { + this.tracerProvider.Dispose(); + } - activity?.SetTag("foo", "value"); + base.Dispose(disposing); } - protected override void Dispose(bool isDisposing) + protected override void RunWorkItemInParallel() { - if (isDisposing) - { - this.tracerProvider.Dispose(); - } + using var activity = ActivitySource.StartActivity("test"); - base.Dispose(isDisposing); + activity?.SetTag("foo", "value"); } } } diff --git a/test/OpenTelemetry.Tests.Stress/Program.cs b/test/OpenTelemetry.Tests.Stress/Program.cs index a5f6fb8975e..ce67d1c8511 100644 --- a/test/OpenTelemetry.Tests.Stress/Program.cs +++ b/test/OpenTelemetry.Tests.Stress/Program.cs @@ -3,14 +3,16 @@ namespace OpenTelemetry.Tests.Stress; -public static class Program +internal static class Program { public static int Main(string[] args) { return StressTestFactory.RunSynchronously(args); } - private sealed class DemoStressTest : StressTest +#pragma warning disable CA1812 // Avoid uninstantiated internal classes + private sealed class DemoStressTest : StressTests +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { public DemoStressTest(StressTestOptions options) : base(options) diff --git a/test/OpenTelemetry.Tests.Stress/StressTestFactory.cs b/test/OpenTelemetry.Tests.Stress/StressTestFactory.cs index 6f3e7ff9ea7..aa67a66e670 100644 --- a/test/OpenTelemetry.Tests.Stress/StressTestFactory.cs +++ b/test/OpenTelemetry.Tests.Stress/StressTestFactory.cs @@ -5,16 +5,18 @@ namespace OpenTelemetry.Tests.Stress; +#pragma warning disable CA1515 // Consider making public types internal public static class StressTestFactory +#pragma warning restore CA1515 // Consider making public types internal { public static int RunSynchronously(string[] commandLineArguments) - where TStressTest : StressTest + where TStressTest : StressTests { return RunSynchronously(commandLineArguments); } public static int RunSynchronously(string[] commandLineArguments) - where TStressTest : StressTest + where TStressTest : StressTests where TStressTestOptions : StressTestOptions { return Parser.Default.ParseArguments(commandLineArguments) diff --git a/test/OpenTelemetry.Tests.Stress/StressTestNativeMethods.cs b/test/OpenTelemetry.Tests.Stress/StressTestNativeMethods.cs index da3df1c2864..8222c9dc9fc 100644 --- a/test/OpenTelemetry.Tests.Stress/StressTestNativeMethods.cs +++ b/test/OpenTelemetry.Tests.Stress/StressTestNativeMethods.cs @@ -23,6 +23,7 @@ public static ulong GetCpuCycles() } [DllImport("kernel32.dll")] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool QueryProcessCycleTime(IntPtr hProcess, out ulong cycles); } diff --git a/test/OpenTelemetry.Tests.Stress/StressTestOptions.cs b/test/OpenTelemetry.Tests.Stress/StressTestOptions.cs index 2dcb2b2e47c..4e5f9387355 100644 --- a/test/OpenTelemetry.Tests.Stress/StressTestOptions.cs +++ b/test/OpenTelemetry.Tests.Stress/StressTestOptions.cs @@ -5,7 +5,9 @@ namespace OpenTelemetry.Tests.Stress; +#pragma warning disable CA1515 // Consider making public types internal public class StressTestOptions +#pragma warning restore CA1515 // Consider making public types internal { [Option('c', "concurrency", HelpText = "The concurrency (maximum degree of parallelism) for the stress test. Default value: Environment.ProcessorCount.", Required = false)] public int Concurrency { get; set; } diff --git a/test/OpenTelemetry.Tests.Stress/StressTest.cs b/test/OpenTelemetry.Tests.Stress/StressTests.cs similarity index 95% rename from test/OpenTelemetry.Tests.Stress/StressTest.cs rename to test/OpenTelemetry.Tests.Stress/StressTests.cs index cc4e1cb14fd..4a39deb0427 100644 --- a/test/OpenTelemetry.Tests.Stress/StressTest.cs +++ b/test/OpenTelemetry.Tests.Stress/StressTests.cs @@ -3,19 +3,20 @@ using System.Diagnostics; using System.Diagnostics.Metrics; +using System.Globalization; using System.Runtime.InteropServices; using System.Text.Json; using OpenTelemetry.Metrics; namespace OpenTelemetry.Tests.Stress; -public abstract class StressTest : IDisposable +public abstract class StressTests : IDisposable where T : StressTestOptions { private volatile bool bContinue = true; private volatile string output = "Test results not available yet."; - protected StressTest(T options) + protected StressTests(T options) { this.Options = options ?? throw new ArgumentNullException(nameof(options)); } @@ -71,7 +72,7 @@ public void RunSynchronously() using var meterProvider = options.PrometheusInternalMetricsPort != 0 ? Sdk.CreateMeterProviderBuilder() .AddMeter(meter.Name) .AddRuntimeInstrumentation() - .AddPrometheusHttpListener(o => o.UriPrefixes = new string[] { $"http://localhost:{options.PrometheusInternalMetricsPort}/" }) + .AddPrometheusHttpListener(o => o.UriPrefixes = [$"http://localhost:{options.PrometheusInternalMetricsPort}/"]) .Build() : null; var statistics = new MeasurementData[options.Concurrency]; @@ -111,7 +112,7 @@ public void RunSynchronously() switch (key) { case ConsoleKey.Enter: - Console.WriteLine(string.Format("{0} {1}", DateTime.UtcNow.ToString("O"), this.output)); + Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0} {1}", DateTime.UtcNow.ToString("O"), this.output)); break; case ConsoleKey.Escape: this.bContinue = false; @@ -197,16 +198,16 @@ public void RunSynchronously() #endif } - protected virtual void WriteRunInformationToConsole() + protected virtual void Dispose(bool disposing) { } - protected abstract void RunWorkItemInParallel(); - - protected virtual void Dispose(bool isDisposing) + protected virtual void WriteRunInformationToConsole() { } + protected abstract void RunWorkItemInParallel(); + // Padding to avoid false sharing. // For most systems, the cache line size should be less than or equal to 128 bytes. private struct MeasurementData diff --git a/test/OpenTelemetry.Tests/BaseExporterTest.cs b/test/OpenTelemetry.Tests/BaseExporterTests.cs similarity index 65% rename from test/OpenTelemetry.Tests/BaseExporterTest.cs rename to test/OpenTelemetry.Tests/BaseExporterTests.cs index 39095636996..0bd99875d01 100644 --- a/test/OpenTelemetry.Tests/BaseExporterTest.cs +++ b/test/OpenTelemetry.Tests/BaseExporterTests.cs @@ -5,19 +5,19 @@ namespace OpenTelemetry.Tests; -public class BaseExporterTest +public class BaseExporterTests { [Fact] public void Verify_ForceFlush_HandlesException() { // By default, ForceFlush should return true. - var testExporter = new DelegatingExporter(); + using var testExporter = new DelegatingExporter(); Assert.True(testExporter.ForceFlush()); // BaseExporter should catch any exceptions and return false. - var exceptionTestExporter = new DelegatingExporter + using var exceptionTestExporter = new DelegatingExporter { - OnForceFlushFunc = (timeout) => throw new Exception("test exception"), + OnForceFlushFunc = _ => throw new InvalidOperationException("test exception"), }; Assert.False(exceptionTestExporter.ForceFlush()); } @@ -26,7 +26,7 @@ public void Verify_ForceFlush_HandlesException() public void Verify_Shutdown_HandlesSecond() { // By default, ForceFlush should return true. - var testExporter = new DelegatingExporter(); + using var testExporter = new DelegatingExporter(); Assert.True(testExporter.Shutdown()); // A second Shutdown should return false. @@ -37,9 +37,9 @@ public void Verify_Shutdown_HandlesSecond() public void Verify_Shutdown_HandlesException() { // BaseExporter should catch any exceptions and return false. - var exceptionTestExporter = new DelegatingExporter + using var exceptionTestExporter = new DelegatingExporter { - OnShutdownFunc = (timeout) => throw new Exception("test exception"), + OnShutdownFunc = _ => throw new InvalidOperationException("test exception"), }; Assert.False(exceptionTestExporter.Shutdown()); } diff --git a/test/OpenTelemetry.Tests/BaseProcessorTest.cs b/test/OpenTelemetry.Tests/BaseProcessorTests.cs similarity index 69% rename from test/OpenTelemetry.Tests/BaseProcessorTest.cs rename to test/OpenTelemetry.Tests/BaseProcessorTests.cs index 61516290135..b5d89766b28 100644 --- a/test/OpenTelemetry.Tests/BaseProcessorTest.cs +++ b/test/OpenTelemetry.Tests/BaseProcessorTests.cs @@ -5,17 +5,17 @@ namespace OpenTelemetry.Tests; -public class BaseProcessorTest +public class BaseProcessorTests { [Fact] public void Verify_ForceFlush_HandlesException() { // By default, ForceFlush should return true. - var testProcessor = new DelegatingProcessor(); + using var testProcessor = new DelegatingProcessor(); Assert.True(testProcessor.ForceFlush()); // BaseExporter should catch any exceptions and return false. - testProcessor.OnForceFlushFunc = (timeout) => throw new Exception("test exception"); + testProcessor.OnForceFlushFunc = _ => throw new InvalidOperationException("test exception"); Assert.False(testProcessor.ForceFlush()); } @@ -23,7 +23,7 @@ public void Verify_ForceFlush_HandlesException() public void Verify_Shutdown_HandlesSecond() { // By default, Shutdown should return true. - var testProcessor = new DelegatingProcessor(); + using var testProcessor = new DelegatingProcessor(); Assert.True(testProcessor.Shutdown()); // A second Shutdown should return false. @@ -34,9 +34,9 @@ public void Verify_Shutdown_HandlesSecond() public void Verify_Shutdown_HandlesException() { // BaseExporter should catch any exceptions and return false. - var exceptionTestProcessor = new DelegatingProcessor + using var exceptionTestProcessor = new DelegatingProcessor { - OnShutdownFunc = (timeout) => throw new Exception("test exception"), + OnShutdownFunc = _ => throw new InvalidOperationException("test exception"), }; Assert.False(exceptionTestProcessor.Shutdown()); } @@ -44,7 +44,7 @@ public void Verify_Shutdown_HandlesException() [Fact] public void NoOp() { - var testProcessor = new DelegatingProcessor(); + using var testProcessor = new DelegatingProcessor(); // These two methods are no-op, but account for 7% of the test coverage. testProcessor.OnStart(new object()); diff --git a/test/OpenTelemetry.Tests/Concurrency/MetricsConcurrencyTests.cs b/test/OpenTelemetry.Tests/Concurrency/MetricsConcurrencyTests.cs index e20be44ee87..8f4c4ca1900 100644 --- a/test/OpenTelemetry.Tests/Concurrency/MetricsConcurrencyTests.cs +++ b/test/OpenTelemetry.Tests/Concurrency/MetricsConcurrencyTests.cs @@ -28,7 +28,7 @@ public void MultithreadedLongHistogramTestConcurrencyTest() .WithTestingIterations(100) .WithMemoryAccessRaceCheckingEnabled(true); - var test = TestingEngine.Create(config, this.aggregatorTests.MultiThreadedHistogramUpdateAndSnapShotTest); + using var test = TestingEngine.Create(config, this.aggregatorTests.MultiThreadedHistogramUpdateAndSnapShotTest); test.Run(); diff --git a/test/OpenTelemetry.Tests/EventSourceTest.cs b/test/OpenTelemetry.Tests/EventSourceTests.cs similarity index 92% rename from test/OpenTelemetry.Tests/EventSourceTest.cs rename to test/OpenTelemetry.Tests/EventSourceTests.cs index 5e7d3e2f258..f9680c5a6fb 100644 --- a/test/OpenTelemetry.Tests/EventSourceTest.cs +++ b/test/OpenTelemetry.Tests/EventSourceTests.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Tests; -public class EventSourceTest +public class EventSourceTests { [Fact] public void EventSourceTest_OpenTelemetrySdkEventSource() diff --git a/test/OpenTelemetry.Tests/Internal/AssemblyVersionExtensionsTests.cs b/test/OpenTelemetry.Tests/Internal/AssemblyVersionExtensionsTests.cs index b009846c8d7..fd8f682af1e 100644 --- a/test/OpenTelemetry.Tests/Internal/AssemblyVersionExtensionsTests.cs +++ b/test/OpenTelemetry.Tests/Internal/AssemblyVersionExtensionsTests.cs @@ -25,7 +25,7 @@ public void ParseAssemblyInformationalVersionTests(string informationalVersion, Assert.Equal(expectedVersion, actualVersion); } - private class TestAssembly(string informationalVersion) : Assembly + private sealed class TestAssembly(string informationalVersion) : Assembly { public override object[] GetCustomAttributes(Type attributeType, bool inherit) { diff --git a/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs b/test/OpenTelemetry.Tests/Internal/CircularBufferTests.cs similarity index 99% rename from test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs rename to test/OpenTelemetry.Tests/Internal/CircularBufferTests.cs index 4f19a099e38..bb45f1707f0 100644 --- a/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs +++ b/test/OpenTelemetry.Tests/Internal/CircularBufferTests.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Internal.Tests; -public class CircularBufferTest +public class CircularBufferTests { [Fact] public void CheckInvalidArgument() diff --git a/test/OpenTelemetry.Tests/Internal/JsonStringArrayTagWriterTests.cs b/test/OpenTelemetry.Tests/Internal/JsonStringArrayTagWriterTests.cs index fbdc3da9cf7..ac67d026c19 100644 --- a/test/OpenTelemetry.Tests/Internal/JsonStringArrayTagWriterTests.cs +++ b/test/OpenTelemetry.Tests/Internal/JsonStringArrayTagWriterTests.cs @@ -193,6 +193,13 @@ protected override void OnUnsupportedTagDropped(string tagKey, string tagValueTy { } + protected override bool TryWriteEmptyTag(ref Tag state, string key, object? value) + { + throw new NotImplementedException(); + } + + protected override bool TryWriteByteArrayTag(ref Tag consoleTag, string key, ReadOnlySpan value) => false; + public struct Tag { public string? Key; diff --git a/test/OpenTelemetry.Tests/Internal/MathHelperTest.cs b/test/OpenTelemetry.Tests/Internal/MathHelperTests.cs similarity index 99% rename from test/OpenTelemetry.Tests/Internal/MathHelperTest.cs rename to test/OpenTelemetry.Tests/Internal/MathHelperTests.cs index a0d1c6fa9fc..b838dd78fb1 100644 --- a/test/OpenTelemetry.Tests/Internal/MathHelperTest.cs +++ b/test/OpenTelemetry.Tests/Internal/MathHelperTests.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Internal.Tests; -public class MathHelperTest +public class MathHelperTests { [Theory] [InlineData(0b0000_0000, 8)] diff --git a/test/OpenTelemetry.Tests/Internal/PeriodicExportingMetricReaderHelperTests.cs b/test/OpenTelemetry.Tests/Internal/PeriodicExportingMetricReaderHelperTests.cs index f1d5f843277..236cbe1e727 100644 --- a/test/OpenTelemetry.Tests/Internal/PeriodicExportingMetricReaderHelperTests.cs +++ b/test/OpenTelemetry.Tests/Internal/PeriodicExportingMetricReaderHelperTests.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using Microsoft.Extensions.Configuration; using OpenTelemetry.Exporter; using OpenTelemetry.Metrics; @@ -23,7 +24,9 @@ public void Dispose() [Fact] public void CreatePeriodicExportingMetricReader_Defaults() { +#pragma warning disable CA2000 // Dispose objects before losing scope var reader = CreatePeriodicExportingMetricReader(); +#pragma warning restore CA2000 // Dispose objects before losing scope Assert.Equal(60000, reader.ExportIntervalMilliseconds); Assert.Equal(30000, reader.ExportTimeoutMilliseconds); @@ -34,7 +37,7 @@ public void CreatePeriodicExportingMetricReader_Defaults() public void CreatePeriodicExportingMetricReader_TemporalityPreference_FromOptions() { var value = MetricReaderTemporalityPreference.Delta; - var reader = CreatePeriodicExportingMetricReader(new() + using var reader = CreatePeriodicExportingMetricReader(new() { TemporalityPreference = value, }); @@ -47,7 +50,7 @@ public void CreatePeriodicExportingMetricReader_ExportIntervalMilliseconds_FromO { Environment.SetEnvironmentVariable(PeriodicExportingMetricReaderOptions.OTelMetricExportIntervalEnvVarKey, "88888"); // should be ignored, as value set via options has higher priority var value = 123; - var reader = CreatePeriodicExportingMetricReader(new() + using var reader = CreatePeriodicExportingMetricReader(new() { PeriodicExportingMetricReaderOptions = new() { @@ -63,7 +66,7 @@ public void CreatePeriodicExportingMetricReader_ExportTimeoutMilliseconds_FromOp { Environment.SetEnvironmentVariable(PeriodicExportingMetricReaderOptions.OTelMetricExportTimeoutEnvVarKey, "99999"); // should be ignored, as value set via options has higher priority var value = 456; - var reader = CreatePeriodicExportingMetricReader(new() + using var reader = CreatePeriodicExportingMetricReader(new() { PeriodicExportingMetricReaderOptions = new() { @@ -78,8 +81,8 @@ public void CreatePeriodicExportingMetricReader_ExportTimeoutMilliseconds_FromOp public void CreatePeriodicExportingMetricReader_ExportIntervalMilliseconds_FromEnvVar() { var value = 789; - Environment.SetEnvironmentVariable(PeriodicExportingMetricReaderOptions.OTelMetricExportIntervalEnvVarKey, value.ToString()); - var reader = CreatePeriodicExportingMetricReader(); + Environment.SetEnvironmentVariable(PeriodicExportingMetricReaderOptions.OTelMetricExportIntervalEnvVarKey, value.ToString(CultureInfo.InvariantCulture)); + using var reader = CreatePeriodicExportingMetricReader(); Assert.Equal(value, reader.ExportIntervalMilliseconds); } @@ -88,8 +91,8 @@ public void CreatePeriodicExportingMetricReader_ExportIntervalMilliseconds_FromE public void CreatePeriodicExportingMetricReader_ExportTimeoutMilliseconds_FromEnvVar() { var value = 246; - Environment.SetEnvironmentVariable(PeriodicExportingMetricReaderOptions.OTelMetricExportTimeoutEnvVarKey, value.ToString()); - var reader = CreatePeriodicExportingMetricReader(); + Environment.SetEnvironmentVariable(PeriodicExportingMetricReaderOptions.OTelMetricExportTimeoutEnvVarKey, value.ToString(CultureInfo.InvariantCulture)); + using var reader = CreatePeriodicExportingMetricReader(); Assert.Equal(value, reader.ExportTimeoutMilliseconds); } @@ -131,7 +134,9 @@ private static PeriodicExportingMetricReader CreatePeriodicExportingMetricReader { options ??= new(); +#pragma warning disable CA2000 // Dispose objects before losing scope var dummyMetricExporter = new InMemoryExporter(Array.Empty()); +#pragma warning restore CA2000 // Dispose objects before losing scope return PeriodicExportingMetricReaderHelper.CreatePeriodicExportingMetricReader(dummyMetricExporter, options); } } diff --git a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigParserTest.cs b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigParserTests.cs similarity index 50% rename from test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigParserTest.cs rename to test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigParserTests.cs index 2cb86dcbc14..11bb09e7c01 100644 --- a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigParserTest.cs +++ b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigParserTests.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Internal.Tests; -public class SelfDiagnosticsConfigParserTest +public class SelfDiagnosticsConfigParserTests { [Fact] public void SelfDiagnosticsConfigParser_TryParseFilePath_Success() @@ -72,4 +72,77 @@ public void SelfDiagnosticsConfigParser_TryParseLogLevel() Assert.True(SelfDiagnosticsConfigParser.TryParseLogLevel(configJson, out string? logLevelString)); Assert.Equal("Error", logLevelString); } + + [Fact] + public void SelfDiagnosticsConfigParser_TryParseFormatMessage_Success() + { + string configJson = """ + { + "LogDirectory": "Diagnostics", + "FileSize": 1024, + "LogLevel": "Error", + "FormatMessage": "true" + } + """; + Assert.True(SelfDiagnosticsConfigParser.TryParseFormatMessage(configJson, out bool formatMessage)); + Assert.True(formatMessage); + } + + [Fact] + public void SelfDiagnosticsConfigParser_TryParseFormatMessage_CaseInsensitive() + { + string configJson = """ + { + "LogDirectory": "Diagnostics", + "fileSize": 1024, + "formatMessage": "FALSE" + } + """; + Assert.True(SelfDiagnosticsConfigParser.TryParseFormatMessage(configJson, out bool formatMessage)); + Assert.False(formatMessage); + } + + [Fact] + public void SelfDiagnosticsConfigParser_TryParseFormatMessage_MissingField() + { + string configJson = """ + { + "LogDirectory": "Diagnostics", + "FileSize": 1024, + "LogLevel": "Error" + } + """; + Assert.True(SelfDiagnosticsConfigParser.TryParseFormatMessage(configJson, out bool formatMessage)); + Assert.False(formatMessage); // Should default to false + } + + [Fact] + public void SelfDiagnosticsConfigParser_TryParseFormatMessage_InvalidValue() + { + string configJson = """ + { + "LogDirectory": "Diagnostics", + "FileSize": 1024, + "LogLevel": "Error", + "FormatMessage": "invalid" + } + """; + Assert.False(SelfDiagnosticsConfigParser.TryParseFormatMessage(configJson, out bool formatMessage)); + Assert.False(formatMessage); // Should default to false + } + + [Fact] + public void SelfDiagnosticsConfigParser_TryParseFormatMessage_UnquotedBoolean() + { + string configJson = """ + { + "LogDirectory": "Diagnostics", + "FileSize": 1024, + "LogLevel": "Error", + "FormatMessage": true + } + """; + Assert.True(SelfDiagnosticsConfigParser.TryParseFormatMessage(configJson, out bool formatMessage)); + Assert.True(formatMessage); + } } diff --git a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigRefresherTest.cs b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigRefresherTests.cs similarity index 88% rename from test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigRefresherTest.cs rename to test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigRefresherTests.cs index 939e64671f1..a87d8823cff 100644 --- a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigRefresherTest.cs +++ b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigRefresherTests.cs @@ -9,15 +9,15 @@ namespace OpenTelemetry.Internal.Tests; -public class SelfDiagnosticsConfigRefresherTest +public class SelfDiagnosticsConfigRefresherTests { - private static readonly string ConfigFilePath = SelfDiagnosticsConfigParser.ConfigFileName; + private const string ConfigFilePath = SelfDiagnosticsConfigParser.ConfigFileName; private static readonly byte[] MessageOnNewFile = SelfDiagnosticsConfigRefresher.MessageOnNewFile; private static readonly string MessageOnNewFileString = Encoding.UTF8.GetString(SelfDiagnosticsConfigRefresher.MessageOnNewFile); private readonly ITestOutputHelper output; - public SelfDiagnosticsConfigRefresherTest(ITestOutputHelper output) + public SelfDiagnosticsConfigRefresherTests(ITestOutputHelper output) { this.output = output; } @@ -38,7 +38,7 @@ public void SelfDiagnosticsConfigRefresher_OmitAsConfigured() byte[] actualBytes = ReadFile(logDirectory, bufferSize); string logText = Encoding.UTF8.GetString(actualBytes); this.output.WriteLine(logText); // for debugging in case the test fails - Assert.StartsWith(MessageOnNewFileString, logText); + Assert.StartsWith(MessageOnNewFileString, logText, StringComparison.Ordinal); // The event was omitted Assert.Equal('\0', (char)actualBytes[MessageOnNewFile.Length]); @@ -65,12 +65,12 @@ public void SelfDiagnosticsConfigRefresher_CaptureAsConfigured() int bufferSize = 2 * (MessageOnNewFileString.Length + expectedMessage.Length); byte[] actualBytes = ReadFile(logDirectory, bufferSize); string logText = Encoding.UTF8.GetString(actualBytes); - Assert.StartsWith(MessageOnNewFileString, logText); + Assert.StartsWith(MessageOnNewFileString, logText, StringComparison.Ordinal); // The event was captured string logLine = logText.Substring(MessageOnNewFileString.Length); string logMessage = ParseLogMessage(logLine); - Assert.StartsWith(expectedMessage, logMessage); + Assert.StartsWith(expectedMessage, logMessage, StringComparison.Ordinal); } finally { @@ -88,7 +88,11 @@ private static string ParseLogMessage(string logLine) private static byte[] ReadFile(string logDirectory, int byteCount) { var outputFileName = Path.GetFileName(Process.GetCurrentProcess().MainModule?.FileName) + "." +#if NET + + Environment.ProcessId + ".log"; +#else + Process.GetCurrentProcess().Id + ".log"; +#endif var outputFilePath = Path.Combine(logDirectory, outputFileName); using var file = File.Open(outputFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); byte[] actualBytes = new byte[byteCount]; diff --git a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTests.cs similarity index 85% rename from test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs rename to test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTests.cs index 0ee7d0dc71e..b164d13fff7 100644 --- a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs +++ b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTests.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics.Tracing; +using System.Globalization; using System.IO.MemoryMappedFiles; using System.Text; using OpenTelemetry.Tests; @@ -9,7 +10,7 @@ namespace OpenTelemetry.Internal.Tests; -public class SelfDiagnosticsEventListenerTest +public class SelfDiagnosticsEventListenerTests { private const string LOGFILEPATH = "Diagnostics.log"; private const string Ellipses = "...\n"; @@ -28,8 +29,8 @@ public void SelfDiagnosticsEventListener_constructor_Invalid_Input() [Fact] public void SelfDiagnosticsEventListener_EventSourceSetup_LowerSeverity() { - var configRefresher = new TestSelfDiagnosticsConfigRefresher(); - _ = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); + using var configRefresher = new TestSelfDiagnosticsConfigRefresher(); + using var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); // Emitting a Verbose event. Or any EventSource event with lower severity than Error. OpenTelemetrySdkEventSource.Log.ActivityStarted("Activity started", "1"); @@ -39,8 +40,8 @@ public void SelfDiagnosticsEventListener_EventSourceSetup_LowerSeverity() [Fact] public void SelfDiagnosticsEventListener_EventSourceSetup_HigherSeverity() { - var configRefresher = new TestSelfDiagnosticsConfigRefresher(); - _ = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); + using var configRefresher = new TestSelfDiagnosticsConfigRefresher(); + using var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); // Emitting an Error event. Or any EventSource event with higher than or equal to to Error severity. OpenTelemetrySdkEventSource.Log.TracerProviderException("TestEvent", "Exception Details"); @@ -53,9 +54,9 @@ public void SelfDiagnosticsEventListener_WriteEvent() // Arrange var memoryMappedFile = MemoryMappedFile.CreateFromFile(LOGFILEPATH, FileMode.Create, null, 1024); Stream stream = memoryMappedFile.CreateViewStream(); - var configRefresher = new TestSelfDiagnosticsConfigRefresher(stream); + using var configRefresher = new TestSelfDiagnosticsConfigRefresher(stream); string eventMessage = "Event Message"; - var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); + using var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); // Act: call WriteEvent method directly listener.WriteEvent(eventMessage, null); @@ -70,15 +71,15 @@ public void SelfDiagnosticsEventListener_WriteEvent() [Fact] public void SelfDiagnosticsEventListener_DateTimeGetBytes() { - var configRefresher = new TestSelfDiagnosticsConfigRefresher(); - var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); + using var configRefresher = new TestSelfDiagnosticsConfigRefresher(); + using var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); // Check DateTimeKind of Utc, Local, and Unspecified DateTime[] datetimes = [ - DateTime.SpecifyKind(DateTime.Parse("1996-12-01T14:02:31.1234567-08:00"), DateTimeKind.Utc), - DateTime.SpecifyKind(DateTime.Parse("1996-12-01T14:02:31.1234567-08:00"), DateTimeKind.Local), - DateTime.SpecifyKind(DateTime.Parse("1996-12-01T14:02:31.1234567-08:00"), DateTimeKind.Unspecified), + DateTime.SpecifyKind(DateTime.Parse("1996-12-01T14:02:31.1234567-08:00", CultureInfo.InvariantCulture), DateTimeKind.Utc), + DateTime.SpecifyKind(DateTime.Parse("1996-12-01T14:02:31.1234567-08:00", CultureInfo.InvariantCulture), DateTimeKind.Local), + DateTime.SpecifyKind(DateTime.Parse("1996-12-01T14:02:31.1234567-08:00", CultureInfo.InvariantCulture), DateTimeKind.Unspecified), DateTime.UtcNow, DateTime.Now, ]; @@ -97,7 +98,7 @@ public void SelfDiagnosticsEventListener_DateTimeGetBytes() string[] results = new string[datetimes.Length]; for (int i = 0; i < datetimes.Length; i++) { - int len = listener.DateTimeGetBytes(datetimes[i], buffer, pos); + int len = SelfDiagnosticsEventListener.DateTimeGetBytes(datetimes[i], buffer, pos); results[i] = Encoding.Default.GetString(buffer, pos, len); pos += len; } @@ -109,10 +110,10 @@ public void SelfDiagnosticsEventListener_DateTimeGetBytes() public void SelfDiagnosticsEventListener_EmitEvent_OmitAsConfigured() { // Arrange - var configRefresher = new TestSelfDiagnosticsConfigRefresher(); + using var configRefresher = new TestSelfDiagnosticsConfigRefresher(); var memoryMappedFile = MemoryMappedFile.CreateFromFile(LOGFILEPATH, FileMode.Create, null, 1024); Stream stream = memoryMappedFile.CreateViewStream(); - _ = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); + using var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); // Act: emit an event with severity lower than configured OpenTelemetrySdkEventSource.Log.ActivityStarted("ActivityStart", "123"); @@ -148,8 +149,8 @@ public void SelfDiagnosticsEventListener_EmitEvent_CaptureAsConfigured() // Arrange var memoryMappedFile = MemoryMappedFile.CreateFromFile(LOGFILEPATH, FileMode.Create, null, 1024); Stream stream = memoryMappedFile.CreateViewStream(); - var configRefresher = new TestSelfDiagnosticsConfigRefresher(stream); - _ = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); + using var configRefresher = new TestSelfDiagnosticsConfigRefresher(stream); + using var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); // Act: emit an event with severity equal to configured OpenTelemetrySdkEventSource.Log.TracerProviderException("TestEvent", "Exception Details"); @@ -191,7 +192,7 @@ public void SelfDiagnosticsEventListener_EncodeInBuffer_EnoughSpace() // '\n' will be appended to the original string "abc" after EncodeInBuffer is called. // The byte where '\n' will be placed should not be touched within EncodeInBuffer, so it stays as '\0'. - byte[] expected = Encoding.UTF8.GetBytes("abc\0"); + byte[] expected = "abc\0"u8.ToArray(); AssertBufferOutput(expected, buffer, startPos, endPos + 1); } @@ -204,7 +205,7 @@ public void SelfDiagnosticsEventListener_EncodeInBuffer_NotEnoughSpaceForFullStr // It's a quick estimate by assumption that most Unicode characters takes up to 2 16-bit UTF-16 chars, // which can be up to 4 bytes when encoded in UTF-8. int endPos = SelfDiagnosticsEventListener.EncodeInBuffer("abc", false, buffer, startPos); - byte[] expected = Encoding.UTF8.GetBytes("ab...\0"); + byte[] expected = "ab...\0"u8.ToArray(); AssertBufferOutput(expected, buffer, startPos, endPos + 1); } @@ -214,7 +215,7 @@ public void SelfDiagnosticsEventListener_EncodeInBuffer_NotEvenSpaceForTruncated byte[] buffer = new byte[20]; int startPos = buffer.Length - Ellipses.Length; // Just enough space for "...\n". int endPos = SelfDiagnosticsEventListener.EncodeInBuffer("abc", false, buffer, startPos); - byte[] expected = Encoding.UTF8.GetBytes("...\0"); + byte[] expected = "...\0"u8.ToArray(); AssertBufferOutput(expected, buffer, startPos, endPos + 1); } @@ -233,7 +234,7 @@ public void SelfDiagnosticsEventListener_EncodeInBuffer_IsParameter_EnoughSpace( byte[] buffer = new byte[20]; int startPos = buffer.Length - EllipsesWithBrackets.Length - 6; // Just enough space for "abc" even if "...\n" need to be added. int endPos = SelfDiagnosticsEventListener.EncodeInBuffer("abc", true, buffer, startPos); - byte[] expected = Encoding.UTF8.GetBytes("{abc}\0"); + byte[] expected = "{abc}\0"u8.ToArray(); AssertBufferOutput(expected, buffer, startPos, endPos + 1); } @@ -243,7 +244,7 @@ public void SelfDiagnosticsEventListener_EncodeInBuffer_IsParameter_NotEnoughSpa byte[] buffer = new byte[20]; int startPos = buffer.Length - EllipsesWithBrackets.Length - 5; // Just not space for "...\n". int endPos = SelfDiagnosticsEventListener.EncodeInBuffer("abc", true, buffer, startPos); - byte[] expected = Encoding.UTF8.GetBytes("{ab...}\0"); + byte[] expected = "{ab...}\0"u8.ToArray(); AssertBufferOutput(expected, buffer, startPos, endPos + 1); } @@ -253,7 +254,7 @@ public void SelfDiagnosticsEventListener_EncodeInBuffer_IsParameter_NotEvenSpace byte[] buffer = new byte[20]; int startPos = buffer.Length - EllipsesWithBrackets.Length; // Just enough space for "{...}\n". int endPos = SelfDiagnosticsEventListener.EncodeInBuffer("abc", true, buffer, startPos); - byte[] expected = Encoding.UTF8.GetBytes("{...}\0"); + byte[] expected = "{...}\0"u8.ToArray(); AssertBufferOutput(expected, buffer, startPos, endPos + 1); } @@ -287,7 +288,7 @@ private static void AssertFileOutput(string filePath, string eventMessage) string logLine = Encoding.UTF8.GetString(buffer, 0, totalBytesRead); string logMessage = ParseLogMessage(logLine); - Assert.StartsWith(eventMessage, logMessage); + Assert.StartsWith(eventMessage, logMessage, StringComparison.Ordinal); } private static string ParseLogMessage(string logLine) diff --git a/test/OpenTelemetry.Tests/LoggerExtensions.cs b/test/OpenTelemetry.Tests/LoggerExtensions.cs new file mode 100644 index 00000000000..210a2c77762 --- /dev/null +++ b/test/OpenTelemetry.Tests/LoggerExtensions.cs @@ -0,0 +1,51 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.Logging; + +namespace OpenTelemetry.Tests; + +internal static partial class LoggerExtensions +{ + [LoggerMessage(LogLevel.Information, "Hello world {Data}")] + public static partial void HelloWorld(this ILogger logger, int data); + + [LoggerMessage(LogLevel.Information, "Hello world {Data}")] + public static partial void HelloWorld(this ILogger logger, string data); + + [LoggerMessage(LogLevel.Information, "Hello, World!")] + public static partial void HelloWorld(this ILogger logger); + + [LoggerMessage(LogLevel.Information, "Hello from {Name} {Price}.")] + public static partial void HelloFrom(this ILogger logger, string name, double price); + + [LoggerMessage(LogLevel.Information, "{Food}")] + public static partial void Food(this ILogger logger, object food); + + [LoggerMessage(LogLevel.Information, "Log")] + public static partial void Log(this ILogger logger); + + [LoggerMessage("Log")] + public static partial void Log(this ILogger logger, LogLevel logLevel); + + [LoggerMessage(LogLevel.Information, "Log within a dropped activity")] + public static partial void LogWithinADroppedActivity(this ILogger logger); + + [LoggerMessage(LogLevel.Information, "Log within activity marked as RecordOnly")] + public static partial void LogWithinRecordOnlyActivity(this ILogger logger); + + [LoggerMessage(LogLevel.Information, "Log within activity marked as RecordAndSample")] + public static partial void LogWithinRecordAndSampleActivity(this ILogger logger); + + [LoggerMessage(LogLevel.Information, "Dispose called")] + public static partial void DisposedCalled(this ILogger logger); + + [LoggerMessage(LogLevel.Information, "{Product} {Year}!")] + public static partial void LogProduct(this ILogger logger, string product, int year); + + [LoggerMessage(LogLevel.Information, "{Product} {Year} {Complex}!")] + public static partial void LogProduct(this ILogger logger, string product, int year, object complex); + + [LoggerMessage(LogLevel.Information, "Exception Occurred")] + public static partial void LogException(this ILogger logger, Exception exception); +} diff --git a/test/OpenTelemetry.Tests/Logs/BatchExportLogRecordProcessorOptionsTest.cs b/test/OpenTelemetry.Tests/Logs/BatchExportLogRecordProcessorOptionsTests.cs similarity index 96% rename from test/OpenTelemetry.Tests/Logs/BatchExportLogRecordProcessorOptionsTest.cs rename to test/OpenTelemetry.Tests/Logs/BatchExportLogRecordProcessorOptionsTests.cs index d6ebbc74e07..f9cd6d840c3 100644 --- a/test/OpenTelemetry.Tests/Logs/BatchExportLogRecordProcessorOptionsTest.cs +++ b/test/OpenTelemetry.Tests/Logs/BatchExportLogRecordProcessorOptionsTests.cs @@ -6,9 +6,9 @@ namespace OpenTelemetry.Logs.Tests; -public sealed class BatchExportLogRecordProcessorOptionsTest : IDisposable +public sealed class BatchExportLogRecordProcessorOptionsTests : IDisposable { - public BatchExportLogRecordProcessorOptionsTest() + public BatchExportLogRecordProcessorOptionsTests() { ClearEnvVars(); } diff --git a/test/OpenTelemetry.Tests/Logs/BatchLogRecordExportProcessorTests.cs b/test/OpenTelemetry.Tests/Logs/BatchLogRecordExportProcessorTests.cs index 02835e80e00..cd76a4c8880 100644 --- a/test/OpenTelemetry.Tests/Logs/BatchLogRecordExportProcessorTests.cs +++ b/test/OpenTelemetry.Tests/Logs/BatchLogRecordExportProcessorTests.cs @@ -18,7 +18,9 @@ public void StateValuesAndScopeBufferingTest() List exportedItems = new(); using var processor = new BatchLogRecordExportProcessor( +#pragma warning disable CA2000 // Dispose objects before losing scope new InMemoryExporter(exportedItems), +#pragma warning restore CA2000 // Dispose objects before losing scope scheduledDelayMilliseconds: int.MaxValue); using var scope = scopeProvider.Push(exportedItems); @@ -27,7 +29,7 @@ public void StateValuesAndScopeBufferingTest() var logRecord = pool.Rent(); - var state = new LogRecordTest.DisposingState("Hello world"); + var state = new LogRecordTests.DisposingState("Hello world"); logRecord.ILoggerData.ScopeProvider = scopeProvider; logRecord.StateValues = state; @@ -78,13 +80,15 @@ public void StateBufferingTest() List exportedItems = new(); using var processor = new BatchLogRecordExportProcessor( +#pragma warning disable CA2000 // Dispose objects before losing scope new InMemoryExporter(exportedItems)); +#pragma warning restore CA2000 // Dispose objects before losing scope var pool = LogRecordSharedPool.Current; var logRecord = pool.Rent(); - var state = new LogRecordTest.DisposingState("Hello world"); + var state = new LogRecordTests.DisposingState("Hello world"); logRecord.State = state; processor.OnEnd(logRecord); @@ -111,7 +115,9 @@ public void CopyMadeWhenLogRecordIsFromThreadStaticPoolTest() List exportedItems = new(); using var processor = new BatchLogRecordExportProcessor( +#pragma warning disable CA2000 // Dispose objects before losing scope new InMemoryExporter(exportedItems)); +#pragma warning restore CA2000 // Dispose objects before losing scope var pool = LogRecordThreadStaticPool.Instance; @@ -130,7 +136,9 @@ public void LogRecordAddedToBatchIfNotFromAnyPoolTest() List exportedItems = new(); using var processor = new BatchLogRecordExportProcessor( +#pragma warning disable CA2000 // Dispose objects before losing scope new InMemoryExporter(exportedItems)); +#pragma warning restore CA2000 // Dispose objects before losing scope var logRecord = new LogRecord(); diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs b/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs index d38c82d1a0c..a354081dc9f 100644 --- a/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs +++ b/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs @@ -118,8 +118,8 @@ public void ClearTests() var logRecord1 = pool.Rent(); logRecord1.AttributeStorage = new List>(16) { - new KeyValuePair("key1", "value1"), - new KeyValuePair("key2", "value2"), + new("key1", "value1"), + new("key2", "value2"), }; logRecord1.ScopeStorage = new List(8) { null, null }; @@ -168,9 +168,11 @@ public async Task ExportTest(bool warmup) } } +#pragma warning disable CA2000 // Dispose objects before losing scope using BatchLogRecordExportProcessor processor = new(new NoopExporter()); +#pragma warning restore CA2000 // Dispose objects before losing scope - List tasks = new(); + List tasks = []; for (int i = 0; i < Environment.ProcessorCount; i++) { @@ -178,7 +180,9 @@ public async Task ExportTest(bool warmup) { Random random = new Random(); +#pragma warning disable CA5394 // Do not use insecure randomness await Task.Delay(random.Next(100, 150)); +#pragma warning restore CA5394 // Do not use insecure randomness for (int i = 0; i < 1000; i++) { @@ -189,7 +193,9 @@ public async Task ExportTest(bool warmup) // This should no-op mostly. pool.Return(logRecord); +#pragma warning disable CA5394 // Do not use insecure randomness await Task.Delay(random.Next(0, 20)); +#pragma warning restore CA5394 // Do not use insecure randomness } })); } @@ -230,7 +236,7 @@ public async Task DeadlockTest() var pool = LogRecordSharedPool.Current; - List tasks = new(); + List tasks = []; for (int i = 0; i < Environment.ProcessorCount; i++) { diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordStateProcessorTests.cs b/test/OpenTelemetry.Tests/Logs/LogRecordStateProcessorTests.cs index 8b7cb43a4ec..ff4badcdeaa 100644 --- a/test/OpenTelemetry.Tests/Logs/LogRecordStateProcessorTests.cs +++ b/test/OpenTelemetry.Tests/Logs/LogRecordStateProcessorTests.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; +using OpenTelemetry.Tests; using Xunit; namespace OpenTelemetry.Logs.Tests; @@ -22,7 +23,7 @@ public void LogProcessorSetStateTest(bool includeAttributes, bool parseStateValu { var logger = loggerFactory.CreateLogger("TestLogger"); - logger.LogInformation("Hello world {data}", 1234); + logger.HelloWorld(1234); } Assert.Single(exportedItems); @@ -63,7 +64,7 @@ public void LogProcessorSetStateToUnsupportedTypeTest(bool includeAttributes, bo { var logger = loggerFactory.CreateLogger("TestLogger"); - logger.LogInformation("Hello world {data}", 1234); + logger.HelloWorld(1234); } Assert.Single(exportedItems); @@ -103,7 +104,7 @@ public void LogProcessorSetAttributesTest(bool includeAttributes, bool parseStat { var logger = loggerFactory.CreateLogger("TestLogger"); - logger.LogInformation("Hello world {data}", 1234); + logger.HelloWorld(1234); } Assert.Single(exportedItems); @@ -148,7 +149,7 @@ public void LogProcessorSetAttributesAndStateMixedTest(bool includeAttributes, b { var logger = loggerFactory.CreateLogger("TestLogger"); - logger.LogInformation("Hello world {data}", 1234); + logger.HelloWorld(1234); } Assert.Single(exportedItems); @@ -225,7 +226,7 @@ private static void AssertStateAndAttributes( else { Assert.Null(state); - state = Array.Empty>(); + state = []; } if (attributesExpectedCount > 0) @@ -236,7 +237,7 @@ private static void AssertStateAndAttributes( else { Assert.Null(attributes); - attributes = Array.Empty>(); + attributes = []; } } diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs b/test/OpenTelemetry.Tests/Logs/LogRecordTests.cs similarity index 83% rename from test/OpenTelemetry.Tests/Logs/LogRecordTest.cs rename to test/OpenTelemetry.Tests/Logs/LogRecordTests.cs index d20a822f061..ace0fce475f 100644 --- a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs +++ b/test/OpenTelemetry.Tests/Logs/LogRecordTests.cs @@ -6,14 +6,13 @@ using System.Globalization; using Microsoft.Extensions.Logging; using OpenTelemetry.Exporter; -using OpenTelemetry.Logs; using OpenTelemetry.Tests; using OpenTelemetry.Trace; using Xunit; namespace OpenTelemetry.Logs.Tests; -public sealed class LogRecordTest +public sealed class LogRecordTests { private enum Field { @@ -26,12 +25,12 @@ private enum Field public void CheckCategoryNameForLog() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); - logger.LogInformation("Log"); + logger.Log(); var categoryName = exportedItems[0].CategoryName; - Assert.Equal(typeof(LogRecordTest).FullName, categoryName); + Assert.Equal(typeof(LogRecordTests).FullName, categoryName); } [Theory] @@ -44,10 +43,9 @@ public void CheckCategoryNameForLog() public void CheckLogLevel(LogLevel logLevel) { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); - const string message = "Log {logLevel}"; - logger.Log(logLevel, message, logLevel); + logger.Log(logLevel); var logLevelRecorded = exportedItems[0].LogLevel; Assert.Equal(logLevel, logLevelRecorded); @@ -59,10 +57,9 @@ public void CheckLogLevel(LogLevel logLevel) public void CheckStateForUnstructuredLog(bool includeFormattedMessage) { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: o => o.IncludeFormattedMessage = includeFormattedMessage); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); - const string message = "Hello, World!"; - logger.LogInformation(message); + logger.HelloWorld(); Assert.NotNull(exportedItems[0].State); @@ -72,10 +69,10 @@ public void CheckStateForUnstructuredLog(bool includeFormattedMessage) // state only has {OriginalFormat} Assert.Single(attributes); - Assert.Equal(message, exportedItems[0].Body); + Assert.Equal("Hello, World!", exportedItems[0].Body); if (includeFormattedMessage) { - Assert.Equal(message, exportedItems[0].FormattedMessage); + Assert.Equal("Hello, World!", exportedItems[0].FormattedMessage); } else { @@ -89,11 +86,13 @@ public void CheckStateForUnstructuredLog(bool includeFormattedMessage) public void CheckStateForUnstructuredLogWithStringInterpolation(bool includeFormattedMessage) { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: o => o.IncludeFormattedMessage = includeFormattedMessage); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); #pragma warning disable CA2254 // Template should be a static expression +#pragma warning disable CA1848 // Use the LoggerMessage delegates var message = $"Hello from potato {0.99}."; logger.LogInformation(message); +#pragma warning restore CA1848 // Use the LoggerMessage delegates #pragma warning restore CA2254 // Template should be a static expression Assert.NotNull(exportedItems[0].State); @@ -121,10 +120,9 @@ public void CheckStateForUnstructuredLogWithStringInterpolation(bool includeForm public void CheckStateForStructuredLogWithTemplate(bool includeFormattedMessage) { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: o => o.IncludeFormattedMessage = includeFormattedMessage); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); - const string message = "Hello from {name} {price}."; - logger.LogInformation(message, "tomato", 2.99); + logger.HelloFrom("tomato", 2.99); Assert.NotNull(exportedItems[0].State); @@ -135,18 +133,18 @@ public void CheckStateForStructuredLogWithTemplate(bool includeFormattedMessage) Assert.Equal(3, attributes.Count); // Check if state has name - Assert.Contains(attributes, item => item.Key == "name"); - Assert.Equal("tomato", attributes.First(item => item.Key == "name").Value); + Assert.Contains(attributes, item => item.Key == "Name"); + Assert.Equal("tomato", attributes.First(item => item.Key == "Name").Value); // Check if state has price - Assert.Contains(attributes, item => item.Key == "price"); - Assert.Equal(2.99, attributes.First(item => item.Key == "price").Value); + Assert.Contains(attributes, item => item.Key == "Price"); + Assert.Equal(2.99, attributes.First(item => item.Key == "Price").Value); // Check if state has OriginalFormat Assert.Contains(attributes, item => item.Key == "{OriginalFormat}"); - Assert.Equal(message, attributes.First(item => item.Key == "{OriginalFormat}").Value); + Assert.Equal("Hello from {Name} {Price}.", attributes.First(item => item.Key == "{OriginalFormat}").Value); - Assert.Equal(message, exportedItems[0].Body); + Assert.Equal("Hello from {Name} {Price}.", exportedItems[0].Body); if (includeFormattedMessage) { Assert.Equal("Hello from tomato 2.99.", exportedItems[0].FormattedMessage); @@ -163,10 +161,10 @@ public void CheckStateForStructuredLogWithTemplate(bool includeFormattedMessage) public void CheckStateForStructuredLogWithStrongType(bool includeFormattedMessage) { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: o => o.IncludeFormattedMessage = includeFormattedMessage); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); var food = new Food { Name = "artichoke", Price = 3.99 }; - logger.LogInformation("{food}", food); + logger.Food(food); Assert.NotNull(exportedItems[0].State); @@ -177,18 +175,18 @@ public void CheckStateForStructuredLogWithStrongType(bool includeFormattedMessag Assert.Equal(2, attributes.Count); // Check if state has food - Assert.Contains(attributes, item => item.Key == "food"); + Assert.Contains(attributes, item => item.Key == "Food"); - var foodParameter = attributes.First(item => item.Key == "food").Value as Food?; + var foodParameter = attributes.First(item => item.Key == "Food").Value as Food?; Assert.NotNull(foodParameter); Assert.Equal(food.Name, foodParameter.Value.Name); Assert.Equal(food.Price, foodParameter.Value.Price); // Check if state has OriginalFormat Assert.Contains(attributes, item => item.Key == "{OriginalFormat}"); - Assert.Equal("{food}", attributes.First(item => item.Key == "{OriginalFormat}").Value); + Assert.Equal("{Food}", attributes.First(item => item.Key == "{OriginalFormat}").Value); - Assert.Equal("{food}", exportedItems[0].Body); + Assert.Equal("{Food}", exportedItems[0].Body); if (includeFormattedMessage) { Assert.Equal(food.ToString(), exportedItems[0].FormattedMessage); @@ -205,10 +203,10 @@ public void CheckStateForStructuredLogWithStrongType(bool includeFormattedMessag public void CheckStateForStructuredLogWithAnonymousType(bool includeFormattedMessage) { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: o => o.IncludeFormattedMessage = includeFormattedMessage); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); var anonymousType = new { Name = "pumpkin", Price = 5.99 }; - logger.LogInformation("{food}", anonymousType); + logger.Food(anonymousType); Assert.NotNull(exportedItems[0].State); @@ -219,18 +217,18 @@ public void CheckStateForStructuredLogWithAnonymousType(bool includeFormattedMes Assert.Equal(2, attributes.Count); // Check if state has food - Assert.Contains(attributes, item => item.Key == "food"); + Assert.Contains(attributes, item => item.Key == "Food"); - var foodParameter = attributes.First(item => item.Key == "food").Value as dynamic; + var foodParameter = attributes.First(item => item.Key == "Food").Value as dynamic; Assert.NotNull(foodParameter); Assert.Equal(anonymousType.Name, foodParameter!.Name); Assert.Equal(anonymousType.Price, foodParameter!.Price); // Check if state has OriginalFormat Assert.Contains(attributes, item => item.Key == "{OriginalFormat}"); - Assert.Equal("{food}", attributes.First(item => item.Key == "{OriginalFormat}").Value); + Assert.Equal("{Food}", attributes.First(item => item.Key == "{OriginalFormat}").Value); - Assert.Equal("{food}", exportedItems[0].Body); + Assert.Equal("{Food}", exportedItems[0].Body); if (includeFormattedMessage) { Assert.Equal(anonymousType.ToString(), exportedItems[0].FormattedMessage); @@ -247,14 +245,15 @@ public void CheckStateForStructuredLogWithAnonymousType(bool includeFormattedMes public void CheckStateForStructuredLogWithGeneralType(bool includeFormattedMessage) { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: o => o.IncludeFormattedMessage = includeFormattedMessage); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); + var trufflePrice = 299.99; var food = new Dictionary { ["Name"] = "truffle", - ["Price"] = 299.99, + ["Price"] = trufflePrice, }; - logger.LogInformation("{food}", food); + logger.Food(food); Assert.NotNull(exportedItems[0].State); @@ -265,29 +264,21 @@ public void CheckStateForStructuredLogWithGeneralType(bool includeFormattedMessa Assert.Equal(2, attributes.Count); // Check if state has food - Assert.Contains(attributes, item => item.Key == "food"); + Assert.Contains(attributes, item => item.Key == "Food"); - var foodParameter = attributes.First(item => item.Key == "food").Value as Dictionary; + var foodParameter = attributes.First(item => item.Key == "Food").Value as Dictionary; Assert.NotNull(foodParameter); Assert.True(food.Count == foodParameter.Count && !food.Except(foodParameter).Any()); // Check if state has OriginalFormat Assert.Contains(attributes, item => item.Key == "{OriginalFormat}"); - Assert.Equal("{food}", attributes.First(item => item.Key == "{OriginalFormat}").Value); + Assert.Equal("{Food}", attributes.First(item => item.Key == "{OriginalFormat}").Value); - Assert.Equal("{food}", exportedItems[0].Body); + Assert.Equal("{Food}", exportedItems[0].Body); if (includeFormattedMessage) { - var prevCulture = CultureInfo.CurrentCulture; - CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; - try - { - Assert.Equal("[Name, truffle], [Price, 299.99]", exportedItems[0].FormattedMessage); - } - finally - { - CultureInfo.CurrentCulture = prevCulture; - } + var priceInCurrentCulture = trufflePrice.ToString(CultureInfo.CurrentCulture); + Assert.Equal($"[Name, truffle], [Price, {priceInCurrentCulture}]", exportedItems[0].FormattedMessage); } else { @@ -299,13 +290,12 @@ public void CheckStateForStructuredLogWithGeneralType(bool includeFormattedMessa public void CheckStateForExceptionLogged() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); var exceptionMessage = "Exception Message"; - var exception = new Exception(exceptionMessage); + var exception = new InvalidOperationException(exceptionMessage); - const string message = "Exception Occurred"; - logger.LogInformation(exception, message); + logger.LogException(exception); Assert.NotNull(exportedItems[0].State); @@ -326,8 +316,8 @@ public void CheckStateForExceptionLogged() Assert.NotNull(loggedException); Assert.Equal(exceptionMessage, loggedException.Message); - Assert.Equal(message, exportedItems[0].Body); - Assert.Equal(message, state.ToString()); + Assert.Equal("Exception Occurred", exportedItems[0].Body); + Assert.Equal("Exception Occurred", state.ToString()); Assert.Null(exportedItems[0].FormattedMessage); } @@ -335,9 +325,9 @@ public void CheckStateForExceptionLogged() public void CheckStateCanBeSet() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); - logger.LogInformation("This does not matter."); + logger.Log(); var logRecord = exportedItems[0]; logRecord.State = "newState"; @@ -350,17 +340,17 @@ public void CheckStateCanBeSet() public void CheckStateValuesCanBeSet() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); logger.Log( LogLevel.Information, 0, - new List> { new KeyValuePair("Key1", "Value1") }, + new List> { new("Key1", "Value1") }, null, (s, e) => "OpenTelemetry!"); var logRecord = exportedItems[0]; - var expectedStateValues = new List> { new KeyValuePair("Key2", "Value2") }; + var expectedStateValues = new List> { new("Key2", "Value2") }; logRecord.StateValues = expectedStateValues; Assert.Equal(expectedStateValues, logRecord.StateValues); @@ -370,9 +360,9 @@ public void CheckStateValuesCanBeSet() public void CheckFormattedMessageCanBeSet() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeFormattedMessage = true); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); - logger.LogInformation("OpenTelemetry {Greeting} {Subject}!", "Hello", "World"); + logger.HelloFrom("tomato", 3.0); var logRecord = exportedItems[0]; var expectedFormattedMessage = "OpenTelemetry Good Night!"; logRecord.FormattedMessage = expectedFormattedMessage; @@ -384,7 +374,9 @@ public void CheckFormattedMessageCanBeSet() public void CheckStateCanBeSetByProcessor() { var exportedItems = new List(); +#pragma warning disable CA2000 // Dispose objects before losing scope var exporter = new InMemoryExporter(exportedItems); +#pragma warning restore CA2000 // Dispose objects before losing scope using var loggerFactory = LoggerFactory.Create(builder => { builder.AddOpenTelemetry(options => @@ -394,8 +386,8 @@ public void CheckStateCanBeSetByProcessor() }); }); - var logger = loggerFactory.CreateLogger(); - logger.LogInformation($"This does not matter."); + var logger = loggerFactory.CreateLogger(); + logger.Log(); var state = exportedItems[0].State as IReadOnlyList>; Assert.NotNull(state); @@ -407,7 +399,6 @@ public void CheckStateCanBeSetByProcessor() public void CheckStateValuesCanBeSetByProcessor() { var exportedItems = new List(); - var exporter = new InMemoryExporter(exportedItems); using var loggerFactory = LoggerFactory.Create(builder => { builder.AddOpenTelemetry(options => @@ -418,8 +409,8 @@ public void CheckStateValuesCanBeSetByProcessor() }); }); - var logger = loggerFactory.CreateLogger(); - logger.LogInformation("This does not matter."); + var logger = loggerFactory.CreateLogger(); + logger.Log(); var stateValue = exportedItems[0]; Assert.NotNull(stateValue.StateValues); @@ -431,7 +422,6 @@ public void CheckStateValuesCanBeSetByProcessor() public void CheckFormattedMessageCanBeSetByProcessor() { var exportedItems = new List(); - var exporter = new InMemoryExporter(exportedItems); using var loggerFactory = LoggerFactory.Create(builder => { builder.AddOpenTelemetry(options => @@ -442,8 +432,8 @@ public void CheckFormattedMessageCanBeSetByProcessor() }); }); - var logger = loggerFactory.CreateLogger(); - logger.LogInformation("OpenTelemetry {Greeting} {Subject}!", "Hello", "World"); + var logger = loggerFactory.CreateLogger(); + logger.HelloFrom("potato", 2.99); var item = exportedItems[0]; Assert.Equal("OpenTelemetry Good Night!", item.FormattedMessage); @@ -453,9 +443,9 @@ public void CheckFormattedMessageCanBeSetByProcessor() public void CheckTraceIdForLogWithinDroppedActivity() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); - logger.LogInformation("Log within a dropped activity"); + logger.LogWithinADroppedActivity(); var logRecord = exportedItems[0]; Assert.Null(Activity.Current); @@ -470,7 +460,7 @@ public void CheckTraceIdForLogWithinDroppedActivity() public void CheckTraceIdForLogWithinActivityMarkedAsRecordOnly(bool includeTraceState) { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: o => o.IncludeTraceState = includeTraceState); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); var sampler = new RecordOnlySampler(); var exportedActivityList = new List(); @@ -486,7 +476,7 @@ public void CheckTraceIdForLogWithinActivityMarkedAsRecordOnly(bool includeTrace Assert.NotNull(activity); activity.TraceStateString = "key1=value1"; - logger.LogInformation("Log within activity marked as RecordOnly"); + logger.LogWithinRecordOnlyActivity(); var logRecord = exportedItems[0]; var currentActivity = Activity.Current; @@ -509,7 +499,7 @@ public void CheckTraceIdForLogWithinActivityMarkedAsRecordOnly(bool includeTrace public void CheckTraceIdForLogWithinActivityMarkedAsRecordAndSample() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); var sampler = new AlwaysOnSampler(); var exportedActivityList = new List(); @@ -523,7 +513,7 @@ public void CheckTraceIdForLogWithinActivityMarkedAsRecordAndSample() using var activity = activitySource.StartActivity("Activity"); - logger.LogInformation("Log within activity marked as RecordAndSample"); + logger.LogWithinRecordAndSampleActivity(); var logRecord = exportedItems[0]; var currentActivity = Activity.Current; @@ -537,9 +527,9 @@ public void CheckTraceIdForLogWithinActivityMarkedAsRecordAndSample() public void VerifyIncludeFormattedMessage_False() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeFormattedMessage = false); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); - logger.LogInformation("OpenTelemetry!"); + logger.Log(); var logRecord = exportedItems[0]; Assert.Null(logRecord.FormattedMessage); } @@ -548,22 +538,22 @@ public void VerifyIncludeFormattedMessage_False() public void VerifyIncludeFormattedMessage_True() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeFormattedMessage = true); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); - logger.LogInformation("OpenTelemetry!"); + logger.Log(); var logRecord = exportedItems[0]; - Assert.Equal("OpenTelemetry!", logRecord.FormattedMessage); + Assert.Equal("Log", logRecord.FormattedMessage); - logger.LogInformation("OpenTelemetry {Greeting} {Subject}!", "Hello", "World"); + logger.HelloFrom("tomato", 3.11); logRecord = exportedItems[1]; - Assert.Equal("OpenTelemetry Hello World!", logRecord.FormattedMessage); + Assert.Equal("Hello from tomato 3.11.", logRecord.FormattedMessage); } [Fact] public void IncludeFormattedMessageTestWhenFormatterNull() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeFormattedMessage = true); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); logger.Log(LogLevel.Information, default, "Hello World!", null, null!); var logRecord = exportedItems[0]; @@ -586,11 +576,11 @@ public void IncludeFormattedMessageTestWhenFormatterNull() public void VerifyIncludeScopes_False() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeScopes = false); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); using var scope = logger.BeginScope("string_scope"); - logger.LogInformation("OpenTelemetry!"); + logger.Log(); var logRecord = exportedItems[0]; List scopes = []; @@ -602,16 +592,16 @@ public void VerifyIncludeScopes_False() public void VerifyIncludeScopes_True() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeScopes = true); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); using var scope = logger.BeginScope("string_scope"); - logger.LogInformation("OpenTelemetry!"); + logger.Log(); var logRecord = exportedItems[0]; List scopes = []; - logger.LogInformation("OpenTelemetry!"); + logger.Log(); logRecord = exportedItems[1]; int reachedDepth = -1; @@ -640,7 +630,7 @@ public void VerifyIncludeScopes_True() ]; using var scope2 = logger.BeginScope(expectedScope2); - logger.LogInformation("OpenTelemetry!"); + logger.Log(); logRecord = exportedItems[2]; reachedDepth = -1; @@ -671,7 +661,7 @@ public void VerifyIncludeScopes_True() ]; using var scope3 = logger.BeginScope(expectedScope3); - logger.LogInformation("OpenTelemetry!"); + logger.Log(); logRecord = exportedItems[3]; reachedDepth = -1; @@ -701,11 +691,11 @@ public void VerifyIncludeScopes_True() public void VerifyParseStateValues_UsingStandardExtensions(bool parseStateValues) { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = parseStateValues); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); // Tests state parsing with standard extensions. - logger.LogInformation("{Product} {Year}!", "OpenTelemetry", 2021); + logger.LogProduct("OpenTelemetry", 2021); var logRecord = exportedItems[0]; Assert.NotNull(logRecord.StateValues); @@ -726,7 +716,7 @@ public void VerifyParseStateValues_UsingStandardExtensions(bool parseStateValues var complex = new { Property = "Value" }; - logger.LogInformation("{Product} {Year} {Complex}!", "OpenTelemetry", 2021, complex); + logger.LogProduct("OpenTelemetry", 2021, complex); logRecord = exportedItems[1]; Assert.NotNull(logRecord.StateValues); @@ -754,7 +744,7 @@ public void VerifyParseStateValues_UsingStandardExtensions(bool parseStateValues public void ParseStateValuesUsingStructTest() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); // Tests struct IReadOnlyList> parse path. @@ -776,14 +766,14 @@ public void ParseStateValuesUsingStructTest() public void ParseStateValuesUsingListTest() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); // Tests ref IReadOnlyList> parse path. logger.Log( LogLevel.Information, 0, - new List> { new KeyValuePair("Key1", "Value1") }, + new List> { new("Key1", "Value1") }, null, (s, e) => "OpenTelemetry!"); var logRecord = exportedItems[0]; @@ -798,7 +788,7 @@ public void ParseStateValuesUsingListTest() public void ParseStateValuesUsingIEnumerableTest() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); // Tests IEnumerable> parse path. @@ -820,7 +810,7 @@ public void ParseStateValuesUsingIEnumerableTest() public void ParseStateValuesUsingNonconformingCustomTypeTest() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); // Tests unknown state parse path. @@ -850,7 +840,7 @@ public void ParseStateValuesUsingNonconformingCustomTypeTest() public void DisposingStateTest() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); DisposingState state = new DisposingState("Hello world"); @@ -879,7 +869,9 @@ public void DisposingStateTest() [InlineData(false)] public void ReusedLogRecordScopeTest(bool buffer) { +#pragma warning disable CA2000 // Dispose objects before losing scope var processor = new ScopeProcessor(buffer); +#pragma warning restore CA2000 // Dispose objects before losing scope using var loggerFactory = LoggerFactory.Create(builder => { @@ -894,12 +886,12 @@ public void ReusedLogRecordScopeTest(bool buffer) using (var scope1 = logger.BeginScope("scope1")) { - logger.LogInformation("message1"); + logger.Log(); } using (var scope2 = logger.BeginScope("scope2")) { - logger.LogInformation("message2"); + logger.HelloWorld(); } Assert.Equal(2, processor.Scopes.Count); @@ -915,16 +907,16 @@ public void IncludeStateTest() { options.IncludeAttributes = false; }); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); - logger.LogInformation("Hello {world}", "earth"); + logger.HelloWorld("Earth"); var logRecord = exportedItems[0]; Assert.Null(logRecord.State); Assert.Null(logRecord.Attributes); - Assert.Equal("Hello earth", logRecord.Body); + Assert.Equal("Hello world Earth", logRecord.Body); } [Theory] @@ -979,15 +971,15 @@ public void LogRecordInstrumentationScopeTest() { using var loggerFactory = InitializeLoggerFactory(out List exportedItems); - var logger = loggerFactory.CreateLogger(); + var logger = loggerFactory.CreateLogger(); - logger.LogInformation("Hello world!"); + logger.HelloWorld(); var logRecord = exportedItems.FirstOrDefault(); Assert.NotNull(logRecord); Assert.NotNull(logRecord.Logger); - Assert.Equal("OpenTelemetry.Logs.Tests.LogRecordTest", logRecord.Logger.Name); + Assert.Equal("OpenTelemetry.Logs.Tests.LogRecordTests", logRecord.Logger.Name); Assert.Null(logRecord.Logger.Version); } @@ -1011,7 +1003,9 @@ public void LogRecordCategoryNameAliasForInstrumentationScopeTests() var exportedItems = new List(); using (var loggerProvider = Sdk.CreateLoggerProviderBuilder() +#pragma warning disable CA2000 // Dispose objects before losing scope .AddProcessor(new BatchLogRecordExportProcessor(new InMemoryExporter(exportedItems))) +#pragma warning restore CA2000 // Dispose objects before losing scope .Build()) { var logger = loggerProvider.GetLogger("TestName"); @@ -1035,7 +1029,7 @@ private static ILoggerFactory InitializeLoggerFactory(out List export configure?.Invoke(options); options.AddInMemoryExporter(items); }); - builder.AddFilter(typeof(LogRecordTest).FullName, LogLevel.Trace); + builder.AddFilter(typeof(LogRecordTests).FullName, LogLevel.Trace); }); } @@ -1052,7 +1046,7 @@ internal struct Food public StructState(params KeyValuePair[] items) { - this.list = new List>(items); + this.list = [.. items]; } public int Count => this.list.Count; @@ -1086,10 +1080,14 @@ public string? Value { get { +#if NET + ObjectDisposedException.ThrowIf(this.disposed, this); +#else if (this.disposed) { throw new ObjectDisposedException(nameof(DisposingState)); } +#endif return this.value; } @@ -1099,7 +1097,9 @@ public string? Value public KeyValuePair this[int index] => index switch { 0 => new KeyValuePair(nameof(this.Value), this.Value), +#pragma warning disable CA2201 // Do not raise reserved exception types _ => throw new IndexOutOfRangeException(nameof(index)), +#pragma warning restore CA2201 // Do not raise reserved exception types }; public void Dispose() @@ -1118,7 +1118,7 @@ public void Dispose() IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } - private class RedactionProcessor : BaseProcessor + private sealed class RedactionProcessor : BaseProcessor { private readonly Field fieldToUpdate; @@ -1131,11 +1131,11 @@ public override void OnEnd(LogRecord logRecord) { if (this.fieldToUpdate == Field.State) { - logRecord.State = new List> { new KeyValuePair("newStateKey", "newStateValue") }; + logRecord.State = new List> { new("newStateKey", "newStateValue") }; } else if (this.fieldToUpdate == Field.StateValues) { - logRecord.StateValues = new List> { new KeyValuePair("newStateValueKey", "newStateValueValue") }; + logRecord.StateValues = new List> { new("newStateValueKey", "newStateValueValue") }; } else { @@ -1144,13 +1144,13 @@ public override void OnEnd(LogRecord logRecord) } } - private class ListState : IEnumerable> + private sealed class ListState : IEnumerable> { private readonly List> list; public ListState(params KeyValuePair[] items) { - this.list = new List>(items); + this.list = [.. items]; } public IEnumerator> GetEnumerator() @@ -1164,7 +1164,7 @@ IEnumerator IEnumerable.GetEnumerator() } } - private class CustomState + private sealed class CustomState { public const string ToStringValue = "CustomState.ToString"; @@ -1174,7 +1174,7 @@ public override string ToString() => ToStringValue; } - private class ScopeProcessor : BaseProcessor + private sealed class ScopeProcessor : BaseProcessor { private readonly bool buffer; diff --git a/test/OpenTelemetry.Tests/Logs/LoggerFactoryAndResourceBuilderTests.cs b/test/OpenTelemetry.Tests/Logs/LoggerFactoryAndResourceBuilderTests.cs index e955939aea2..e900cd11e6d 100644 --- a/test/OpenTelemetry.Tests/Logs/LoggerFactoryAndResourceBuilderTests.cs +++ b/test/OpenTelemetry.Tests/Logs/LoggerFactoryAndResourceBuilderTests.cs @@ -14,9 +14,16 @@ public sealed class LoggerFactoryAndResourceBuilderTests public void TestLogExporterCanAccessResource() { VerifyResourceBuilder( - assert: (Resource resource) => + assert: resource => { - Assert.Contains(resource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.ToString()!.Contains("unknown_service")); + Assert.Contains( + resource.Attributes, + kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName && +#if NET + kvp.Value.ToString()!.Contains("unknown_service", StringComparison.Ordinal)); +#else + kvp.Value.ToString()!.Contains("unknown_service")); +#endif }); } @@ -28,9 +35,9 @@ public void VerifyResourceBuilder_WithServiceNameEnVar() Environment.SetEnvironmentVariable(OtelServiceNameEnvVarDetector.EnvVarKey, "MyService"); VerifyResourceBuilder( - assert: (Resource resource) => + assert: resource => { - Assert.Contains(resource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.Equals("MyService")); + Assert.Contains(resource.Attributes, kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.Equals("MyService")); }); } finally diff --git a/test/OpenTelemetry.Tests/Logs/LoggerProviderBuilderExtensionsTests.cs b/test/OpenTelemetry.Tests/Logs/LoggerProviderBuilderExtensionsTests.cs index ea25d748604..c956a9a7920 100644 --- a/test/OpenTelemetry.Tests/Logs/LoggerProviderBuilderExtensionsTests.cs +++ b/test/OpenTelemetry.Tests/Logs/LoggerProviderBuilderExtensionsTests.cs @@ -17,7 +17,9 @@ public void LoggerProviderBuilderAddInstrumentationTest() using (var provider = Sdk.CreateLoggerProviderBuilder() .AddInstrumentation() .AddInstrumentation((sp, provider) => new CustomInstrumentation() { Provider = provider }) +#pragma warning disable CA2000 // Dispose objects before losing scope .AddInstrumentation(new CustomInstrumentation()) +#pragma warning restore CA2000 // Dispose objects before losing scope .AddInstrumentation(() => (object?)null) .Build() as LoggerProviderSdk) { @@ -34,7 +36,7 @@ public void LoggerProviderBuilderAddInstrumentationTest() Assert.Null(((CustomInstrumentation)provider.Instrumentations[2]).Provider); Assert.False(((CustomInstrumentation)provider.Instrumentations[2]).Disposed); - instrumentation = new List(provider.Instrumentations); + instrumentation = [.. provider.Instrumentations]; } Assert.True(((CustomInstrumentation)instrumentation[0]).Disposed); @@ -101,7 +103,7 @@ public void LoggerProviderBuilderSetResourceBuilderTests() using var provider = Sdk.CreateLoggerProviderBuilder() .SetResourceBuilder(ResourceBuilder .CreateEmpty() - .AddAttributes(new[] { new KeyValuePair("key1", "value1") })) + .AddAttributes([new KeyValuePair("key1", "value1")])) .Build() as LoggerProviderSdk; Assert.NotNull(provider); @@ -116,7 +118,7 @@ public void LoggerProviderBuilderConfigureResourceBuilderTests() using var provider = Sdk.CreateLoggerProviderBuilder() .ConfigureResource(resource => resource .Clear() - .AddAttributes(new[] { new KeyValuePair("key1", "value1") })) + .AddAttributes([new KeyValuePair("key1", "value1")])) .Build() as LoggerProviderSdk; Assert.NotNull(provider); @@ -335,14 +337,6 @@ protected override void Dispose(bool disposing) } } - private sealed class CustomExporter : BaseExporter - { - public override ExportResult Export(in Batch batch) - { - return ExportResult.Success; - } - } - private sealed class CustomLoggerProviderBuilder : LoggerProviderBuilder { public override LoggerProviderBuilder AddInstrumentation(Func instrumentationFactory) diff --git a/test/OpenTelemetry.Tests/Logs/LoggerProviderExtensionsTests.cs b/test/OpenTelemetry.Tests/Logs/LoggerProviderExtensionsTests.cs index d059dfd4864..20e36e1cc6b 100644 --- a/test/OpenTelemetry.Tests/Logs/LoggerProviderExtensionsTests.cs +++ b/test/OpenTelemetry.Tests/Logs/LoggerProviderExtensionsTests.cs @@ -22,7 +22,9 @@ public void AddProcessorTest() Assert.Null(providerSdk.Processor); +#pragma warning disable CA2000 // Dispose objects before losing scope provider.AddProcessor(new TestProcessor()); +#pragma warning restore CA2000 // Dispose objects before losing scope Assert.NotNull(providerSdk.Processor); } @@ -33,8 +35,10 @@ public void ForceFlushTest() List exportedItems = new(); using var provider = Sdk.CreateLoggerProviderBuilder() .AddProcessor( +#pragma warning disable CA2000 // Dispose objects before losing scope new BatchLogRecordExportProcessor( new InMemoryExporter(exportedItems), +#pragma warning restore CA2000 // Dispose objects before losing scope scheduledDelayMilliseconds: int.MaxValue)) .Build(); diff --git a/test/OpenTelemetry.Tests/Logs/LoggerProviderSdkTests.cs b/test/OpenTelemetry.Tests/Logs/LoggerProviderSdkTests.cs index 182490cbc8b..f321551dec1 100644 --- a/test/OpenTelemetry.Tests/Logs/LoggerProviderSdkTests.cs +++ b/test/OpenTelemetry.Tests/Logs/LoggerProviderSdkTests.cs @@ -37,7 +37,9 @@ public void ForceFlushTest() List exportedItems = new(); +#pragma warning disable CA2000 // Dispose objects before losing scope provider.AddProcessor(new BatchLogRecordExportProcessor(new InMemoryExporter(exportedItems))); +#pragma warning restore CA2000 // Dispose objects before losing scope var logger = provider.GetLogger("TestLogger"); @@ -60,7 +62,9 @@ public void ThreadStaticPoolUsedByProviderTests() Assert.Equal(LogRecordThreadStaticPool.Instance, provider1.LogRecordPool); using var provider2 = Sdk.CreateLoggerProviderBuilder() +#pragma warning disable CA2000 // Dispose objects before losing scope .AddProcessor(new SimpleLogRecordExportProcessor(new NoopExporter())) +#pragma warning restore CA2000 // Dispose objects before losing scope .Build() as LoggerProviderSdk; Assert.NotNull(provider2); @@ -68,8 +72,10 @@ public void ThreadStaticPoolUsedByProviderTests() Assert.Equal(LogRecordThreadStaticPool.Instance, provider2.LogRecordPool); using var provider3 = Sdk.CreateLoggerProviderBuilder() +#pragma warning disable CA2000 // Dispose objects before losing scope .AddProcessor(new SimpleLogRecordExportProcessor(new NoopExporter())) .AddProcessor(new SimpleLogRecordExportProcessor(new NoopExporter())) +#pragma warning restore CA2000 // Dispose objects before losing scope .Build() as LoggerProviderSdk; Assert.NotNull(provider3); @@ -81,6 +87,7 @@ public void ThreadStaticPoolUsedByProviderTests() public void SharedPoolUsedByProviderTests() { using var provider1 = Sdk.CreateLoggerProviderBuilder() +#pragma warning disable CA2000 // Dispose objects before losing scope .AddProcessor(new BatchLogRecordExportProcessor(new NoopExporter())) .Build() as LoggerProviderSdk; @@ -99,11 +106,12 @@ public void SharedPoolUsedByProviderTests() using var provider3 = Sdk.CreateLoggerProviderBuilder() .AddProcessor(new SimpleLogRecordExportProcessor(new NoopExporter())) - .AddProcessor(new CompositeProcessor(new BaseProcessor[] - { + .AddProcessor(new CompositeProcessor( + [ new SimpleLogRecordExportProcessor(new NoopExporter()), new BatchLogRecordExportProcessor(new NoopExporter()), - })) +#pragma warning restore CA2000 // Dispose objects before losing scope + ])) .Build() as LoggerProviderSdk; Assert.NotNull(provider3); @@ -120,12 +128,16 @@ public void AddProcessorTest() Assert.NotNull(provider); Assert.Null(provider.Processor); +#pragma warning disable CA2000 // Dispose objects before losing scope provider.AddProcessor(new NoopProcessor()); +#pragma warning restore CA2000 // Dispose objects before losing scope Assert.NotNull(provider.Processor); Assert.True(provider.Processor is NoopProcessor); +#pragma warning disable CA2000 // Dispose objects before losing scope provider.AddProcessor(new NoopProcessor()); +#pragma warning restore CA2000 // Dispose objects before losing scope Assert.NotNull(provider.Processor); Assert.True(provider.Processor is CompositeProcessor); diff --git a/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggerProviderTests.cs b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggerProviderTests.cs index e40d8d8b60a..847e096871e 100644 --- a/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggerProviderTests.cs +++ b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggerProviderTests.cs @@ -50,7 +50,7 @@ public void VerifyOptionsCannotBeChangedAfterInit(bool initialValue) var optionsMonitor = sp.GetRequiredService>(); - var provider = new OpenTelemetryLoggerProvider(optionsMonitor); + using var provider = new OpenTelemetryLoggerProvider(optionsMonitor); // Verify initial set Assert.Equal(initialValue, provider.Options.IncludeFormattedMessage); diff --git a/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs index 4839964c0ee..424c4144d4a 100644 --- a/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs +++ b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using OpenTelemetry.Tests; using Xunit; namespace OpenTelemetry.Logs.Tests; @@ -389,7 +390,9 @@ protected override void Dispose(bool disposing) } } +#pragma warning disable CA1812 // Avoid uninstantiated internal classes private sealed class TestLogProcessorWithILoggerFactoryDependency : BaseProcessor +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { private readonly ILogger logger; @@ -405,7 +408,7 @@ public TestLogProcessorWithILoggerFactoryDependency(ILoggerFactory loggerFactory protected override void Dispose(bool disposing) { - this.logger.LogInformation("Dispose called"); + this.logger.DisposedCalled(); base.Dispose(disposing); } diff --git a/test/OpenTelemetry.Tests/Metrics/AggregatorTests.cs b/test/OpenTelemetry.Tests/Metrics/AggregatorTests.cs index 9c5613c5795..19bf1097f4b 100644 --- a/test/OpenTelemetry.Tests/Metrics/AggregatorTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/AggregatorTests.cs @@ -21,6 +21,8 @@ public AggregatorTests() this.aggregatorStore = new(MetricStreamIdentity, AggregationType.HistogramWithBuckets, AggregationTemporality.Cumulative, 1024); } + public static TheoryData HistogramInfinityBoundariesTestCases => HistogramBoundaryTestCase.HistogramInfinityBoundariesTestCases(); + [Fact] public void HistogramDistributeToAllBucketsDefault() { @@ -285,13 +287,13 @@ internal static void AssertExponentialBucketsAreCorrect(Base2ExponentialBucketHi Assert.Equal(expected, actual); - actual = new List(); + actual = []; foreach (var bucketCount in data.NegativeBuckets) { actual.Add(bucketCount); } - expected = new List(); + expected = []; foreach (var bucketCount in expectedData.NegativeBuckets) { expected.Add(bucketCount); @@ -450,6 +452,40 @@ internal void ExponentialMaxScaleConfigWorks(int? maxScale) Assert.Equal(expectedScale, metricPoint.GetExponentialHistogramData().Scale); } + [Theory] + [MemberData(nameof(HistogramBoundaryTestCase.HistogramInfinityBoundariesTestCases))] + internal void HistogramBucketBoundariesTest(HistogramBoundaryTestCase boundaryTestCase) + { + // Arrange + var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.HistogramWithBuckets, null, boundaryTestCase.InputBoundaries, Metric.DefaultExponentialHistogramMaxBuckets, Metric.DefaultExponentialHistogramMaxScale); + var expectedTotalBuckets = boundaryTestCase.ExpectedBucketCounts.Length; + + // Act + foreach (var value in boundaryTestCase.InputValues) + { + histogramPoint.Update(value); + } + + histogramPoint.TakeSnapshot(true); + + // Assert + var count = histogramPoint.GetHistogramCount(); + Assert.Equal(boundaryTestCase.InputValues.Length, count); + + int bucketIndex = 0; + int actualBucketCount = 0; + + foreach (var histogramBucket in histogramPoint.GetHistogramBuckets()) + { + Assert.Equal(boundaryTestCase.ExpectedBucketCounts[bucketIndex], histogramBucket.BucketCount); + Assert.Equal(boundaryTestCase.ExpectedBucketBounds[bucketIndex], histogramBucket.ExplicitBound); + bucketIndex++; + actualBucketCount++; + } + + Assert.Equal(expectedTotalBuckets, actualBucketCount); + } + private static void HistogramSnapshotThread(object? obj) { var args = obj as ThreadArguments; @@ -493,7 +529,7 @@ private static void HistogramUpdateThread(object? obj) Interlocked.Increment(ref args.ThreadsFinishedAllUpdatesCount); } - private class ThreadArguments + private sealed class ThreadArguments { public readonly ManualResetEvent MreToEnsureAllThreadsStart; public MetricPoint HistogramPoint; diff --git a/test/OpenTelemetry.Tests/Metrics/Base2ExponentialBucketHistogramHelperTests.cs b/test/OpenTelemetry.Tests/Metrics/Base2ExponentialBucketHistogramHelperTests.cs index 0af08fffc11..fae1f502e08 100644 --- a/test/OpenTelemetry.Tests/Metrics/Base2ExponentialBucketHistogramHelperTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/Base2ExponentialBucketHistogramHelperTests.cs @@ -16,20 +16,26 @@ public Base2ExponentialBucketHistogramHelperTests(ITestOutputHelper output) this.output = output; } - public static IEnumerable GetNonPositiveScales() + public static TheoryData GetNonPositiveScales() { + TheoryData theoryData = []; for (var i = -11; i <= 0; ++i) { - yield return new object[] { i }; + theoryData.Add(i); } + + return theoryData; } - public static IEnumerable GetPositiveScales() + public static TheoryData GetPositiveScales() { + TheoryData theoryData = []; for (var i = 1; i <= 20; ++i) { - yield return new object[] { i }; + theoryData.Add(i); } + + return theoryData; } [Theory] diff --git a/test/OpenTelemetry.Tests/Metrics/Base2ExponentialBucketHistogramTest.cs b/test/OpenTelemetry.Tests/Metrics/Base2ExponentialBucketHistogramTests.cs similarity index 99% rename from test/OpenTelemetry.Tests/Metrics/Base2ExponentialBucketHistogramTest.cs rename to test/OpenTelemetry.Tests/Metrics/Base2ExponentialBucketHistogramTests.cs index a40beac0aa2..36c8bb2e439 100644 --- a/test/OpenTelemetry.Tests/Metrics/Base2ExponentialBucketHistogramTest.cs +++ b/test/OpenTelemetry.Tests/Metrics/Base2ExponentialBucketHistogramTests.cs @@ -7,11 +7,11 @@ namespace OpenTelemetry.Metrics.Tests; -public class Base2ExponentialBucketHistogramTest +public class Base2ExponentialBucketHistogramTests { private readonly ITestOutputHelper output; - public Base2ExponentialBucketHistogramTest(ITestOutputHelper output) + public Base2ExponentialBucketHistogramTests(ITestOutputHelper output) { this.output = output; } diff --git a/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs b/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTests.cs similarity index 99% rename from test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs rename to test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTests.cs index 7666ab1a5df..83dfcf3dc83 100644 --- a/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs +++ b/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTests.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Metrics.Tests; -public class CircularBufferBucketsTest +public class CircularBufferBucketsTests { [Fact] public void ConstructorThrowsOnInvalidCapacity() diff --git a/test/OpenTelemetry.Tests/Metrics/HistogramBoundaryTestCase.cs b/test/OpenTelemetry.Tests/Metrics/HistogramBoundaryTestCase.cs new file mode 100644 index 00000000000..fb8b5affd13 --- /dev/null +++ b/test/OpenTelemetry.Tests/Metrics/HistogramBoundaryTestCase.cs @@ -0,0 +1,70 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Xunit; + +namespace OpenTelemetry.Metrics.Tests; + +#pragma warning disable CA1819 // Properties should not return arrays +#pragma warning disable CA1515 // Consider making public types internal +public class HistogramBoundaryTestCase( +#pragma warning restore CA1515 // Consider making public types internal + string testName, + double[] inputBoundaries, + double[] inputValues, + long[] expectedBucketCounts, + double[] expectedBucketBounds) +{ + public double[] InputBoundaries { get; } = inputBoundaries; + + public double[] InputValues { get; } = inputValues; + + public long[] ExpectedBucketCounts { get; } = expectedBucketCounts; + + public double[] ExpectedBucketBounds { get; } = expectedBucketBounds; + + public string TestName { get; set; } = testName; + + public static TheoryData HistogramInfinityBoundariesTestCases() + { + var data = new TheoryData + { + new( + testName: "Custom boundaries with no infinity in explicit boundaries", + inputBoundaries: [0, 10], + inputValues: [-10, 0, 5, 10, 100], + expectedBucketCounts: [2, 2, 1], + expectedBucketBounds: [0, 10, double.PositiveInfinity]), + + new( + testName: "Custom boundaries with positive infinity", + inputBoundaries: [0, double.PositiveInfinity], + inputValues: [-10, 0, 10, 100], + expectedBucketCounts: [2, 2], + expectedBucketBounds: [0, double.PositiveInfinity]), + + new( + testName: "Custom boundaries with negative infinity", + inputBoundaries: [double.NegativeInfinity, 0, 10], + inputValues: [-100, -10, 0, 5, 10, 100], + expectedBucketCounts: [3, 2, 1], + expectedBucketBounds: [0, 10, double.PositiveInfinity]), + + new( + testName: "Custom boundaries with both infinities", + inputBoundaries: [double.NegativeInfinity, 0, 10, double.PositiveInfinity], + inputValues: [-100, -10, 0, 5, 10, 100], + expectedBucketCounts: [3, 2, 1], + expectedBucketBounds: [0, 10, double.PositiveInfinity]), + + new( + testName: "Custom boundaries with infinities only", + inputBoundaries: [double.NegativeInfinity, double.PositiveInfinity], + inputValues: [-10, 0, 10], + expectedBucketCounts: [3], + expectedBucketBounds: [double.PositiveInfinity]), + }; + + return data; + } +} diff --git a/test/OpenTelemetry.Tests/Metrics/HostingMeterProviderBuilder.cs b/test/OpenTelemetry.Tests/Metrics/HostingMeterProviderBuilder.cs new file mode 100644 index 00000000000..de0cd7aaa5b --- /dev/null +++ b/test/OpenTelemetry.Tests/Metrics/HostingMeterProviderBuilder.cs @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#if BUILDING_HOSTING_TESTS + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.Metrics; + +namespace OpenTelemetry.Metrics.Tests; + +#pragma warning disable CA1515 // Consider making public types internal +public sealed class HostingMeterProviderBuilder : MeterProviderBuilderBase +#pragma warning restore CA1515 // Consider making public types internal +{ + public HostingMeterProviderBuilder(IServiceCollection services) + : base(services) + { + } + + public override MeterProviderBuilder AddMeter(params string[] names) + { + return this.ConfigureServices(services => + { + foreach (var name in names) + { + // Note: The entire purpose of this class is to use the + // IMetricsBuilder API to enable Metrics and NOT the + // traditional AddMeter API. + services.AddMetrics(builder => builder.EnableMetrics(name)); + } + }); + } + + public MeterProviderBuilder AddSdkMeter(params string[] names) + { + return base.AddMeter(names); + } +} +#endif diff --git a/test/OpenTelemetry.Tests/Metrics/KnownHistogramBuckets.cs b/test/OpenTelemetry.Tests/Metrics/KnownHistogramBuckets.cs index b63b4c2651b..27436e00eef 100644 --- a/test/OpenTelemetry.Tests/Metrics/KnownHistogramBuckets.cs +++ b/test/OpenTelemetry.Tests/Metrics/KnownHistogramBuckets.cs @@ -3,7 +3,9 @@ namespace OpenTelemetry.Metrics.Tests; +#pragma warning disable CA1515 // Consider making public types internal public enum KnownHistogramBuckets +#pragma warning restore CA1515 // Consider making public types internal { /// /// Default OpenTelemetry semantic convention buckets. diff --git a/test/OpenTelemetry.Tests/Metrics/MeterProviderBuilderExtensionsTests.cs b/test/OpenTelemetry.Tests/Metrics/MeterProviderBuilderExtensionsTests.cs index 6b2a13f1c00..fa87ebe5da8 100644 --- a/test/OpenTelemetry.Tests/Metrics/MeterProviderBuilderExtensionsTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MeterProviderBuilderExtensionsTests.cs @@ -78,7 +78,9 @@ public void AddInstrumentationTest() using (var provider = Sdk.CreateMeterProviderBuilder() .AddInstrumentation() .AddInstrumentation((sp, provider) => new MyInstrumentation() { Provider = provider }) +#pragma warning disable CA2000 // Dispose objects before losing scope .AddInstrumentation(new MyInstrumentation()) +#pragma warning restore CA2000 // Dispose objects before losing scope .AddInstrumentation(() => (object?)null) .Build() as MeterProviderSdk) { @@ -95,7 +97,7 @@ public void AddInstrumentationTest() Assert.Null(((MyInstrumentation)provider.Instrumentations[2]).Provider); Assert.False(((MyInstrumentation)provider.Instrumentations[2]).Disposed); - instrumentation = new List(provider.Instrumentations); + instrumentation = [.. provider.Instrumentations]; } Assert.NotNull(instrumentation); @@ -373,14 +375,6 @@ private sealed class MyReader : MetricReader { } - private sealed class MyExporter : BaseExporter - { - public override ExportResult Export(in Batch batch) - { - return ExportResult.Success; - } - } - private sealed class MyMeterProviderBuilder : MeterProviderBuilder { public override MeterProviderBuilder AddInstrumentation(Func instrumentationFactory) diff --git a/test/OpenTelemetry.Tests/Metrics/MeterProviderSdkTest.cs b/test/OpenTelemetry.Tests/Metrics/MeterProviderSdkTests.cs similarity index 98% rename from test/OpenTelemetry.Tests/Metrics/MeterProviderSdkTest.cs rename to test/OpenTelemetry.Tests/Metrics/MeterProviderSdkTests.cs index 83d5d4644cf..8f51b19d3a2 100644 --- a/test/OpenTelemetry.Tests/Metrics/MeterProviderSdkTest.cs +++ b/test/OpenTelemetry.Tests/Metrics/MeterProviderSdkTests.cs @@ -8,7 +8,7 @@ namespace OpenTelemetry.Metrics.Tests; -public class MeterProviderSdkTest +public class MeterProviderSdkTests { [Fact] public void BuilderTypeDoesNotChangeTest() diff --git a/test/OpenTelemetry.Tests/Metrics/MeterProviderTests.cs b/test/OpenTelemetry.Tests/Metrics/MeterProviderTests.cs index df0cfec4b04..6cf5fa3c8a1 100644 --- a/test/OpenTelemetry.Tests/Metrics/MeterProviderTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MeterProviderTests.cs @@ -16,11 +16,15 @@ public void MeterProviderFindExporterTest() .AddInMemoryExporter(exportedItems) .Build(); +#pragma warning disable CA2000 // Dispose objects before losing scope Assert.True(meterProvider.TryFindExporter(out InMemoryExporter? inMemoryExporter)); Assert.False(meterProvider.TryFindExporter(out MyExporter? myExporter)); +#pragma warning restore CA2000 // Dispose objects before losing scope } - private class MyExporter : BaseExporter +#pragma warning disable CA1812 // Avoid uninstantiated internal classes + private sealed class MyExporter : BaseExporter +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { public override ExportResult Export(in Batch batch) { diff --git a/test/OpenTelemetry.Tests/Metrics/MetricApiTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricApiTests.cs index e2dcb89d3d2..1929e41ad25 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricApiTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricApiTests.cs @@ -14,10 +14,10 @@ namespace OpenTelemetry.Metrics.Tests; public class MetricApiTests : MetricTestsBase { private const int MaxTimeToAllowForFlush = 10000; + private const long DeltaLongValueUpdatedByEachCall = 10; + private const double DeltaDoubleValueUpdatedByEachCall = 11.987; + private const int NumberOfMetricUpdateByEachThread = 100000; private static readonly int NumberOfThreads = Environment.ProcessorCount; - private static readonly long DeltaLongValueUpdatedByEachCall = 10; - private static readonly double DeltaDoubleValueUpdatedByEachCall = 11.987; - private static readonly int NumberOfMetricUpdateByEachThread = 100000; private readonly ITestOutputHelper output; public MetricApiTests(ITestOutputHelper output) @@ -31,7 +31,7 @@ public void MeasurementWithNullValuedTag() using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); @@ -42,7 +42,7 @@ public void MeasurementWithNullValuedTag() Assert.Single(exportedItems); var metric = exportedItems[0]; Assert.Equal("myCounter", metric.Name); - List metricPoints = new List(); + List metricPoints = []; foreach (ref readonly var mp in metric.GetMetricPoints()) { metricPoints.Add(mp); @@ -64,7 +64,7 @@ public void ObserverCallbackTest() using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); @@ -75,7 +75,7 @@ public void ObserverCallbackTest() Assert.Single(exportedItems); var metric = exportedItems[0]; Assert.Equal("myGauge", metric.Name); - List metricPoints = new List(); + List metricPoints = []; foreach (ref readonly var mp in metric.GetMetricPoints()) { metricPoints.Add(mp); @@ -93,19 +93,19 @@ public void ObserverCallbackExceptionTest() using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); var measurement = new Measurement(100, new("name", "apple"), new("color", "red")); meter.CreateObservableGauge("myGauge", () => measurement); - meter.CreateObservableGauge("myBadGauge", observeValues: () => throw new Exception("gauge read error")); + meter.CreateObservableGauge("myBadGauge", observeValues: () => throw new InvalidOperationException("gauge read error")); meterProvider.ForceFlush(MaxTimeToAllowForFlush); Assert.Single(exportedItems); var metric = exportedItems[0]; Assert.Equal("myGauge", metric.Name); - List metricPoints = new List(); + List metricPoints = []; foreach (ref readonly var mp in metric.GetMetricPoints()) { metricPoints.Add(mp); @@ -125,9 +125,9 @@ public void MetricUnitIsExportedCorrectly(string? unit) { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); @@ -147,9 +147,9 @@ public void MetricDescriptionIsExportedCorrectly(string? description) { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); @@ -173,8 +173,8 @@ public void MetricInstrumentationScopeIsExportedCorrectly() "MeterTagKey", "MeterTagValue"), }; - using var meter = new Meter($"{meterName}", meterVersion, meterTags); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var meter = new Meter(meterName, meterVersion, meterTags); + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); @@ -188,7 +188,7 @@ public void MetricInstrumentationScopeIsExportedCorrectly() Assert.NotNull(metric.MeterTags); - Assert.Single(metric.MeterTags.Where(kvp => kvp.Key == meterTags[0].Key && kvp.Value == meterTags[0].Value)); + Assert.Single(metric.MeterTags, kvp => kvp.Key == meterTags[0].Key && kvp.Value == meterTags[0].Value); } [Fact] @@ -213,7 +213,7 @@ public void MetricInstrumentationScopeAttributesAreTreatedAsIdentifyingProperty( }; using var meter1 = new Meter(meterName, meterVersion, meterTags1); using var meter2 = new Meter(meterName, meterVersion, meterTags2); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meterName) .AddInMemoryExporter(exportedItems)); @@ -227,14 +227,14 @@ public void MetricInstrumentationScopeAttributesAreTreatedAsIdentifyingProperty( bool TagComparator(KeyValuePair lhs, KeyValuePair rhs) { - return lhs.Key.Equals(rhs.Key) && lhs.Value!.GetHashCode().Equals(rhs.Value!.GetHashCode()); + return lhs.Key.Equals(rhs.Key, StringComparison.Ordinal) && lhs.Value!.GetHashCode().Equals(rhs.Value!.GetHashCode()); } var metric = exportedItems.First(m => TagComparator(m.MeterTags!.First(), meterTags1!.First())); Assert.Equal(meterName, metric.MeterName); Assert.Equal(meterVersion, metric.MeterVersion); - List metricPoints = new List(); + List metricPoints = []; foreach (ref readonly var mp in metric.GetMetricPoints()) { metricPoints.Add(mp); @@ -248,7 +248,7 @@ bool TagComparator(KeyValuePair lhs, KeyValuePair(); + metricPoints = []; foreach (ref readonly var mp in metric.GetMetricPoints()) { metricPoints.Add(mp); @@ -264,9 +264,9 @@ public void DuplicateInstrumentRegistration_NoViews_IdenticalInstruments() { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); @@ -281,7 +281,7 @@ public void DuplicateInstrumentRegistration_NoViews_IdenticalInstruments() var metric = exportedItems[0]; Assert.Equal("instrumentName", metric.Name); - List metricPoints = new List(); + List metricPoints = []; foreach (ref readonly var mp in metric.GetMetricPoints()) { metricPoints.Add(mp); @@ -297,9 +297,9 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); @@ -317,7 +317,7 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe Assert.Equal("instrumentDescription1", metric1.Description); Assert.Equal("instrumentDescription2", metric2.Description); - List metric1MetricPoints = new List(); + List metric1MetricPoints = []; foreach (ref readonly var mp in metric1.GetMetricPoints()) { metric1MetricPoints.Add(mp); @@ -327,7 +327,7 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe var metricPoint1 = metric1MetricPoints[0]; Assert.Equal(10, metricPoint1.GetSumLong()); - List metric2MetricPoints = new List(); + List metric2MetricPoints = []; foreach (ref readonly var mp in metric2.GetMetricPoints()) { metric2MetricPoints.Add(mp); @@ -343,9 +343,9 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); @@ -363,7 +363,7 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe Assert.Equal("instrumentUnit1", metric1.Unit); Assert.Equal("instrumentUnit2", metric2.Unit); - List metric1MetricPoints = new List(); + List metric1MetricPoints = []; foreach (ref readonly var mp in metric1.GetMetricPoints()) { metric1MetricPoints.Add(mp); @@ -373,7 +373,7 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe var metricPoint1 = metric1MetricPoints[0]; Assert.Equal(10, metricPoint1.GetSumLong()); - List metric2MetricPoints = new List(); + List metric2MetricPoints = []; foreach (ref readonly var mp in metric2.GetMetricPoints()) { metric2MetricPoints.Add(mp); @@ -389,9 +389,9 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); @@ -407,7 +407,7 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe var metric1 = exportedItems[0]; var metric2 = exportedItems[1]; - List metric1MetricPoints = new List(); + List metric1MetricPoints = []; foreach (ref readonly var mp in metric1.GetMetricPoints()) { metric1MetricPoints.Add(mp); @@ -417,7 +417,7 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe var metricPoint1 = metric1MetricPoints[0]; Assert.Equal(10, metricPoint1.GetSumLong()); - List metric2MetricPoints = new List(); + List metric2MetricPoints = []; foreach (ref readonly var mp in metric2.GetMetricPoints()) { metric2MetricPoints.Add(mp); @@ -433,9 +433,9 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); @@ -451,7 +451,7 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe var metric1 = exportedItems[0]; var metric2 = exportedItems[1]; - List metric1MetricPoints = new List(); + List metric1MetricPoints = []; foreach (ref readonly var mp in metric1.GetMetricPoints()) { metric1MetricPoints.Add(mp); @@ -461,7 +461,7 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe var metricPoint1 = metric1MetricPoints[0]; Assert.Equal(10, metricPoint1.GetSumLong()); - List metric2MetricPoints = new List(); + List metric2MetricPoints = []; foreach (ref readonly var mp in metric2.GetMetricPoints()) { metric2MetricPoints.Add(mp); @@ -478,10 +478,10 @@ public void DuplicateInstrumentNamesFromDifferentMetersWithSameNameDifferentVers { var exportedItems = new List(); - using var meter1 = new Meter($"{Utils.GetCurrentMethodName()}", "1.0"); - using var meter2 = new Meter($"{Utils.GetCurrentMethodName()}", "2.0"); + using var meter1 = new Meter(Utils.GetCurrentMethodName(), "1.0"); + using var meter2 = new Meter(Utils.GetCurrentMethodName(), "2.0"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) .AddMeter(meter2.Name) .AddInMemoryExporter(exportedItems)); @@ -513,7 +513,7 @@ public void DuplicateInstrumentNamesFromDifferentMetersAreAllowed(MetricReaderTe using var meter1 = new Meter($"{Utils.GetCurrentMethodName()}.1.{temporality}"); using var meter2 = new Meter($"{Utils.GetCurrentMethodName()}.2.{temporality}"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => + using var container = BuildMeterProvider(out var meterProvider, builder => { builder .AddMeter(meter1.Name) @@ -560,7 +560,7 @@ public void MeterSourcesWildcardSupportMatchTest(bool hasView) var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => + using var container = BuildMeterProvider(out var meterProvider, builder => { builder .AddMeter("AbcCompany.XyzProduct.Component?") @@ -584,7 +584,7 @@ public void MeterSourcesWildcardSupportMatchTest(bool hasView) meterProvider.ForceFlush(MaxTimeToAllowForFlush); - Assert.True(exportedItems.Count == 5); // "SomeCompany.SomeProduct.SomeComponent" will not be subscribed. + Assert.Equal(5, exportedItems.Count); // "SomeCompany.SomeProduct.SomeComponent" will not be subscribed. if (hasView) { @@ -612,7 +612,7 @@ public void MeterSourcesWildcardSupportNegativeTestNoMeterAdded(bool hasView) var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => + using var container = BuildMeterProvider(out var meterProvider, builder => { builder .AddInMemoryExporter(exportedItems); @@ -629,7 +629,7 @@ public void MeterSourcesWildcardSupportNegativeTestNoMeterAdded(bool hasView) meter2.CreateObservableGauge("myGauge2", () => measurement); meterProvider.ForceFlush(MaxTimeToAllowForFlush); - Assert.True(exportedItems.Count == 0); + Assert.Empty(exportedItems); } [Theory] @@ -644,7 +644,7 @@ public void CounterAggregationTest(bool exportDelta) using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{exportDelta}"); var counterLong = meter.CreateCounter("mycounter"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems, metricReaderOptions => { @@ -741,11 +741,11 @@ public void ObservableCounterAggregationTest(bool exportDelta) { return new List>() { - new Measurement(i++ * 10), + new(i++ * 10), }; }); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems, metricReaderOptions => { @@ -810,15 +810,15 @@ public void ObservableCounterWithTagsAggregationTest(bool exportDelta) "observable-counter", () => { - return new List>() + return new List> { - new Measurement(10, tags1), - new Measurement(10, tags2), - new Measurement(10, tags3), + new(10L, tags1), + new(10L, tags2), + new(10L, tags3), }; }); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems, metricReaderOptions => { @@ -830,7 +830,7 @@ public void ObservableCounterWithTagsAggregationTest(bool exportDelta) Assert.Single(exportedItems); var metric = exportedItems[0]; Assert.Equal("observable-counter", metric.Name); - List metricPoints = new List(); + List metricPoints = []; foreach (ref readonly var mp in metric.GetMetricPoints()) { metricPoints.Add(mp); @@ -906,27 +906,27 @@ public void ObservableCounterSpatialAggregationTest(bool exportDelta) "requestCount", () => { - return new List>() + return new List> { - new Measurement(10, tags1), - new Measurement(10, tags2), - new Measurement(10, tags3), + new(10L, tags1), + new(10L, tags2), + new(10L, tags3), }; }); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems, metricReaderOptions => { metricReaderOptions.TemporalityPreference = exportDelta ? MetricReaderTemporalityPreference.Delta : MetricReaderTemporalityPreference.Cumulative; }) - .AddView("requestCount", new MetricStreamConfiguration() { TagKeys = Array.Empty() })); + .AddView("requestCount", new MetricStreamConfiguration() { TagKeys = [] })); meterProvider.ForceFlush(MaxTimeToAllowForFlush); Assert.Single(exportedItems); var metric = exportedItems[0]; Assert.Equal("requestCount", metric.Name); - List metricPoints = new List(); + List metricPoints = []; foreach (ref readonly var mp in metric.GetMetricPoints()) { metricPoints.Add(mp); @@ -955,7 +955,7 @@ public void UpDownCounterAggregationTest(bool exportDelta) using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{exportDelta}"); var counterLong = meter.CreateUpDownCounter("mycounter"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems, metricReaderOptions => { @@ -1030,13 +1030,13 @@ public void ObservableUpDownCounterAggregationTest(bool exportDelta) "observable-counter", () => { - return new List>() + return new List> { - new Measurement(i++ * 10), + new(i++ * 10L), }; }); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems, metricReaderOptions => { @@ -1091,15 +1091,15 @@ public void ObservableUpDownCounterWithTagsAggregationTest(bool exportDelta) "observable-counter", () => { - return new List>() + return new List> { - new Measurement(10, tags1), - new Measurement(10, tags2), - new Measurement(10, tags3), + new(10L, tags1), + new(10L, tags2), + new(10L, tags3), }; }); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems, metricReaderOptions => { @@ -1111,7 +1111,7 @@ public void ObservableUpDownCounterWithTagsAggregationTest(bool exportDelta) Assert.Single(exportedItems); var metric = exportedItems[0]; Assert.Equal("observable-counter", metric.Name); - List metricPoints = new List(); + List metricPoints = []; foreach (ref readonly var mp in metric.GetMetricPoints()) { metricPoints.Add(mp); @@ -1169,7 +1169,7 @@ public void DimensionsAreOrderInsensitiveWithSortedKeysFirst(bool exportDelta) using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{exportDelta}"); var counterLong = meter.CreateCounter("Counter"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems, metricReaderOptions => { @@ -1260,7 +1260,7 @@ public void DimensionsAreOrderInsensitiveWithUnsortedKeysFirst(bool exportDelta) using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{exportDelta}"); var counterLong = meter.CreateCounter("Counter"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems, metricReaderOptions => { @@ -1353,7 +1353,7 @@ public void TestInstrumentDisposal(MetricReaderTemporalityPreference temporality var counter1 = meter1.CreateCounter("counterFromMeter1"); var counter2 = meter2.CreateCounter("counterFromMeter2"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) .AddMeter(meter2.Name) .AddInMemoryExporter(exportedItems, metricReaderOptions => @@ -1418,7 +1418,11 @@ int MetricPointCount() // Validate second element is overflow attribute. var tagEnumerator = enumerator.Current.Tags.GetEnumerator(); tagEnumerator.MoveNext(); +#if NET + if (!tagEnumerator.Current.Key.Contains("otel.metric.overflow", StringComparison.Ordinal)) +#else if (!tagEnumerator.Current.Key.Contains("otel.metric.overflow")) +#endif { count++; } @@ -1435,7 +1439,7 @@ int MetricPointCount() using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{temporality}"); var counterLong = meter.CreateCounter("mycounterCapTest"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems, metricReaderOptions => { @@ -1530,7 +1534,7 @@ public void InstrumentWithInvalidNameIsIgnoredTest(string instrumentName) using var meter = new Meter("InstrumentWithInvalidNameIsIgnoredTest"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); @@ -1551,7 +1555,7 @@ public void InstrumentWithValidNameIsExportedTest(string name) using var meter = new Meter("InstrumentValidNameIsExportedTest"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); @@ -1573,7 +1577,7 @@ public void SetupSdkProviderWithNoReader(bool hasViews) // This test ensures that MeterProviderSdk can be set up without any reader using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{hasViews}"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => + using var container = BuildMeterProvider(out var meterProvider, builder => { builder .AddMeter(meter.Name); @@ -1592,10 +1596,10 @@ public void SetupSdkProviderWithNoReader(bool hasViews) [Fact] public void UnsupportedMetricInstrument() { - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); @@ -1627,9 +1631,9 @@ public void GaugeIsExportedCorrectly() { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); @@ -1639,7 +1643,7 @@ public void GaugeIsExportedCorrectly() Assert.Single(exportedItems); var metric = exportedItems[0]; Assert.Equal("Background Noise Level", metric.Description); - List metricPoints = new List(); + List metricPoints = []; foreach (ref readonly var mp in metric.GetMetricPoints()) { metricPoints.Add(mp); @@ -1656,8 +1660,8 @@ public void GaugeHandlesNoNewMeasurementsCorrectlyWithTemporality(MetricReaderTe { var exportedMetrics = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedMetrics, metricReaderOptions => { @@ -1771,7 +1775,7 @@ private void MultithreadedCounterTest(T deltaValueUpdatedByEachCall) using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{typeof(T).Name}.{deltaValueUpdatedByEachCall}"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(metricItems)); @@ -1817,11 +1821,13 @@ private void MultithreadedHistogramTest(long[] expected, T[] values) var bucketCounts = new long[11]; var metrics = new List(); +#pragma warning disable CA2000 // Dispose objects before losing scope var metricReader = new BaseExportingMetricReader(new InMemoryExporter(metrics)); +#pragma warning restore CA2000 // Dispose objects before losing scope using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{typeof(T).Name}"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddReader(metricReader)); @@ -1858,7 +1864,7 @@ private void MultithreadedHistogramTest(long[] expected, T[] values) Assert.Equal(expected, bucketCounts); } - private class UpdateThreadArguments + private sealed class UpdateThreadArguments where T : struct, IComparable { public ManualResetEvent MreToBlockUpdateThread; diff --git a/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs index 7ed01e46a72..e46b3e5dc18 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs @@ -36,7 +36,7 @@ public void TestExemplarFilterSetFromConfiguration( }); } - using var container = this.BuildMeterProvider(out var meterProvider, b => + using var container = BuildMeterProvider(out var meterProvider, b => { b.ConfigureServices( s => s.AddSingleton(configBuilder.Build())); @@ -69,16 +69,16 @@ public void TestExemplarsCounter(MetricReaderTemporalityPreference temporality) DateTime testStartTime = DateTime.UtcNow; var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); var counterDouble = meter.CreateCounter("testCounterDouble"); var counterLong = meter.CreateCounter("testCounterLong"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .SetExemplarFilter(ExemplarFilterType.AlwaysOn) .AddView(i => { - if (i.Name.StartsWith("testCounter")) + if (i.Name.StartsWith("testCounter", StringComparison.Ordinal)) { return new MetricStreamConfiguration { @@ -114,7 +114,8 @@ public void TestExemplarsCounter(MetricReaderTemporalityPreference temporality) var secondMeasurementValues = GenerateRandomValues(1, true, measurementValues); foreach (var value in secondMeasurementValues) { - using var act = new Activity("test").Start(); + using var activity = new Activity("test"); + activity.Start(); counterDouble.Add(value.Value); counterLong.Add((long)value.Value); } @@ -184,21 +185,21 @@ public void TestExemplarsObservable(MetricReaderTemporalityPreference temporalit DateTime testStartTime = DateTime.UtcNow; var exportedItems = new List(); - (double Value, bool ExpectTraceId)[] measurementValues = new (double Value, bool ExpectTraceId)[] - { + (double Value, bool ExpectTraceId)[] measurementValues = + [ (18D, false), - (19D, false), - }; + (19D, false) + ]; int measurementIndex = 0; - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); var gaugeDouble = meter.CreateObservableGauge("testGaugeDouble", () => measurementValues[measurementIndex].Value); var gaugeLong = meter.CreateObservableGauge("testGaugeLong", () => (long)measurementValues[measurementIndex].Value); var counterDouble = meter.CreateObservableCounter("counterDouble", () => measurementValues[measurementIndex].Value); var counterLong = meter.CreateObservableCounter("counterLong", () => (long)measurementValues[measurementIndex].Value); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .SetExemplarFilter(ExemplarFilterType.AlwaysOn) .AddInMemoryExporter(exportedItems, metricReaderOptions => @@ -275,7 +276,7 @@ public void TestExemplarsHistogramWithBuckets(MetricReaderTemporalityPreference DateTime testStartTime = DateTime.UtcNow; var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); var histogramWithBucketsAndMinMaxDouble = meter.CreateHistogram("histogramWithBucketsAndMinMaxDouble"); var histogramWithBucketsDouble = meter.CreateHistogram("histogramWithBucketsDouble"); var histogramWithBucketsAndMinMaxLong = meter.CreateHistogram("histogramWithBucketsAndMinMaxLong"); @@ -293,7 +294,7 @@ public void TestExemplarsHistogramWithBuckets(MetricReaderTemporalityPreference }); } - using var container = this.BuildMeterProvider(out var meterProvider, builder => + using var container = BuildMeterProvider(out var meterProvider, builder => { if (string.IsNullOrEmpty(configValue)) { @@ -305,7 +306,7 @@ public void TestExemplarsHistogramWithBuckets(MetricReaderTemporalityPreference .AddMeter(meter.Name) .AddView(i => { - if (i.Name.StartsWith("histogramWithBucketsAndMinMax")) + if (i.Name.StartsWith("histogramWithBucketsAndMinMax", StringComparison.Ordinal)) { return new ExplicitBucketHistogramConfiguration { @@ -329,7 +330,7 @@ public void TestExemplarsHistogramWithBuckets(MetricReaderTemporalityPreference var measurementValues = buckets /* 2000 is here to test overflow measurement */ - .Concat(new double[] { 2000 }) + .Concat([2000.0]) .Select(b => (Value: b, ExpectTraceId: false)) .ToArray(); foreach (var value in measurementValues) @@ -356,7 +357,8 @@ public void TestExemplarsHistogramWithBuckets(MetricReaderTemporalityPreference var secondMeasurementValues = buckets.Take(1).Select(b => (Value: b, ExpectTraceId: true)).ToArray(); foreach (var value in secondMeasurementValues) { - using var act = new Activity("test").Start(); + using var activity = new Activity("test"); + activity.Start(); histogramWithBucketsAndMinMaxDouble.Record(value.Value); histogramWithBucketsDouble.Record(value.Value); histogramWithBucketsAndMinMaxLong.Record((long)value.Value); @@ -425,22 +427,22 @@ public void TestExemplarsHistogramWithoutBuckets(MetricReaderTemporalityPreferen DateTime testStartTime = DateTime.UtcNow; var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); var histogramWithoutBucketsAndMinMaxDouble = meter.CreateHistogram("histogramWithoutBucketsAndMinMaxDouble"); var histogramWithoutBucketsDouble = meter.CreateHistogram("histogramWithoutBucketsDouble"); var histogramWithoutBucketsAndMinMaxLong = meter.CreateHistogram("histogramWithoutBucketsAndMinMaxLong"); var histogramWithoutBucketsLong = meter.CreateHistogram("histogramWithoutBucketsLong"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .SetExemplarFilter(ExemplarFilterType.AlwaysOn) .AddView(i => { - if (i.Name.StartsWith("histogramWithoutBucketsAndMinMax")) + if (i.Name.StartsWith("histogramWithoutBucketsAndMinMax", StringComparison.Ordinal)) { return new ExplicitBucketHistogramConfiguration { - Boundaries = Array.Empty(), + Boundaries = [], ExemplarReservoirFactory = () => new SimpleFixedSizeExemplarReservoir(3), }; } @@ -448,7 +450,7 @@ public void TestExemplarsHistogramWithoutBuckets(MetricReaderTemporalityPreferen { return new ExplicitBucketHistogramConfiguration { - Boundaries = Array.Empty(), + Boundaries = [], RecordMinMax = false, ExemplarReservoirFactory = () => new SimpleFixedSizeExemplarReservoir(3), }; @@ -484,7 +486,8 @@ public void TestExemplarsHistogramWithoutBuckets(MetricReaderTemporalityPreferen var secondMeasurementValues = GenerateRandomValues(1, true, measurementValues); foreach (var value in secondMeasurementValues) { - using var act = new Activity("test").Start(); + using var activity = new Activity("test"); + activity.Start(); histogramWithoutBucketsAndMinMaxDouble.Record(value.Value); histogramWithoutBucketsDouble.Record(value.Value); histogramWithoutBucketsAndMinMaxLong.Record((long)value.Value); @@ -553,18 +556,18 @@ public void TestExemplarsExponentialHistogram(MetricReaderTemporalityPreference DateTime testStartTime = DateTime.UtcNow; var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); var exponentialHistogramWithMinMaxDouble = meter.CreateHistogram("exponentialHistogramWithMinMaxDouble"); var exponentialHistogramDouble = meter.CreateHistogram("exponentialHistogramDouble"); var exponentialHistogramWithMinMaxLong = meter.CreateHistogram("exponentialHistogramWithMinMaxLong"); var exponentialHistogramLong = meter.CreateHistogram("exponentialHistogramLong"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .SetExemplarFilter(ExemplarFilterType.AlwaysOn) .AddView(i => { - if (i.Name.StartsWith("exponentialHistogramWithMinMax")) + if (i.Name.StartsWith("exponentialHistogramWithMinMax", StringComparison.Ordinal)) { return new Base2ExponentialBucketHistogramConfiguration(); } @@ -606,7 +609,8 @@ public void TestExemplarsExponentialHistogram(MetricReaderTemporalityPreference var secondMeasurementValues = GenerateRandomValues(1, true, measurementValues); foreach (var value in secondMeasurementValues) { - using var act = new Activity("test").Start(); + using var activity = new Activity("test"); + activity.Start(); exponentialHistogramWithMinMaxDouble.Record(value.Value); exponentialHistogramDouble.Record(value.Value); exponentialHistogramWithMinMaxLong.Record((long)value.Value); @@ -674,19 +678,20 @@ public void TestTraceBasedExemplarFilter(bool enableTracing) { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); var counter = meter.CreateCounter("testCounter"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .SetExemplarFilter(ExemplarFilterType.TraceBased) .AddInMemoryExporter(exportedItems)); if (enableTracing) { - using var act = new Activity("test").Start(); - act.ActivityTraceFlags = ActivityTraceFlags.Recorded; + using var activity = new Activity("test"); + activity.Start(); + activity.ActivityTraceFlags = ActivityTraceFlags.Recorded; counter.Add(18); } else @@ -721,20 +726,20 @@ public void TestExemplarsFilterTags(bool enableTagFiltering) { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); var histogram = meter.CreateHistogram("testHistogram"); TestExemplarReservoir? testExemplarReservoir = null; - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .SetExemplarFilter(ExemplarFilterType.AlwaysOn) .AddView( histogram.Name, new MetricStreamConfiguration() { - TagKeys = enableTagFiltering ? new string[] { "key1" } : null, + TagKeys = enableTagFiltering ? ["key1"] : null, ExemplarReservoirFactory = () => { if (testExemplarReservoir != null) @@ -799,7 +804,9 @@ private static (double Value, bool ExpectTraceId)[] GenerateRandomValues( var values = new (double, bool)[count]; for (int i = 0; i < count; i++) { +#pragma warning disable CA5394 // Do not use insecure randomness var nextValue = random.NextDouble() * 100_000; +#pragma warning restore CA5394 // Do not use insecure randomness if (values.Any(m => m.Item1 == nextValue || m.Item1 == (long)nextValue) || previousValues?.Any(m => m.Value == nextValue || m.Value == (long)nextValue) == true) { @@ -822,13 +829,15 @@ private static void ValidateExemplars( { int count = 0; + var measurements = measurementValues.ToArray(); + foreach (var exemplar in exemplars) { Assert.True(exemplar.Timestamp >= startTime && exemplar.Timestamp <= endTime, $"{startTime} < {exemplar.Timestamp} < {endTime}"); Assert.Equal(0, exemplar.FilteredTags.MaximumCount); - var measurement = measurementValues.FirstOrDefault(v => v.Value == getExemplarValueFunc(exemplar) - || (long)v.Value == getExemplarValueFunc(exemplar)); + var measurement = measurements.FirstOrDefault(v => v.Value == getExemplarValueFunc(exemplar) + || (long)v.Value == getExemplarValueFunc(exemplar)); Assert.NotEqual(default, measurement); if (measurement.ExpectTraceId) { @@ -844,7 +853,7 @@ private static void ValidateExemplars( count++; } - Assert.Equal(measurementValues.Count(), count); + Assert.Equal(measurements.Length, count); } private sealed class TestExemplarReservoir : FixedSizeExemplarReservoir diff --git a/test/OpenTelemetry.Tests/Metrics/MetricExporterTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricExporterTests.cs index 8c1d10cf48c..fc202267f1f 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricExporterTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricExporterTests.cs @@ -13,11 +13,12 @@ public class MetricExporterTests [InlineData(ExportModes.Pull | ExportModes.Push)] public void FlushMetricExporterTest(ExportModes mode) { - BaseExporter? exporter = null; + BaseExporter? exporter; switch (mode) { case ExportModes.Push: +#pragma warning disable CA2000 // Dispose objects before losing scope exporter = new PushOnlyMetricExporter(); break; case ExportModes.Pull: @@ -31,6 +32,7 @@ public void FlushMetricExporterTest(ExportModes mode) } var reader = new BaseExportingMetricReader(exporter); +#pragma warning restore CA2000 // Dispose objects before losing scope using var meterProvider = Sdk.CreateMeterProviderBuilder() .AddReader(reader) .Build(); @@ -54,7 +56,7 @@ public void FlushMetricExporterTest(ExportModes mode) } [ExportModes(ExportModes.Push)] - private class PushOnlyMetricExporter : BaseExporter + private sealed class PushOnlyMetricExporter : BaseExporter { public override ExportResult Export(in Batch batch) { @@ -63,7 +65,7 @@ public override ExportResult Export(in Batch batch) } [ExportModes(ExportModes.Pull)] - private class PullOnlyMetricExporter : BaseExporter, IPullMetricExporter + private sealed class PullOnlyMetricExporter : BaseExporter, IPullMetricExporter { public Func? Collect { get; set; } @@ -74,7 +76,7 @@ public override ExportResult Export(in Batch batch) } [ExportModes(ExportModes.Pull | ExportModes.Push)] - private class PushPullMetricExporter : BaseExporter + private sealed class PushPullMetricExporter : BaseExporter { public override ExportResult Export(in Batch batch) { diff --git a/test/OpenTelemetry.Tests/Metrics/MetricPointReclaimTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricPointReclaimTests.cs index b7b5ef2d6df..3ffcd82d6d9 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricPointReclaimTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricPointReclaimTests.cs @@ -14,7 +14,7 @@ public class MetricPointReclaimTests [InlineData(true)] public void MeasurementsAreNotDropped(bool emitMetricWithNoDimensions) { - var meter = new Meter(Utils.GetCurrentMethodName()); + using var meter = new Meter(Utils.GetCurrentMethodName()); var counter = meter.CreateCounter("MyFruitCounter"); int numberOfUpdateThreads = 25; @@ -47,7 +47,9 @@ void EmitMetric(object obj) } // There are separate code paths for single dimension vs multiple dimensions +#pragma warning disable CA5394 // Do not use insecure randomness if (random.Next(2) == 0) +#pragma warning restore CA5394 // Do not use insecure randomness { counter.Add(100, new KeyValuePair("key", $"value{i}")); } @@ -100,7 +102,7 @@ void EmitMetric(object obj) [InlineData(true)] public void MeasurementsAreAggregatedEvenAfterTheyAreDropped(bool emitMetricWithNoDimension) { - var meter = new Meter(Utils.GetCurrentMethodName()); + using var meter = new Meter(Utils.GetCurrentMethodName()); var counter = meter.CreateCounter("MyFruitCounter"); long sum = 0; @@ -150,7 +152,9 @@ void EmitMetric() Interlocked.Add(ref sum, 25); } +#pragma warning disable CA5394 // Do not use insecure randomness var index = random.Next(measurementValues.Length); +#pragma warning restore CA5394 // Do not use insecure randomness var measurement = measurementValues[index]; counter.Add(measurement, new KeyValuePair("key", $"value{index}")); Interlocked.Add(ref sum, measurement); @@ -190,7 +194,7 @@ private sealed class ThreadArguments private sealed class CustomExporter : BaseExporter { - public long Sum = 0; + public long Sum; private readonly bool assertNoDroppedMeasurements; diff --git a/test/OpenTelemetry.Tests/Metrics/MetricTestData.cs b/test/OpenTelemetry.Tests/Metrics/MetricTestData.cs index 669212a103a..0a8e45630c5 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricTestData.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricTestData.cs @@ -1,59 +1,61 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using Xunit; + namespace OpenTelemetry.Metrics.Tests; -public class MetricTestData +internal static class MetricTestData { - public static IEnumerable InvalidInstrumentNames - => new List - { - new object[] { " " }, - new object[] { "-first-char-not-alphabetic" }, - new object[] { "1first-char-not-alphabetic" }, - new object[] { "invalid+separator" }, - new object[] { new string('m', 256) }, - new object[] { "a\xb5" }, // `\xb5` is the Micro character - }; + public static TheoryData InvalidInstrumentNames + => + [ + " ", + "-first-char-not-alphabetic", + "1first-char-not-alphabetic", + "invalid+separator", + new('m', 256), + "a\xb5", // `\xb5` is the Micro character + ]; - public static IEnumerable ValidInstrumentNames - => new List - { - new object[] { "m" }, - new object[] { "first-char-alphabetic" }, - new object[] { "my-2-instrument" }, - new object[] { "my.metric" }, - new object[] { "my_metric2" }, - new object[] { new string('m', 255) }, - new object[] { "CaSe-InSeNsItIvE" }, - new object[] { "my_metric/environment/database" }, - }; + public static TheoryData ValidInstrumentNames + => + [ + "m", + "first-char-alphabetic", + "my-2-instrument", + "my.metric", + "my_metric2", + new('m', 255), + "CaSe-InSeNsItIvE", + "my_metric/environment/database", + ]; - public static IEnumerable InvalidHistogramBoundaries - => new List - { - new object[] { new double[] { 0, 0 } }, - new object[] { new double[] { 1, 0 } }, - new object[] { new double[] { 0, 1, 1, 2 } }, - new object[] { new double[] { 0, 1, 2, -1 } }, - }; + public static TheoryData InvalidHistogramBoundaries + => + [ + [0.0, 0.0], + [1.0, 0.0], + [0.0, 1.0, 1.0, 2.0], + [0.0, 1.0, 2.0, -1.0], + ]; - public static IEnumerable ValidHistogramMinMax - => new List - { - new object[] { new double[] { -10, 0, 1, 9, 10, 11, 19 }, new HistogramConfiguration(), -10, 19 }, - new object[] { new double[] { double.NegativeInfinity }, new HistogramConfiguration(), double.NegativeInfinity, double.NegativeInfinity }, - new object[] { new double[] { double.NegativeInfinity, 0, double.PositiveInfinity }, new HistogramConfiguration(), double.NegativeInfinity, double.PositiveInfinity }, - new object[] { new double[] { 1 }, new HistogramConfiguration(), 1, 1 }, - new object[] { new double[] { 5, 100, 4, 101, -2, 97 }, new ExplicitBucketHistogramConfiguration() { Boundaries = new double[] { 10, 20 } }, -2, 101 }, - new object[] { new double[] { 5, 100, 4, 101, -2, 97 }, new Base2ExponentialBucketHistogramConfiguration(), 4, 101 }, - }; + public static TheoryData ValidHistogramMinMax => + new() + { + { [-10.0, 0.0, 1.0, 9.0, 10.0, 11.0, 19.0], new HistogramConfiguration(), -10.0, 19.0 }, + { [double.NegativeInfinity], new HistogramConfiguration(), double.NegativeInfinity, double.NegativeInfinity }, + { [double.NegativeInfinity, 0.0, double.PositiveInfinity], new HistogramConfiguration(), double.NegativeInfinity, double.PositiveInfinity }, + { [1.0], new HistogramConfiguration(), 1.0, 1.0 }, + { [5.0, 100.0, 4.0, 101.0, -2.0, 97.0], new ExplicitBucketHistogramConfiguration { Boundaries = [10.0, 20.0] }, -2.0, 101.0 }, + { [5.0, 100.0, 4.0, 101.0, -2.0, 97.0], new Base2ExponentialBucketHistogramConfiguration(), 4.0, 101.0 }, + }; - public static IEnumerable InvalidHistogramMinMax - => new List + public static TheoryData InvalidHistogramMinMax + => new() { - new object[] { new double[] { 1 }, new HistogramConfiguration() { RecordMinMax = false } }, - new object[] { new double[] { 1 }, new ExplicitBucketHistogramConfiguration() { Boundaries = new double[] { 10, 20 }, RecordMinMax = false } }, - new object[] { new double[] { 1 }, new Base2ExponentialBucketHistogramConfiguration() { RecordMinMax = false } }, + { [1.0], new HistogramConfiguration { RecordMinMax = false } }, + { [1.0], new ExplicitBucketHistogramConfiguration { Boundaries = [10.0, 20.0], RecordMinMax = false } }, + { [1.0], new Base2ExponentialBucketHistogramConfiguration { RecordMinMax = false } }, }; } diff --git a/test/OpenTelemetry.Tests/Metrics/MetricTestsBase.cs b/test/OpenTelemetry.Tests/Metrics/MetricTestsBase.cs index 934433a6fe6..9ca73b30b43 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricTestsBase.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricTestsBase.cs @@ -10,11 +10,14 @@ using Microsoft.Extensions.Diagnostics.Metrics; using Microsoft.Extensions.Hosting; #endif +using OpenTelemetry.Internal; using Xunit; namespace OpenTelemetry.Metrics.Tests; +#pragma warning disable CA1515 // Consider making public types internal public abstract class MetricTestsBase +#pragma warning restore CA1515 // Consider making public types internal { protected MetricTestsBase() { @@ -80,7 +83,7 @@ static void ConfigureBuilder(MeterProviderBuilder builder, Action> expectedTags, ReadOnlyTagCollection actualTags) + internal static void ValidateMetricPointTags(List> expectedTags, ReadOnlyTagCollection actualTags) { int tagIndex = 0; foreach (var tag in actualTags) @@ -93,7 +96,7 @@ public static void ValidateMetricPointTags(List> e Assert.Equal(expectedTags.Count, tagIndex); } - public static long GetLongSum(List metrics) + internal static long GetLongSum(List metrics) { long sum = 0; foreach (var metric in metrics) @@ -114,7 +117,7 @@ public static long GetLongSum(List metrics) return sum; } - public static double GetDoubleSum(List metrics) + internal static double GetDoubleSum(List metrics) { double sum = 0; foreach (var metric in metrics) @@ -135,7 +138,7 @@ public static double GetDoubleSum(List metrics) return sum; } - public static int GetNumberOfMetricPoints(List metrics) + internal static int GetNumberOfMetricPoints(List metrics) { int count = 0; foreach (var metric in metrics) @@ -149,7 +152,7 @@ public static int GetNumberOfMetricPoints(List metrics) return count; } - public static MetricPoint? GetFirstMetricPoint(IEnumerable metrics) + internal static MetricPoint? GetFirstMetricPoint(IEnumerable metrics) { foreach (var metric in metrics) { @@ -165,7 +168,7 @@ public static int GetNumberOfMetricPoints(List metrics) // This method relies on the assumption that MetricPoints are exported in the order in which they are emitted. // For Delta AggregationTemporality, this holds true only until the AggregatorStore has not begun recaliming the MetricPoints. // Provide tags input sorted by Key - public static void CheckTagsForNthMetricPoint(List metrics, List> tags, int n) + internal static void CheckTagsForNthMetricPoint(List metrics, List> tags, int n) { var metric = metrics[0]; var metricPointEnumerator = metric.GetMetricPoints().GetEnumerator(); @@ -185,14 +188,18 @@ public static void CheckTagsForNthMetricPoint(List metrics, List GetExemplars(MetricPoint mp) + { + return mp.TryGetExemplars(out var exemplars) + ? exemplars.ToReadOnlyList() + : []; + } + + internal static IDisposable BuildMeterProvider( out MeterProvider meterProvider, Action configure) { - if (configure == null) - { - throw new ArgumentNullException(nameof(configure)); - } + Guard.ThrowIfNull(configure); #if BUILDING_HOSTING_TESTS var host = BuildHost( @@ -211,45 +218,10 @@ public IDisposable BuildMeterProvider( #endif } - internal static IReadOnlyList GetExemplars(MetricPoint mp) - { - if (mp.TryGetExemplars(out var exemplars)) - { - return exemplars.ToReadOnlyList(); - } - - return Array.Empty(); - } - #if BUILDING_HOSTING_TESTS - public sealed class HostingMeterProviderBuilder : MeterProviderBuilderBase - { - public HostingMeterProviderBuilder(IServiceCollection services) - : base(services) - { - } - - public override MeterProviderBuilder AddMeter(params string[] names) - { - return this.ConfigureServices(services => - { - foreach (var name in names) - { - // Note: The entire purpose of this class is to use the - // IMetricsBuilder API to enable Metrics and NOT the - // traditional AddMeter API. - services.AddMetrics(builder => builder.EnableMetrics(name)); - } - }); - } - - public MeterProviderBuilder AddSdkMeter(params string[] names) - { - return base.AddMeter(names); - } - } - +#pragma warning disable CA1812 // Avoid uninstantiated internal classes private sealed class MetricsSubscriptionManagerCleanupHostedService : IHostedService, IDisposable +#pragma warning restore CA1812 // Avoid uninstantiated internal classes { private readonly object metricsSubscriptionManager; diff --git a/test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs index f2a9ceb6eab..96a1122be39 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs @@ -18,7 +18,7 @@ public void ViewToRenameMetric() using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView("name1", "renamed") .AddInMemoryExporter(exportedItems)); @@ -40,19 +40,19 @@ public void AddViewWithInvalidNameThrowsArgumentException(string viewNewName) using var meter1 = new Meter("AddViewWithInvalidNameThrowsArgumentException"); - var ex = Assert.Throws(() => this.BuildMeterProvider(out var meterProvider, builder => builder + var ex = Assert.Throws(() => BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) .AddView("name1", viewNewName) .AddInMemoryExporter(exportedItems))); - Assert.Contains($"Custom view name {viewNewName} is invalid.", ex.Message); + Assert.Contains($"Custom view name {viewNewName} is invalid.", ex.Message, StringComparison.Ordinal); - ex = Assert.Throws(() => this.BuildMeterProvider(out var meterProvider, builder => builder + ex = Assert.Throws(() => BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) .AddView("name1", new MetricStreamConfiguration() { Name = viewNewName }) .AddInMemoryExporter(exportedItems))); - Assert.Contains($"Custom view name {viewNewName} is invalid.", ex.Message); + Assert.Contains($"Custom view name {viewNewName} is invalid.", ex.Message, StringComparison.Ordinal); } [Fact] @@ -62,7 +62,7 @@ public void AddViewWithNullMetricStreamConfigurationThrowsArgumentNullException( using var meter1 = new Meter("AddViewWithInvalidNameThrowsArgumentException"); - Assert.Throws(() => this.BuildMeterProvider(out var meterProvider, builder => builder + Assert.Throws(() => BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) .AddView("name1", (MetricStreamConfiguration)null!) .AddInMemoryExporter(exportedItems))); @@ -75,7 +75,7 @@ public void AddViewWithNameThrowsInvalidArgumentExceptionWhenConflict() using var meter1 = new Meter("AddViewWithGuaranteedConflictThrowsInvalidArgumentException"); - Assert.Throws(() => this.BuildMeterProvider(out var meterProvider, builder => builder + Assert.Throws(() => BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) .AddView("instrumenta.*", name: "newname") .AddInMemoryExporter(exportedItems))); @@ -88,7 +88,7 @@ public void AddViewWithNameInMetricStreamConfigurationThrowsInvalidArgumentExcep using var meter1 = new Meter("AddViewWithGuaranteedConflictThrowsInvalidArgumentException"); - Assert.Throws(() => this.BuildMeterProvider(out var meterProvider, builder => builder + Assert.Throws(() => BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) .AddView("instrumenta.*", new MetricStreamConfiguration() { Name = "newname" }) .AddInMemoryExporter(exportedItems))); @@ -100,9 +100,9 @@ public void AddViewWithExceptionInUserCallbackAppliedDefault() var exportedItems = new List(); using var meter1 = new Meter("AddViewWithExceptionInUserCallback"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) - .AddView((instrument) => { throw new Exception("bad"); }) + .AddView(_ => { throw new InvalidOperationException("bad"); }) .AddInMemoryExporter(exportedItems)); using (var inMemoryEventListener = new InMemoryEventListener(OpenTelemetrySdkEventSource.Log)) @@ -127,9 +127,9 @@ public void AddViewWithExceptionInUserCallbackNoDefault() var exportedItems = new List(); using var meter1 = new Meter("AddViewWithExceptionInUserCallback"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) - .AddView((instrument) => { throw new Exception("bad"); }) + .AddView(_ => { throw new InvalidOperationException("bad"); }) .AddView("*", MetricStreamConfiguration.Drop) .AddInMemoryExporter(exportedItems)); @@ -137,7 +137,7 @@ public void AddViewWithExceptionInUserCallbackNoDefault() { var counter1 = meter1.CreateCounter("counter1"); counter1.Add(1); - Assert.Single(inMemoryEventListener.Events.Where((e) => e.EventId == 41)); + Assert.Single(inMemoryEventListener.Events, e => e.EventId == 41); } meterProvider.ForceFlush(MaxTimeToAllowForFlush); @@ -154,10 +154,10 @@ public void AddViewsWithAndWithoutExceptionInUserCallback() var exportedItems = new List(); using var meter1 = new Meter("AddViewWithExceptionInUserCallback"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) - .AddView((instrument) => { throw new Exception("bad"); }) - .AddView((instrument) => { return new MetricStreamConfiguration() { Name = "newname" }; }) + .AddView(_ => { throw new InvalidOperationException("bad"); }) + .AddView(_ => { return new MetricStreamConfiguration() { Name = "newname" }; }) .AddInMemoryExporter(exportedItems)); using (var inMemoryEventListener = new InMemoryEventListener(OpenTelemetrySdkEventSource.Log)) @@ -181,10 +181,10 @@ public void AddViewsWithAndWithoutExceptionInUserCallback() [MemberData(nameof(MetricTestData.InvalidHistogramBoundaries), MemberType = typeof(MetricTestData))] public void AddViewWithInvalidHistogramBoundsThrowsArgumentException(double[] boundaries) { - var ex = Assert.Throws(() => this.BuildMeterProvider(out var meterProvider, builder => builder + var ex = Assert.Throws(() => BuildMeterProvider(out var meterProvider, builder => builder .AddView("name1", new ExplicitBucketHistogramConfiguration { Boundaries = boundaries }))); - Assert.Contains("Histogram boundaries must be in ascending order with distinct values", ex.Message); + Assert.Contains("Histogram boundaries must be in ascending order with distinct values", ex.Message, StringComparison.Ordinal); } [Theory] @@ -193,10 +193,10 @@ public void AddViewWithInvalidHistogramBoundsThrowsArgumentException(double[] bo [InlineData(1)] public void AddViewWithInvalidExponentialHistogramMaxSizeConfigThrowsArgumentException(int maxSize) { - var ex = Assert.Throws(() => this.BuildMeterProvider(out var meterProvider, builder => builder + var ex = Assert.Throws(() => BuildMeterProvider(out var meterProvider, builder => builder .AddView("name1", new Base2ExponentialBucketHistogramConfiguration { MaxSize = maxSize }))); - Assert.Contains("Histogram max size is invalid", ex.Message); + Assert.Contains("Histogram max size is invalid", ex.Message, StringComparison.Ordinal); } [Theory] @@ -204,10 +204,10 @@ public void AddViewWithInvalidExponentialHistogramMaxSizeConfigThrowsArgumentExc [InlineData(21)] public void AddViewWithInvalidExponentialHistogramMaxScaleConfigThrowsArgumentException(int maxScale) { - var ex = Assert.Throws(() => this.BuildMeterProvider(out var meterProvider, builder => builder + var ex = Assert.Throws(() => BuildMeterProvider(out var meterProvider, builder => builder .AddView("name1", new Base2ExponentialBucketHistogramConfiguration { MaxScale = maxScale }))); - Assert.Contains("Histogram max scale is invalid", ex.Message); + Assert.Contains("Histogram max scale is invalid", ex.Message, StringComparison.Ordinal); } [Theory] @@ -220,7 +220,7 @@ public void AddViewWithInvalidHistogramBoundsIgnored(double[] boundaries) var counter1 = meter1.CreateCounter("counter1"); - using (var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using (var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) .AddView((instrument) => { @@ -245,7 +245,7 @@ public void ViewWithValidNameExported(string viewNewName) var exportedItems = new List(); using var meter1 = new Meter("ViewWithInvalidNameIgnoredTest"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) .AddView("name1", viewNewName) .AddInMemoryExporter(exportedItems)); @@ -268,7 +268,7 @@ public void ViewToRenameMetricConditionally() var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) .AddMeter(meter2.Name) .AddView((instrument) => @@ -307,7 +307,7 @@ public void ViewWithInvalidNameIgnoredConditionally(string viewNewName) { using var meter1 = new Meter("ViewToRenameMetricConditionallyTest"); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) // since here it's a func, we can't validate the name right away @@ -343,7 +343,7 @@ public void ViewWithValidNameConditionally(string viewNewName) { using var meter1 = new Meter("ViewToRenameMetricConditionallyTest"); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) .AddView((instrument) => { @@ -379,7 +379,7 @@ public void ViewWithNullCustomNameTakesInstrumentName() using var meter = new Meter("ViewToRenameMetricConditionallyTest"); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView((instrument) => { @@ -414,7 +414,7 @@ public void ViewToProduceMultipleStreamsFromInstrument() using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView("name1", "renamedStream1") .AddView("name1", "renamedStream2") @@ -435,7 +435,7 @@ public void ViewToProduceMultipleStreamsWithDuplicatesFromInstrument() using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView("name1", "renamedStream1") .AddView("name1", "renamedStream2") @@ -460,7 +460,7 @@ public void ViewWithHistogramConfigurationIgnoredWhenAppliedToNonHistogram() using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView("NotAHistogram", new ExplicitBucketHistogramConfiguration() { Name = "ImAnExplicitBoundsHistogram" }) .AddView("NotAHistogram", new Base2ExponentialBucketHistogramConfiguration() { Name = "ImAnExponentialHistogram" }) @@ -475,7 +475,7 @@ public void ViewWithHistogramConfigurationIgnoredWhenAppliedToNonHistogram() Assert.Equal("NotAHistogram", metric.Name); - List metricPoints = new List(); + List metricPoints = []; foreach (ref readonly var mp in metric.GetMetricPoints()) { metricPoints.Add(mp); @@ -493,7 +493,7 @@ public void ViewToProduceCustomHistogramBound() var exportedItems = new List(); var boundaries = new double[] { 10, 20 }; - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView("MyHistogram", new ExplicitBucketHistogramConfiguration() { Name = "MyHistogramDefaultBound" }) .AddView("MyHistogram", new ExplicitBucketHistogramConfiguration() { Boundaries = boundaries }) @@ -515,7 +515,7 @@ public void ViewToProduceCustomHistogramBound() Assert.Equal("MyHistogramDefaultBound", metricDefault.Name); Assert.Equal("MyHistogram", metricCustom.Name); - List metricPointsDefault = new List(); + List metricPointsDefault = []; foreach (ref readonly var mp in metricDefault.GetMetricPoints()) { metricPointsDefault.Add(mp); @@ -542,7 +542,7 @@ public void ViewToProduceCustomHistogramBound() Assert.Equal(Metric.DefaultHistogramBounds.Length + 1, actualCount); - List metricPointsCustom = new List(); + List metricPointsCustom = []; foreach (ref readonly var mp in metricCustom.GetMetricPoints()) { metricPointsCustom.Add(mp); @@ -559,7 +559,7 @@ public void ViewToProduceCustomHistogramBound() index = 0; actualCount = 0; - expectedBucketCounts = new long[] { 5, 2, 0 }; + expectedBucketCounts = [5, 2, 0]; foreach (var histogramMeasurement in histogramPoint.GetHistogramBuckets()) { Assert.Equal(expectedBucketCounts[index], histogramMeasurement.BucketCount); @@ -577,7 +577,7 @@ public void HistogramWithAdviceBoundaries_HandlesAllTypes() var exportedItems = new List(); int counter = 0; - using var container = this.BuildMeterProvider(out var meterProvider, builder => + using var container = BuildMeterProvider(out var meterProvider, builder => { builder.AddMeter(meter.Name); builder.AddInMemoryExporter(exportedItems); @@ -586,11 +586,11 @@ public void HistogramWithAdviceBoundaries_HandlesAllTypes() // Test cases for different histogram types var histograms = new Instrument[] { - meter.CreateHistogram("longHistogram", unit: null, description: null, tags: null, new() { HistogramBucketBoundaries = new List() { 10, 20 } }), - meter.CreateHistogram("intHistogram", unit: null, description: null, tags: null, new() { HistogramBucketBoundaries = new List() { 10, 20 } }), - meter.CreateHistogram("shortHistogram", unit: null, description: null, tags: null, new() { HistogramBucketBoundaries = new List() { 10, 20 } }), - meter.CreateHistogram("floatHistogram", unit: null, description: null, tags: null, new() { HistogramBucketBoundaries = new List() { 10.0F, 20.0F } }), - meter.CreateHistogram("doubleHistogram", unit: null, description: null, tags: null, new() { HistogramBucketBoundaries = new List() { 10.0, 20.0 } }), + meter.CreateHistogram("longHistogram", unit: null, description: null, tags: null, new() { HistogramBucketBoundaries = [10L, 20L] }), + meter.CreateHistogram("intHistogram", unit: null, description: null, tags: null, new() { HistogramBucketBoundaries = [10, 20] }), + meter.CreateHistogram("shortHistogram", unit: null, description: null, tags: null, new() { HistogramBucketBoundaries = [10, 20] }), + meter.CreateHistogram("floatHistogram", unit: null, description: null, tags: null, new() { HistogramBucketBoundaries = [10.0F, 20.0F] }), + meter.CreateHistogram("doubleHistogram", unit: null, description: null, tags: null, new() { HistogramBucketBoundaries = [10.0, 20.0] }), }; foreach (var histogram in histograms) @@ -635,7 +635,7 @@ public void HistogramWithAdviceBoundaries_HandlesAllTypes() meterProvider.ForceFlush(MaxTimeToAllowForFlush); var metricCustom = exportedItems[counter]; - List metricPointsCustom = new List(); + List metricPointsCustom = []; foreach (ref readonly var mp in metricCustom.GetMetricPoints()) { metricPointsCustom.Add(mp); @@ -652,7 +652,7 @@ public void HistogramWithAdviceBoundaries_HandlesAllTypes() var index = 0; var actualCount = 0; - long[] expectedBucketCounts = new long[] { 2, 1, 0 }; + long[] expectedBucketCounts = [2, 1, 0]; foreach (var histogramMeasurement in histogramPoint.GetHistogramBuckets()) { @@ -672,10 +672,10 @@ public void HistogramWithAdviceBoundariesSpecifiedTests(bool useViewToOverride) { using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - IReadOnlyList adviceBoundaries = new List() { 5, 10, 20 }; - double[] viewBoundaries = new double[] { 10, 20 }; + IReadOnlyList adviceBoundaries = [5L, 10L, 20L]; + double[] viewBoundaries = [10, 20]; - using var container = this.BuildMeterProvider(out var meterProvider, builder => + using var container = BuildMeterProvider(out var meterProvider, builder => { builder.AddMeter(meter.Name); @@ -712,7 +712,7 @@ public void HistogramWithAdviceBoundariesSpecifiedTests(bool useViewToOverride) Assert.Equal("MyHistogram", metricCustom.Name); - List metricPointsCustom = new List(); + List metricPointsCustom = []; foreach (ref readonly var mp in metricCustom.GetMetricPoints()) { metricPointsCustom.Add(mp); @@ -729,7 +729,7 @@ public void HistogramWithAdviceBoundariesSpecifiedTests(bool useViewToOverride) var index = 0; var actualCount = 0; - long[] expectedBucketCounts = useViewToOverride ? new long[] { 5, 2, 1 } : new long[] { 3, 2, 2, 1 }; + long[] expectedBucketCounts = useViewToOverride ? [5, 2, 1] : [3, 2, 2, 1]; foreach (var histogramMeasurement in histogramPoint.GetHistogramBuckets()) { @@ -749,7 +749,7 @@ public void ViewToProduceExponentialHistogram() using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView("MyHistogram", new Base2ExponentialBucketHistogramConfiguration()) .AddInMemoryExporter(exportedItems)); @@ -793,11 +793,20 @@ public void ViewToProduceExponentialHistogram() [MemberData(nameof(MetricTestData.ValidHistogramMinMax), MemberType = typeof(MetricTestData))] public void HistogramMinMax(double[] values, HistogramConfiguration histogramConfiguration, double expectedMin, double expectedMax) { +#if NET + Assert.NotNull(values); +#else + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } +#endif + using var meter = new Meter(Utils.GetCurrentMethodName()); var histogram = meter.CreateHistogram("MyHistogram"); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView(histogram.Name, histogramConfiguration) .AddInMemoryExporter(exportedItems)); @@ -831,11 +840,20 @@ public void HistogramMinMax(double[] values, HistogramConfiguration histogramCon [MemberData(nameof(MetricTestData.InvalidHistogramMinMax), MemberType = typeof(MetricTestData))] public void HistogramMinMaxNotPresent(double[] values, HistogramConfiguration histogramConfiguration) { +#if NET + Assert.NotNull(values); +#else + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } +#endif + using var meter = new Meter(Utils.GetCurrentMethodName()); var histogram = meter.CreateHistogram("MyHistogram"); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView(histogram.Name, histogramConfiguration) .AddInMemoryExporter(exportedItems)); @@ -863,21 +881,21 @@ public void ViewToSelectTagKeys() using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView("FruitCounter", new MetricStreamConfiguration() { - TagKeys = new string[] { "name" }, + TagKeys = ["name"], Name = "NameOnly", }) .AddView("FruitCounter", new MetricStreamConfiguration() { - TagKeys = new string[] { "size" }, + TagKeys = ["size"], Name = "SizeOnly", }) .AddView("FruitCounter", new MetricStreamConfiguration() { - TagKeys = Array.Empty(), + TagKeys = [], Name = "NoTags", }) .AddInMemoryExporter(exportedItems)); @@ -896,7 +914,7 @@ public void ViewToSelectTagKeys() Assert.Equal(3, exportedItems.Count); var metric = exportedItems[0]; Assert.Equal("NameOnly", metric.Name); - List metricPoints = new List(); + List metricPoints = []; foreach (ref readonly var mp in metric.GetMetricPoints()) { metricPoints.Add(mp); @@ -934,7 +952,7 @@ public void ViewToDropSingleInstrument() using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView("counterNotInteresting", MetricStreamConfiguration.Drop) .AddInMemoryExporter(exportedItems)); @@ -957,7 +975,7 @@ public void ViewToDropSingleInstrumentObservableCounter() using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView("observableCounterNotInteresting", MetricStreamConfiguration.Drop) .AddInMemoryExporter(exportedItems)); @@ -978,7 +996,7 @@ public void ViewToDropSingleInstrumentObservableGauge() using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView("observableGaugeNotInteresting", MetricStreamConfiguration.Drop) .AddInMemoryExporter(exportedItems)); @@ -999,7 +1017,7 @@ public void ViewToDropMultipleInstruments() using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView("server*", MetricStreamConfiguration.Drop) .AddInMemoryExporter(exportedItems)); @@ -1026,7 +1044,7 @@ public void ViewToDropAndRetainInstrument() using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView("server.requests", MetricStreamConfiguration.Drop) .AddView("server.requests", "server.request_renamed") @@ -1049,9 +1067,9 @@ public void ViewConflict_OneInstrument_DifferentDescription() { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView("instrumentName", new MetricStreamConfiguration() { Description = "newDescription1" }) .AddView("instrumentName", new MetricStreamConfiguration() { Description = "newDescription2" }) @@ -1069,7 +1087,7 @@ public void ViewConflict_OneInstrument_DifferentDescription() Assert.Equal("newDescription1", metric1.Description); Assert.Equal("newDescription2", metric2.Description); - List metric1MetricPoints = new List(); + List metric1MetricPoints = []; foreach (ref readonly var mp in metric1.GetMetricPoints()) { metric1MetricPoints.Add(mp); @@ -1079,7 +1097,7 @@ public void ViewConflict_OneInstrument_DifferentDescription() var metricPoint1 = metric1MetricPoints[0]; Assert.Equal(10, metricPoint1.GetSumLong()); - List metric2MetricPoints = new List(); + List metric2MetricPoints = []; foreach (ref readonly var mp in metric2.GetMetricPoints()) { metric2MetricPoints.Add(mp); @@ -1098,7 +1116,7 @@ public void CardinalityLimitOfMatchingViewTakesPrecedenceOverMeterProvider(bool using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); - using var container = this.BuildMeterProvider(out var meterProvider, builder => + using var container = BuildMeterProvider(out var meterProvider, builder => { if (setDefault) { @@ -1152,9 +1170,9 @@ public void ViewConflict_TwoDistinctInstruments_ThreeStreams() { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView((instrument) => { @@ -1216,17 +1234,17 @@ public void ViewConflict_TwoIdenticalInstruments_TwoViews_DifferentTags() { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView((instrument) => { - return new MetricStreamConfiguration { TagKeys = new[] { "key1" } }; + return new MetricStreamConfiguration { TagKeys = ["key1"] }; }) .AddView((instrument) => { - return new MetricStreamConfiguration { TagKeys = new[] { "key2" } }; + return new MetricStreamConfiguration { TagKeys = ["key2"] }; }) .AddInMemoryExporter(exportedItems)); @@ -1263,17 +1281,17 @@ public void ViewConflict_TwoIdenticalInstruments_TwoViews_SameTags() { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView((instrument) => { - return new MetricStreamConfiguration { TagKeys = new[] { "key1" } }; + return new MetricStreamConfiguration { TagKeys = ["key1"] }; }) .AddView((instrument) => { - return new MetricStreamConfiguration { TagKeys = new[] { "key1" } }; + return new MetricStreamConfiguration { TagKeys = ["key1"] }; }) .AddInMemoryExporter(exportedItems)); @@ -1311,17 +1329,17 @@ public void ViewConflict_TwoIdenticalInstruments_TwoViews_DifferentHistogramBoun { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView((instrument) => { - return new ExplicitBucketHistogramConfiguration { Boundaries = new[] { 5.0, 10.0 } }; + return new ExplicitBucketHistogramConfiguration { Boundaries = [5.0, 10.0] }; }) .AddView((instrument) => { - return new ExplicitBucketHistogramConfiguration { Boundaries = new[] { 10.0, 20.0 } }; + return new ExplicitBucketHistogramConfiguration { Boundaries = [10.0, 20.0] }; }) .AddInMemoryExporter(exportedItems)); @@ -1361,7 +1379,7 @@ public void ViewConflict_TwoIdenticalInstruments_TwoViews_DifferentHistogramBoun actualCount++; } - metricPoints = new List(); + metricPoints = []; foreach (ref readonly var mp in metric2.GetMetricPoints()) { metricPoints.Add(mp); @@ -1374,7 +1392,7 @@ public void ViewConflict_TwoIdenticalInstruments_TwoViews_DifferentHistogramBoun index = 0; actualCount = 0; - expectedBucketCounts = new long[] { 0, 2, 0 }; + expectedBucketCounts = [0, 2, 0]; foreach (var histogramMeasurement in metricPoint.GetHistogramBuckets()) { Assert.Equal(expectedBucketCounts[index], histogramMeasurement.BucketCount); @@ -1388,15 +1406,15 @@ public void ViewConflict_TwoInstruments_OneMatchesView() { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView((instrument) => { if (instrument.Name == "name") { - return new MetricStreamConfiguration { Name = "othername", TagKeys = new[] { "key1" } }; + return new MetricStreamConfiguration { Name = "othername", TagKeys = ["key1"] }; } else { @@ -1441,9 +1459,9 @@ public void ViewConflict_TwoInstruments_ConflictAvoidedBecauseSecondInstrumentIs { var exportedItems = new List(); - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); - using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + using var container = BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddView((instrument) => { diff --git a/test/OpenTelemetry.Tests/Metrics/MultipleReadersTests.cs b/test/OpenTelemetry.Tests/Metrics/MultipleReadersTests.cs index c2a1ca5ccbb..26769fe39ab 100644 --- a/test/OpenTelemetry.Tests/Metrics/MultipleReadersTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MultipleReadersTests.cs @@ -3,6 +3,7 @@ using System.Diagnostics.Metrics; using Microsoft.Extensions.DependencyInjection; +using OpenTelemetry.Exporter; using OpenTelemetry.Tests; using Xunit; @@ -10,6 +11,96 @@ namespace OpenTelemetry.Metrics.Tests; public class MultipleReadersTests { + [Fact] + public void ReaderCannotBeRegisteredMoreThanOnce() + { + var exportedItems = new List(); + using var exporter = new InMemoryExporter(exportedItems); + using var reader = new BaseExportingMetricReader(exporter); + + using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + + var meterProviderBuilder1 = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddReader(reader); + var meterProviderBuilder2 = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddReader(reader); + + using var meterProvider1 = meterProviderBuilder1.Build(); + Assert.Throws(() => meterProviderBuilder2.Build()); + } + + [Fact] + public void MultipleReadersOneCollectsIndependently() + { + var exportedItems1 = new List(); + var exportedItems2 = new List(); + + using var exporter1 = new InMemoryExporter(exportedItems1); + using var exporter2 = new InMemoryExporter(exportedItems2); + using var reader1 = new BaseExportingMetricReader(exporter1); + using var reader2 = new BaseExportingMetricReader(exporter2); + + using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + var counter = meter.CreateCounter("counter"); + + var meterProviderBuilder = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddReader(reader1) + .AddReader(reader2); + + using var meterProvider = meterProviderBuilder.Build(); + + counter.Add(1); + + reader1.Collect(); + + Assert.Single(exportedItems1); + Assert.Empty(exportedItems2); + } + + [Fact] + public void MultipleReadersDifferentTemporality() + { + var exportedItems1 = new List(); + var exportedItems2 = new List(); + + using var exporter1 = new InMemoryExporter(exportedItems1); + using var exporter2 = new InMemoryExporter(exportedItems2); + using var reader1 = new BaseExportingMetricReader(exporter1); + using var reader2 = new BaseExportingMetricReader(exporter2); + reader1.TemporalityPreference = MetricReaderTemporalityPreference.Delta; + reader2.TemporalityPreference = MetricReaderTemporalityPreference.Cumulative; + + using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + var counter = meter.CreateCounter("counter"); + + var meterProviderBuilder = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddReader(reader1) + .AddReader(reader2); + + using var meterProvider = meterProviderBuilder.Build(); + + counter.Add(1); + + reader1.Collect(); + reader2.Collect(); + + AssertLongSumValueForMetric(exportedItems1[0], 1); + AssertLongSumValueForMetric(exportedItems2[0], 1); + + exportedItems1.Clear(); + + counter.Add(10); + reader1.Collect(); + reader2.Collect(); + + AssertLongSumValueForMetric(exportedItems1[0], 10); + AssertLongSumValueForMetric(exportedItems2[0], 11); + } + [Theory] [InlineData(MetricReaderTemporalityPreference.Delta, false)] [InlineData(MetricReaderTemporalityPreference.Delta, true)] diff --git a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj index 2b66cc258d7..f68174421b6 100644 --- a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj +++ b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj @@ -19,14 +19,7 @@ - - - - - - - diff --git a/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTest.cs b/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTests.cs similarity index 96% rename from test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTest.cs rename to test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTests.cs index ae9df7faaf1..2f2c337d67e 100644 --- a/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTest.cs +++ b/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTests.cs @@ -6,9 +6,9 @@ namespace OpenTelemetry.Resources.Tests; -public class OtelEnvResourceDetectorTest : IDisposable +public sealed class OtelEnvResourceDetectorTests : IDisposable { - public OtelEnvResourceDetectorTest() + public OtelEnvResourceDetectorTests() { Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, null); } diff --git a/test/OpenTelemetry.Tests/Resources/OtelServiceNameEnvVarDetectorTests.cs b/test/OpenTelemetry.Tests/Resources/OtelServiceNameEnvVarDetectorTests.cs index 305c1d403b8..e9553119ed4 100644 --- a/test/OpenTelemetry.Tests/Resources/OtelServiceNameEnvVarDetectorTests.cs +++ b/test/OpenTelemetry.Tests/Resources/OtelServiceNameEnvVarDetectorTests.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Resources.Tests; -public class OtelServiceNameEnvVarDetectorTests : IDisposable +public sealed class OtelServiceNameEnvVarDetectorTests : IDisposable { public OtelServiceNameEnvVarDetectorTests() { diff --git a/test/OpenTelemetry.Tests/Resources/ResourceBuilderTests.cs b/test/OpenTelemetry.Tests/Resources/ResourceBuilderTests.cs index 15ecdeedc88..3fec4a65c71 100644 --- a/test/OpenTelemetry.Tests/Resources/ResourceBuilderTests.cs +++ b/test/OpenTelemetry.Tests/Resources/ResourceBuilderTests.cs @@ -13,7 +13,7 @@ public void ServiceResource_ServiceName() var resource = ResourceBuilder.CreateEmpty().AddService("my-service").Build(); Assert.Equal(2, resource.Attributes.Count()); Assert.Contains(new KeyValuePair(ResourceSemanticConventions.AttributeServiceName, "my-service"), resource.Attributes); - Assert.Single(resource.Attributes.Where(kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName)); + Assert.Single(resource.Attributes, kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName); Assert.True(Guid.TryParse((string)resource.Attributes.Single(kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceInstance).Value, out _)); } diff --git a/test/OpenTelemetry.Tests/Resources/ResourceTest.cs b/test/OpenTelemetry.Tests/Resources/ResourceTests.cs similarity index 92% rename from test/OpenTelemetry.Tests/Resources/ResourceTest.cs rename to test/OpenTelemetry.Tests/Resources/ResourceTests.cs index 9d5ab40a78d..8f844b77e4e 100644 --- a/test/OpenTelemetry.Tests/Resources/ResourceTest.cs +++ b/test/OpenTelemetry.Tests/Resources/ResourceTests.cs @@ -6,12 +6,12 @@ namespace OpenTelemetry.Resources.Tests; -public class ResourceTest : IDisposable +public sealed class ResourceTests : IDisposable { private const string KeyName = "key"; private const string ValueName = "value"; - public ResourceTest() + public ResourceTests() { ClearEnvVars(); } @@ -165,18 +165,26 @@ public void CreateResource_SupportedAttributeTypes() public void CreateResource_SupportedAttributeArrayTypes() { // Arrange + string[] stringArray = ["stringValue"]; + bool[] boolArray = [true]; + double[] doubleArray = [0.1D]; + long[] longArray = [1L]; + int[] intArray = [1]; + short[] shortArray = [1]; + float[] floatArray = [0.1f]; + var attributes = new Dictionary { // natively supported array types - { "string arr", new string[] { "stringValue" } }, - { "bool arr", new bool[] { true } }, - { "double arr", new double[] { 0.1d } }, - { "long arr", new long[] { 1L } }, + { "string arr", stringArray }, + { "bool arr", boolArray }, + { "double arr", doubleArray }, + { "long arr", longArray }, // have to convert to other primitive array types - { "int arr", new int[] { 1 } }, - { "short arr", new short[] { (short)1 } }, - { "float arr", new float[] { 0.1f } }, + { "int arr", intArray }, + { "short arr", shortArray }, + { "float arr", floatArray }, }; // Act @@ -184,16 +192,15 @@ public void CreateResource_SupportedAttributeArrayTypes() // Assert Assert.Equal(7, resource.Attributes.Count()); - Assert.Equal(new string[] { "stringValue" }, resource.Attributes.Where(x => x.Key == "string arr").FirstOrDefault().Value); - Assert.Equal(new bool[] { true }, resource.Attributes.Where(x => x.Key == "bool arr").FirstOrDefault().Value); - Assert.Equal(new double[] { 0.1d }, resource.Attributes.Where(x => x.Key == "double arr").FirstOrDefault().Value); - Assert.Equal(new long[] { 1L }, resource.Attributes.Where(x => x.Key == "long arr").FirstOrDefault().Value); + Assert.Equal(stringArray, resource.Attributes.FirstOrDefault(x => x.Key == "string arr").Value); + Assert.Equal(boolArray, resource.Attributes.FirstOrDefault(x => x.Key == "bool arr").Value); + Assert.Equal(doubleArray, resource.Attributes.FirstOrDefault(x => x.Key == "double arr").Value); + Assert.Equal(longArray, resource.Attributes.FirstOrDefault(x => x.Key == "long arr").Value); - var longArr = new long[] { 1 }; - var doubleArr = new double[] { Convert.ToDouble(0.1f, System.Globalization.CultureInfo.InvariantCulture) }; - Assert.Equal(longArr, resource.Attributes.Where(x => x.Key == "int arr").FirstOrDefault().Value); - Assert.Equal(longArr, resource.Attributes.Where(x => x.Key == "short arr").FirstOrDefault().Value); - Assert.Equal(doubleArr, resource.Attributes.Where(x => x.Key == "float arr").FirstOrDefault().Value); + double[] nonNativeDoubleArray = [Convert.ToDouble(0.1f, System.Globalization.CultureInfo.InvariantCulture)]; + Assert.Equal(longArray, resource.Attributes.FirstOrDefault(x => x.Key == "int arr").Value); + Assert.Equal(longArray, resource.Attributes.FirstOrDefault(x => x.Key == "short arr").Value); + Assert.Equal(nonNativeDoubleArray, resource.Attributes.FirstOrDefault(x => x.Key == "float arr").Value); } [Fact] @@ -561,15 +568,15 @@ internal static void ValidateTelemetrySdkAttributes(IEnumerable("telemetry.sdk.name", "opentelemetry"), attributes); Assert.Contains(new KeyValuePair("telemetry.sdk.language", "dotnet"), attributes); - var versionAttribute = attributes.Where(pair => pair.Key.Equals("telemetry.sdk.version")); + var versionAttribute = attributes.Where(pair => pair.Key.Equals("telemetry.sdk.version", StringComparison.Ordinal)); Assert.Single(versionAttribute); } internal static void ValidateDefaultAttributes(IEnumerable> attributes) { - var serviceName = attributes.Where(pair => pair.Key.Equals("service.name")); + var serviceName = attributes.Where(pair => pair.Key.Equals("service.name", StringComparison.Ordinal)); Assert.Single(serviceName); - Assert.Contains("unknown_service", serviceName.FirstOrDefault().Value as string); + Assert.Contains("unknown_service", serviceName.FirstOrDefault().Value as string, StringComparison.Ordinal); } private static void ClearEnvVars() diff --git a/test/OpenTelemetry.Tests/Shared/DelegatingProcessor.cs b/test/OpenTelemetry.Tests/Shared/DelegatingProcessor.cs index db5148a5ab6..2cff5e680c3 100644 --- a/test/OpenTelemetry.Tests/Shared/DelegatingProcessor.cs +++ b/test/OpenTelemetry.Tests/Shared/DelegatingProcessor.cs @@ -3,7 +3,7 @@ namespace OpenTelemetry.Tests; -public class DelegatingProcessor : BaseProcessor +internal sealed class DelegatingProcessor : BaseProcessor where T : class { public Func OnForceFlushFunc { get; set; } = (timeout) => true; diff --git a/test/OpenTelemetry.Tests/Shared/EnabledOnDockerPlatformTheoryAttribute.cs b/test/OpenTelemetry.Tests/Shared/EnabledOnDockerPlatformTheoryAttribute.cs deleted file mode 100644 index 4fda3e1a9bc..00000000000 --- a/test/OpenTelemetry.Tests/Shared/EnabledOnDockerPlatformTheoryAttribute.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Diagnostics; -using System.Text; -using Xunit; - -namespace OpenTelemetry.Tests; - -/// -/// This skips tests if the required Docker engine is not available. -/// -internal class EnabledOnDockerPlatformTheoryAttribute : TheoryAttribute -{ - /// - /// Initializes a new instance of the class. - /// - public EnabledOnDockerPlatformTheoryAttribute(DockerPlatform dockerPlatform) - { - const string executable = "docker"; - - var stdout = new StringBuilder(); - var stderr = new StringBuilder(); - - void AppendStdout(object sender, DataReceivedEventArgs e) => stdout.Append(e.Data); - void AppendStderr(object sender, DataReceivedEventArgs e) => stderr.Append(e.Data); - - var processStartInfo = new ProcessStartInfo(); - processStartInfo.FileName = executable; - processStartInfo.Arguments = string.Join(" ", "version", "--format '{{.Server.Os}}'"); - processStartInfo.RedirectStandardOutput = true; - processStartInfo.RedirectStandardError = true; - processStartInfo.UseShellExecute = false; - - var process = new Process(); - process.StartInfo = processStartInfo; - process.OutputDataReceived += AppendStdout; - process.ErrorDataReceived += AppendStderr; - - try - { - process.Start(); - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - process.WaitForExit(); - } - finally - { - process.OutputDataReceived -= AppendStdout; - process.ErrorDataReceived -= AppendStderr; - } - - if (0.Equals(process.ExitCode) && stdout.ToString().Contains(dockerPlatform.ToString().ToLowerInvariant())) - { - return; - } - - this.Skip = $"The Docker {dockerPlatform} engine is not available."; - } - - public enum DockerPlatform - { - /// - /// Docker Linux engine. - /// - Linux, - - /// - /// Docker Windows engine. - /// - Windows, - } -} diff --git a/test/OpenTelemetry.Tests/Shared/EventSourceTestHelper.cs b/test/OpenTelemetry.Tests/Shared/EventSourceTestHelper.cs index deb638e6552..8b30f7694b1 100644 --- a/test/OpenTelemetry.Tests/Shared/EventSourceTestHelper.cs +++ b/test/OpenTelemetry.Tests/Shared/EventSourceTestHelper.cs @@ -4,6 +4,7 @@ using System.Diagnostics.Tracing; using System.Globalization; using System.Reflection; +using Xunit.Sdk; namespace OpenTelemetry.Tests; @@ -34,11 +35,11 @@ private static void VerifyMethodImplementation(EventSource eventSource, MethodIn actualEvent = listener.Messages.FirstOrDefault(x => x.EventId == 0); if (actualEvent != null) { - throw new Exception(actualEvent.Message); + throw new InvalidOperationException(actualEvent.Message); } // give up - throw new Exception("Listener failed to collect event."); + throw new InvalidOperationException("Listener failed to collect event."); } VerifyEventId(eventMethod, actualEvent); @@ -49,7 +50,7 @@ private static void VerifyMethodImplementation(EventSource eventSource, MethodIn { var name = eventMethod.DeclaringType?.Name + "." + eventMethod.Name; - throw new Exception("Method '" + name + "' is implemented incorrectly.", e); + throw new InvalidOperationException("Method '" + name + "' is implemented incorrectly.", e); } finally { @@ -100,7 +101,7 @@ private static void VerifyEventMessage(MethodInfo eventMethod, EventWrittenEvent { string expectedMessage = eventArguments.Length == 0 ? GetEventAttribute(eventMethod).Message! - : string.Format(CultureInfo.InvariantCulture, GetEventAttribute(eventMethod).Message!, eventArguments)!; + : string.Format(CultureInfo.InvariantCulture, GetEventAttribute(eventMethod).Message!, eventArguments); string actualMessage = string.Format(CultureInfo.InvariantCulture, actualEvent.Message!, actualEvent.Payload!.ToArray()); AssertEqual(nameof(VerifyEventMessage), expectedMessage, actualMessage); } @@ -116,7 +117,7 @@ private static void AssertEqual(string methodName, T expected, T actual) methodName, expected, actual); - throw new Exception(errorMessage); + throw EqualException.ForMismatchedValuesWithError(expected, actual, banner: errorMessage); } } @@ -128,6 +129,6 @@ private static EventAttribute GetEventAttribute(MethodInfo eventMethod) private static IEnumerable GetEventMethods(EventSource eventSource) { MethodInfo[] methods = eventSource.GetType().GetMethods(); - return methods.Where(m => m.GetCustomAttributes(typeof(EventAttribute), false).Any()); + return methods.Where(m => m.GetCustomAttributes(typeof(EventAttribute), false).Length > 0); } } diff --git a/test/OpenTelemetry.Tests/Shared/IEEE754Double.cs b/test/OpenTelemetry.Tests/Shared/IEEE754Double.cs index e897b09b29d..9053be979e8 100644 --- a/test/OpenTelemetry.Tests/Shared/IEEE754Double.cs +++ b/test/OpenTelemetry.Tests/Shared/IEEE754Double.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Tests; [StructLayout(LayoutKind.Explicit)] -public struct IEEE754Double +internal struct IEEE754Double { [FieldOffset(0)] public double DoubleValue = 0; @@ -24,16 +24,31 @@ public IEEE754Double(double value) public static implicit operator double(IEEE754Double value) { - return value.DoubleValue; + return ToDouble(value); } public static IEEE754Double operator ++(IEEE754Double value) + { + return Increment(value); + } + + public static IEEE754Double operator --(IEEE754Double value) + { + return Decrement(value); + } + + public static double ToDouble(IEEE754Double value) + { + return value.DoubleValue; + } + + public static IEEE754Double Increment(IEEE754Double value) { value.ULongValue++; return value; } - public static IEEE754Double operator --(IEEE754Double value) + public static IEEE754Double Decrement(IEEE754Double value) { value.ULongValue--; return value; @@ -56,7 +71,11 @@ public static IEEE754Double FromULong(ulong value) public static IEEE754Double FromString(string value) { +#if NET + return FromLong(Convert.ToInt64(value.Replace(" ", string.Empty, StringComparison.Ordinal), 2)); +#else return FromLong(Convert.ToInt64(value.Replace(" ", string.Empty), 2)); +#endif } public override string ToString() diff --git a/test/OpenTelemetry.Tests/Shared/InMemoryEventListener.cs b/test/OpenTelemetry.Tests/Shared/InMemoryEventListener.cs index 9768d619643..832f21f90d9 100644 --- a/test/OpenTelemetry.Tests/Shared/InMemoryEventListener.cs +++ b/test/OpenTelemetry.Tests/Shared/InMemoryEventListener.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Tests; -internal class InMemoryEventListener : EventListener +internal sealed class InMemoryEventListener : EventListener { public ConcurrentQueue Events = new(); diff --git a/test/OpenTelemetry.Tests/Shared/RecordOnlySampler.cs b/test/OpenTelemetry.Tests/Shared/RecordOnlySampler.cs index eb03f764e62..d2b37549a33 100644 --- a/test/OpenTelemetry.Tests/Shared/RecordOnlySampler.cs +++ b/test/OpenTelemetry.Tests/Shared/RecordOnlySampler.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Tests; -internal class RecordOnlySampler : TestSampler +internal sealed class RecordOnlySampler : TestSampler { public override SamplingResult ShouldSample(in SamplingParameters param) { diff --git a/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundFactAttribute.cs b/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundFactAttribute.cs index a465db2ee6c..624dbc2c2ed 100644 --- a/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundFactAttribute.cs +++ b/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundFactAttribute.cs @@ -5,16 +5,19 @@ namespace OpenTelemetry.Tests; -internal class SkipUnlessEnvVarFoundFactAttribute : FactAttribute +internal sealed class SkipUnlessEnvVarFoundFactAttribute : FactAttribute { public SkipUnlessEnvVarFoundFactAttribute(string environmentVariable) { + this.EnvironmentVariable = environmentVariable; if (string.IsNullOrEmpty(GetEnvironmentVariable(environmentVariable))) { this.Skip = $"Skipped because {environmentVariable} environment variable was not configured."; } } + public string EnvironmentVariable { get; } + public static string? GetEnvironmentVariable(string environmentVariableName) { string? environmentVariableValue = Environment.GetEnvironmentVariable(environmentVariableName, EnvironmentVariableTarget.Process); diff --git a/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundTheoryAttribute.cs b/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundTheoryAttribute.cs index 0e955e7dd2c..dd0a5011d51 100644 --- a/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundTheoryAttribute.cs +++ b/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundTheoryAttribute.cs @@ -5,16 +5,19 @@ namespace OpenTelemetry.Tests; -internal class SkipUnlessEnvVarFoundTheoryAttribute : TheoryAttribute +internal sealed class SkipUnlessEnvVarFoundTheoryAttribute : TheoryAttribute { public SkipUnlessEnvVarFoundTheoryAttribute(string environmentVariable) { + this.EnvironmentVariable = environmentVariable; if (string.IsNullOrEmpty(GetEnvironmentVariable(environmentVariable))) { this.Skip = $"Skipped because {environmentVariable} environment variable was not configured."; } } + public string EnvironmentVariable { get; } + public static string? GetEnvironmentVariable(string environmentVariableName) { string? environmentVariableValue = Environment.GetEnvironmentVariable(environmentVariableName, EnvironmentVariableTarget.Process); diff --git a/test/OpenTelemetry.Tests/Shared/SkipUnlessTrueTheoryAttribute.cs b/test/OpenTelemetry.Tests/Shared/SkipUnlessTrueTheoryAttribute.cs deleted file mode 100644 index e1bb7983910..00000000000 --- a/test/OpenTelemetry.Tests/Shared/SkipUnlessTrueTheoryAttribute.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Reflection; -using OpenTelemetry.Internal; -using Xunit; - -namespace OpenTelemetry.Tests; - -internal sealed class SkipUnlessTrueTheoryAttribute : TheoryAttribute -{ - public SkipUnlessTrueTheoryAttribute(Type typeContainingTest, string testFieldName, string skipMessage) - { - Guard.ThrowIfNull(typeContainingTest); - Guard.ThrowIfNullOrEmpty(testFieldName); - Guard.ThrowIfNullOrEmpty(skipMessage); - - var field = typeContainingTest.GetField(testFieldName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) - ?? throw new InvalidOperationException($"Static field '{testFieldName}' could not be found on '{typeContainingTest}' type."); - - if (field.FieldType != typeof(Func)) - { - throw new InvalidOperationException($"Field '{testFieldName}' on '{typeContainingTest}' type should be defined as '{typeof(Func)}'."); - } - - var testFunc = (Func)field.GetValue(null)!; - - if (!testFunc()) - { - this.Skip = skipMessage; - } - } -} diff --git a/test/OpenTelemetry.Tests/Shared/TestActivityExportProcessor.cs b/test/OpenTelemetry.Tests/Shared/TestActivityExportProcessor.cs index 092ce859c67..bdeb432b516 100644 --- a/test/OpenTelemetry.Tests/Shared/TestActivityExportProcessor.cs +++ b/test/OpenTelemetry.Tests/Shared/TestActivityExportProcessor.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Tests; -internal class TestActivityExportProcessor : SimpleActivityExportProcessor +internal sealed class TestActivityExportProcessor : SimpleActivityExportProcessor { public List ExportedItems = new(); diff --git a/test/OpenTelemetry.Tests/Shared/TestActivityProcessor.cs b/test/OpenTelemetry.Tests/Shared/TestActivityProcessor.cs index a85511da51c..2ca908126d2 100644 --- a/test/OpenTelemetry.Tests/Shared/TestActivityProcessor.cs +++ b/test/OpenTelemetry.Tests/Shared/TestActivityProcessor.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Tests; -internal class TestActivityProcessor : BaseProcessor +internal sealed class TestActivityProcessor : BaseProcessor { public Action? StartAction; public Action? EndAction; @@ -20,11 +20,11 @@ public TestActivityProcessor(Action? onStart, Action? onEnd) this.EndAction = onEnd; } - public bool ShutdownCalled { get; private set; } = false; + public bool ShutdownCalled { get; private set; } - public bool ForceFlushCalled { get; private set; } = false; + public bool ForceFlushCalled { get; private set; } - public bool DisposedCalled { get; private set; } = false; + public bool DisposedCalled { get; private set; } public override void OnStart(Activity span) { @@ -51,5 +51,6 @@ protected override bool OnShutdown(int timeoutMilliseconds) protected override void Dispose(bool disposing) { this.DisposedCalled = true; + base.Dispose(disposing); } } diff --git a/test/OpenTelemetry.Tests/Shared/TestEventListener.cs b/test/OpenTelemetry.Tests/Shared/TestEventListener.cs index fb497190f5e..71474b6cc4a 100644 --- a/test/OpenTelemetry.Tests/Shared/TestEventListener.cs +++ b/test/OpenTelemetry.Tests/Shared/TestEventListener.cs @@ -8,7 +8,7 @@ namespace OpenTelemetry.Tests; /// /// Event listener for testing event sources. /// -internal class TestEventListener : EventListener +internal sealed class TestEventListener : EventListener { /// Unique Id used to identify events from the test thread. private readonly Guid activityId; @@ -29,7 +29,7 @@ public TestEventListener() this.activityId = Guid.NewGuid(); EventSource.SetCurrentThreadActivityId(this.activityId); - this.events = new List(); + this.events = []; this.eventWritten = new AutoResetEvent(false); this.OnOnEventWritten = e => { @@ -66,6 +66,12 @@ public void ClearMessages() this.events.Clear(); } + public override void Dispose() + { + this.eventWritten.Dispose(); + base.Dispose(); + } + /// Handler for event source writes. /// The event data that was written. protected override void OnEventWritten(EventWrittenEventArgs eventData) diff --git a/test/OpenTelemetry.Tests/Shared/TestHttpServer.cs b/test/OpenTelemetry.Tests/Shared/TestHttpServer.cs index bcfcc7d9fda..accadb236ba 100644 --- a/test/OpenTelemetry.Tests/Shared/TestHttpServer.cs +++ b/test/OpenTelemetry.Tests/Shared/TestHttpServer.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Tests; -internal class TestHttpServer +internal static class TestHttpServer { private static readonly Random GlobalRandom = new(); @@ -20,7 +20,9 @@ public static IDisposable RunServer(Action action, out stri { try { +#pragma warning disable CA5394 // Do not use insecure randomness port = GlobalRandom.Next(2000, 5000); +#pragma warning restore CA5394 // Do not use insecure randomness server = new RunningServer(action, host, port); server.Start(); break; @@ -41,7 +43,7 @@ public static IDisposable RunServer(Action action, out stri return server; } - private class RunningServer : IDisposable + private sealed class RunningServer : IDisposable { private readonly Task httpListenerTask; private readonly HttpListener listener; @@ -64,7 +66,9 @@ public RunningServer(Action action, string host, int port) this.initialized.Set(); +#pragma warning disable CA2007 // Do not directly await a Task action(await ctxTask); +#pragma warning disable CA2007 // Do not directly await a Task } catch (Exception ex) { @@ -94,6 +98,7 @@ public void Dispose() { this.listener.Close(); this.httpListenerTask?.Wait(); + this.initialized.Dispose(); } catch (ObjectDisposedException) { diff --git a/test/OpenTelemetry.Tests/Shared/Utils.cs b/test/OpenTelemetry.Tests/Shared/Utils.cs index d85c8c74e47..95f24821d0e 100644 --- a/test/OpenTelemetry.Tests/Shared/Utils.cs +++ b/test/OpenTelemetry.Tests/Shared/Utils.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Tests; -internal class Utils +internal static class Utils { [MethodImpl(MethodImplOptions.NoInlining)] public static string GetCurrentMethodName() diff --git a/test/OpenTelemetry.Tests/SimpleExportProcessorTest.cs b/test/OpenTelemetry.Tests/SimpleExportProcessorTests.cs similarity index 65% rename from test/OpenTelemetry.Tests/SimpleExportProcessorTest.cs rename to test/OpenTelemetry.Tests/SimpleExportProcessorTests.cs index 3e5b92c5b52..6476db62cc2 100644 --- a/test/OpenTelemetry.Tests/SimpleExportProcessorTest.cs +++ b/test/OpenTelemetry.Tests/SimpleExportProcessorTests.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Tests; -public class SimpleExportProcessorTest +public class SimpleExportProcessorTests { [Fact] public void Verify_SimpleExportProcessor_HandlesException() @@ -13,16 +13,18 @@ public void Verify_SimpleExportProcessor_HandlesException() int counter = 0; // here our exporter will throw an exception. +#pragma warning disable CA2000 // Dispose objects before losing scope var testExporter = new DelegatingExporter { - OnExportFunc = (batch) => + OnExportFunc = batch => { counter++; - throw new Exception("test exception"); + throw new InvalidOperationException("test exception"); }, }; +#pragma warning restore CA2000 // Dispose objects before losing scope - var testSimpleExportProcessor = new TestSimpleExportProcessor(testExporter); + using var testSimpleExportProcessor = new TestSimpleExportProcessor(testExporter); // Verify that the Processor catches and suppresses the exception. testSimpleExportProcessor.OnEnd(new object()); @@ -34,7 +36,7 @@ public void Verify_SimpleExportProcessor_HandlesException() /// /// Testable class for abstract . /// - public class TestSimpleExportProcessor : SimpleExportProcessor + private sealed class TestSimpleExportProcessor : SimpleExportProcessor { public TestSimpleExportProcessor(BaseExporter exporter) : base(exporter) diff --git a/test/OpenTelemetry.Tests/SuppressInstrumentationTest.cs b/test/OpenTelemetry.Tests/SuppressInstrumentationTests.cs similarity index 89% rename from test/OpenTelemetry.Tests/SuppressInstrumentationTest.cs rename to test/OpenTelemetry.Tests/SuppressInstrumentationTests.cs index d6ccb414ddf..730c1b02af5 100644 --- a/test/OpenTelemetry.Tests/SuppressInstrumentationTest.cs +++ b/test/OpenTelemetry.Tests/SuppressInstrumentationTests.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Tests; -public class SuppressInstrumentationTest +public class SuppressInstrumentationTests { [Fact] public static void UsingSuppressInstrumentation() @@ -56,12 +56,17 @@ public async Task SuppressInstrumentationScopeEnterIsLocalToAsyncFlow() Assert.False(Sdk.SuppressInstrumentation); // SuppressInstrumentationScope.Enter called inside the task is only applicable to this async flow - await Task.Factory.StartNew(() => - { - Assert.False(Sdk.SuppressInstrumentation); - Assert.Equal(1, SuppressInstrumentationScope.Enter()); - Assert.True(Sdk.SuppressInstrumentation); - }); + + await Task.Factory.StartNew( + () => + { + Assert.False(Sdk.SuppressInstrumentation); + Assert.Equal(1, SuppressInstrumentationScope.Enter()); + Assert.True(Sdk.SuppressInstrumentation); + }, + CancellationToken.None, + TaskCreationOptions.None, + TaskScheduler.Default); Assert.False(Sdk.SuppressInstrumentation); // Changes made by SuppressInstrumentationScope.Enter in the task above are not reflected here as it's not part of the same async flow } diff --git a/test/OpenTelemetry.Tests/TestSelfDiagnosticsConfigRefresher.cs b/test/OpenTelemetry.Tests/TestSelfDiagnosticsConfigRefresher.cs index 9038cb00087..3014c0e94d9 100644 --- a/test/OpenTelemetry.Tests/TestSelfDiagnosticsConfigRefresher.cs +++ b/test/OpenTelemetry.Tests/TestSelfDiagnosticsConfigRefresher.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Tests; -internal class TestSelfDiagnosticsConfigRefresher(Stream? stream = null) : SelfDiagnosticsConfigRefresher +internal sealed class TestSelfDiagnosticsConfigRefresher(Stream? stream = null) : SelfDiagnosticsConfigRefresher { private readonly Stream? stream = stream; diff --git a/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorOptionsTest.cs b/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorOptionsTests.cs similarity index 97% rename from test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorOptionsTest.cs rename to test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorOptionsTests.cs index a741928fa26..6dd6bde7ff9 100644 --- a/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorOptionsTest.cs +++ b/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorOptionsTests.cs @@ -6,9 +6,9 @@ namespace OpenTelemetry.Trace.Tests; -public class BatchExportActivityProcessorOptionsTest : IDisposable +public sealed class BatchExportActivityProcessorOptionsTests : IDisposable { - public BatchExportActivityProcessorOptionsTest() + public BatchExportActivityProcessorOptionsTests() { ClearEnvVars(); } diff --git a/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorTest.cs b/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorTests.cs similarity index 78% rename from test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorTest.cs rename to test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorTests.cs index 02a5d86ed98..fe1cdfab45f 100644 --- a/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorTest.cs +++ b/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorTests.cs @@ -7,7 +7,7 @@ namespace OpenTelemetry.Trace.Tests; -public class BatchExportActivityProcessorTest +public class BatchExportActivityProcessorTests { [Fact] public void CheckNullExporter() @@ -27,7 +27,7 @@ public void CheckConstructorWithInvalidValues() } [Fact] - public void CheckIfBatchIsExportingOnQueueLimit() + public async Task CheckIfBatchIsExportingOnQueueLimit() { var exportedItems = new List(); using var exporter = new InMemoryExporter(exportedItems); @@ -44,10 +44,7 @@ public void CheckIfBatchIsExportingOnQueueLimit() processor.OnEnd(activity); - for (int i = 0; i < 10 && exportedItems.Count == 0; i++) - { - Thread.Sleep(500); - } + await WaitForMinimumCountAsync(exportedItems, 1); Assert.Single(exportedItems); @@ -69,7 +66,7 @@ public void CheckForceFlushWithInvalidTimeout() [InlineData(Timeout.Infinite)] [InlineData(0)] [InlineData(1)] - public void CheckForceFlushExport(int timeout) + public async Task CheckForceFlushExport(int timeout) { var exportedItems = new List(); using var exporter = new InMemoryExporter(exportedItems); @@ -95,22 +92,23 @@ public void CheckForceFlushExport(int timeout) Assert.Equal(0, processor.ProcessedCount); // waiting to see if time is triggering the exporter - Thread.Sleep(1_000); + await Task.Delay(TimeSpan.FromSeconds(1)); Assert.Empty(exportedItems); // forcing flush - processor.ForceFlush(timeout); + var result = processor.ForceFlush(timeout); - if (timeout == 0) - { - // ForceFlush(0) will trigger flush and return immediately, so let's sleep for a while - Thread.Sleep(1_000); - } + Assert.Equal(timeout != 0, result); - Assert.Equal(2, exportedItems.Count); + // Wait for the expected number of items to be exported + int expectedCount = 2; - Assert.Equal(2, processor.ProcessedCount); - Assert.Equal(2, processor.ReceivedCount); + await WaitForMinimumCountAsync(exportedItems, expectedCount); + + Assert.Equal(expectedCount, exportedItems.Count); + + Assert.Equal(expectedCount, processor.ProcessedCount); + Assert.Equal(expectedCount, processor.ReceivedCount); Assert.Equal(0, processor.DroppedCount); } @@ -118,7 +116,7 @@ public void CheckForceFlushExport(int timeout) [InlineData(Timeout.Infinite)] [InlineData(0)] [InlineData(1)] - public void CheckShutdownExport(int timeoutMilliseconds) + public async Task CheckShutdownExport(int timeoutMilliseconds) { var exportedItems = new List(); using var exporter = new InMemoryExporter(exportedItems); @@ -136,10 +134,7 @@ public void CheckShutdownExport(int timeoutMilliseconds) processor.OnEnd(activity); processor.Shutdown(timeoutMilliseconds); - if (timeoutMilliseconds < 1_000) - { - Thread.Sleep(1_000 - timeoutMilliseconds); - } + await WaitForMinimumCountAsync(exportedItems, 1); Assert.Single(exportedItems); @@ -174,7 +169,9 @@ public void CheckExportForRecordingButNotSampledActivity() public void CheckExportDrainsBatchOnFailure() { using var processor = new BatchActivityExportProcessor( +#pragma warning disable CA2000 // Dispose objects before losing scope exporter: new FailureExporter(), +#pragma warning restore CA2000 // Dispose objects before losing scope maxQueueSize: 3, maxExportBatchSize: 3); @@ -191,7 +188,22 @@ public void CheckExportDrainsBatchOnFailure() Assert.Equal(3, processor.ProcessedCount); // Verify batch was drained even though nothing was exported. } - private class FailureExporter : BaseExporter + private static async Task WaitForMinimumCountAsync(List collection, int minimum) + { + var maximumWait = TimeSpan.FromSeconds(5); + var waitInterval = TimeSpan.FromSeconds(0.25); + + using var cts = new CancellationTokenSource(maximumWait); + + // We check for a minimum because if there are too many it's better to + // terminate the loop and let the assert in the caller fail immediately + while (!cts.IsCancellationRequested && collection.Count < minimum) + { + await Task.Delay(waitInterval); + } + } + + private sealed class FailureExporter : BaseExporter where T : class { public override ExportResult Export(in Batch batch) => ExportResult.Failure; diff --git a/test/OpenTelemetry.Tests/Trace/BatchTest.cs b/test/OpenTelemetry.Tests/Trace/BatchTests.cs similarity index 99% rename from test/OpenTelemetry.Tests/Trace/BatchTest.cs rename to test/OpenTelemetry.Tests/Trace/BatchTests.cs index 6f0a31842f3..918852a45bf 100644 --- a/test/OpenTelemetry.Tests/Trace/BatchTest.cs +++ b/test/OpenTelemetry.Tests/Trace/BatchTests.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Trace.Tests; -public class BatchTest +public class BatchTests { [Fact] public void CheckConstructorExceptions() diff --git a/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs b/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs index 343bfa6eec4..a287f897714 100644 --- a/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs +++ b/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs @@ -13,10 +13,10 @@ public class CompositeActivityProcessorTests public void CompositeActivityProcessor_BadArgs() { Assert.Throws(() => new CompositeProcessor(null!)); - Assert.Throws(() => new CompositeProcessor(Array.Empty>())); + Assert.Throws(() => new CompositeProcessor([])); using var p1 = new TestActivityProcessor(null, null); - using var processor = new CompositeProcessor(new[] { p1 }); + using var processor = new CompositeProcessor([p1]); Assert.Throws(() => processor.AddProcessor(null!)); } @@ -34,7 +34,7 @@ public void CompositeActivityProcessor_CallsAllProcessorSequentially() using var activity = new Activity("test"); - using (var processor = new CompositeProcessor(new[] { p1, p2 })) + using (var processor = new CompositeProcessor([p1, p2])) { processor.OnStart(activity); processor.OnEnd(activity); @@ -47,14 +47,14 @@ public void CompositeActivityProcessor_CallsAllProcessorSequentially() public void CompositeActivityProcessor_ProcessorThrows() { using var p1 = new TestActivityProcessor( - activity => { throw new Exception("Start exception"); }, - activity => { throw new Exception("End exception"); }); + _ => throw new InvalidOperationException("Start exception"), + _ => throw new InvalidOperationException("End exception")); using var activity = new Activity("test"); - using var processor = new CompositeProcessor(new[] { p1 }); - Assert.Throws(() => { processor.OnStart(activity); }); - Assert.Throws(() => { processor.OnEnd(activity); }); + using var processor = new CompositeProcessor([p1]); + Assert.Throws(() => { processor.OnStart(activity); }); + Assert.Throws(() => { processor.OnEnd(activity); }); } [Fact] @@ -63,7 +63,7 @@ public void CompositeActivityProcessor_ShutsDownAll() using var p1 = new TestActivityProcessor(null, null); using var p2 = new TestActivityProcessor(null, null); - using var processor = new CompositeProcessor(new[] { p1, p2 }); + using var processor = new CompositeProcessor([p1, p2]); processor.Shutdown(); Assert.True(p1.ShutdownCalled); Assert.True(p2.ShutdownCalled); @@ -78,7 +78,7 @@ public void CompositeActivityProcessor_ForceFlush(int timeout) using var p1 = new TestActivityProcessor(null, null); using var p2 = new TestActivityProcessor(null, null); - using var processor = new CompositeProcessor(new[] { p1, p2 }); + using var processor = new CompositeProcessor([p1, p2]); processor.ForceFlush(timeout); Assert.True(p1.ForceFlushCalled); @@ -93,7 +93,7 @@ public void CompositeActivityProcessor_ForwardsParentProvider() using var p1 = new TestActivityProcessor(null, null); using var p2 = new TestActivityProcessor(null, null); - using var processor = new CompositeProcessor(new[] { p1, p2 }); + using var processor = new CompositeProcessor([p1, p2]); Assert.Null(processor.ParentProvider); Assert.Null(p1.ParentProvider); diff --git a/test/OpenTelemetry.Tests/Trace/CurrentSpanTests.cs b/test/OpenTelemetry.Tests/Trace/CurrentSpanTests.cs index 080242299e6..76a85d5b2f6 100644 --- a/test/OpenTelemetry.Tests/Trace/CurrentSpanTests.cs +++ b/test/OpenTelemetry.Tests/Trace/CurrentSpanTests.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Trace.Tests; -public class CurrentSpanTests : IDisposable +public sealed class CurrentSpanTests : IDisposable { private readonly Tracer tracer; @@ -27,7 +27,8 @@ public void CurrentSpan_WhenNoContext() [Fact] public void CurrentSpan_WhenActivityExists() { - using var activity = new Activity("foo").Start(); + using var activity = new Activity("foo"); + activity.Start(); Assert.True(Tracer.CurrentSpan.Context.IsValid); } diff --git a/test/OpenTelemetry.Tests/Trace/ExceptionProcessorTest.cs b/test/OpenTelemetry.Tests/Trace/ExceptionProcessorTests.cs similarity index 92% rename from test/OpenTelemetry.Tests/Trace/ExceptionProcessorTest.cs rename to test/OpenTelemetry.Tests/Trace/ExceptionProcessorTests.cs index c597cd7c5fd..178db510e83 100644 --- a/test/OpenTelemetry.Tests/Trace/ExceptionProcessorTest.cs +++ b/test/OpenTelemetry.Tests/Trace/ExceptionProcessorTests.cs @@ -7,7 +7,7 @@ namespace OpenTelemetry.Trace.Tests; -public class ExceptionProcessorTest +public class ExceptionProcessorTests { [Fact] public void ActivityStatusSetToErrorWhenExceptionProcessorEnabled() @@ -17,7 +17,9 @@ public void ActivityStatusSetToErrorWhenExceptionProcessorEnabled() using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource(activitySourceName) .SetSampler(new AlwaysOnSampler()) +#pragma warning disable CA2000 // Dispose objects before losing scope .AddProcessor(new ExceptionProcessor()) +#pragma warning restore CA2000 // Dispose objects before losing scope .Build(); Activity? activity1 = null; @@ -32,7 +34,7 @@ public void ActivityStatusSetToErrorWhenExceptionProcessorEnabled() { using (activity2 = activitySource.StartActivity("Activity2")) { - throw new Exception("Oops!"); + throw new InvalidOperationException("Oops!"); } } } @@ -51,7 +53,7 @@ public void ActivityStatusSetToErrorWhenExceptionProcessorEnabled() try { - throw new Exception("Oops!"); + throw new InvalidOperationException("Oops!"); } catch (Exception) { @@ -120,7 +122,7 @@ public void ActivityStatusNotSetWhenExceptionProcessorNotEnabled() { using (activity = activitySource.StartActivity("Activity")) { - throw new Exception("Oops!"); + throw new InvalidOperationException("Oops!"); } } catch (Exception) diff --git a/test/OpenTelemetry.Tests/Trace/ExportProcessorTest.cs b/test/OpenTelemetry.Tests/Trace/ExportProcessorTests.cs similarity index 87% rename from test/OpenTelemetry.Tests/Trace/ExportProcessorTest.cs rename to test/OpenTelemetry.Tests/Trace/ExportProcessorTests.cs index a696d7a2cd1..b7d3bcc911a 100644 --- a/test/OpenTelemetry.Tests/Trace/ExportProcessorTest.cs +++ b/test/OpenTelemetry.Tests/Trace/ExportProcessorTests.cs @@ -8,7 +8,7 @@ namespace OpenTelemetry.Trace.Tests; -public class ExportProcessorTest +public class ExportProcessorTests { [Fact] public void ExportProcessorIgnoresActivityWhenDropped() @@ -16,7 +16,9 @@ public void ExportProcessorIgnoresActivityWhenDropped() var activitySourceName = Utils.GetCurrentMethodName(); var sampler = new AlwaysOffSampler(); var exportedItems = new List(); +#pragma warning disable CA2000 // Dispose objects before losing scope using var processor = new TestActivityExportProcessor(new InMemoryExporter(exportedItems)); +#pragma warning restore CA2000 // Dispose objects before losing scope using var activitySource = new ActivitySource(activitySourceName); using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource(activitySourceName) @@ -40,7 +42,9 @@ public void ExportProcessorIgnoresActivityMarkedAsRecordOnly() var activitySourceName = Utils.GetCurrentMethodName(); var sampler = new RecordOnlySampler(); var exportedItems = new List(); +#pragma warning disable CA2000 // Dispose objects before losing scope using var processor = new TestActivityExportProcessor(new InMemoryExporter(exportedItems)); +#pragma warning restore CA2000 // Dispose objects before losing scope using var activitySource = new ActivitySource(activitySourceName); using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource(activitySourceName) @@ -64,7 +68,9 @@ public void ExportProcessorExportsActivityMarkedAsRecordAndSample() var activitySourceName = Utils.GetCurrentMethodName(); var sampler = new AlwaysOnSampler(); var exportedItems = new List(); +#pragma warning disable CA2000 // Dispose objects before losing scope using var processor = new TestActivityExportProcessor(new InMemoryExporter(exportedItems)); +#pragma warning restore CA2000 // Dispose objects before losing scope using var activitySource = new ActivitySource(activitySourceName); using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource(activitySourceName) diff --git a/test/OpenTelemetry.Tests/Trace/LinkTest.cs b/test/OpenTelemetry.Tests/Trace/LinkTests.cs similarity index 71% rename from test/OpenTelemetry.Tests/Trace/LinkTest.cs rename to test/OpenTelemetry.Tests/Trace/LinkTests.cs index b0a64793597..0b416a0e144 100644 --- a/test/OpenTelemetry.Tests/Trace/LinkTest.cs +++ b/test/OpenTelemetry.Tests/Trace/LinkTests.cs @@ -6,33 +6,37 @@ namespace OpenTelemetry.Trace.Tests; -public class LinkTest : IDisposable +public sealed class LinkTests : IDisposable { - private readonly IDictionary attributesMap = new Dictionary(); + private readonly Dictionary attributesMap = []; private readonly SpanContext spanContext; private readonly SpanAttributes tags; - public LinkTest() + public LinkTests() { this.spanContext = new SpanContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None); + long[] longArray = [1L, 2L]; + string[] stringArray = ["a", "b"]; + bool[] boolArray = [true, false]; + double[] doubleArray = [0.1, -0.1]; this.attributesMap.Add("MyAttributeKey0", "MyStringAttribute"); this.attributesMap.Add("MyAttributeKey1", 10L); this.attributesMap.Add("MyAttributeKey2", true); this.attributesMap.Add("MyAttributeKey3", 0.005); - this.attributesMap.Add("MyAttributeKey4", new long[] { 1, 2 }); - this.attributesMap.Add("MyAttributeKey5", new string[] { "a", "b" }); - this.attributesMap.Add("MyAttributeKey6", new bool[] { true, false }); - this.attributesMap.Add("MyAttributeKey7", new double[] { 0.1, -0.1 }); + this.attributesMap.Add("MyAttributeKey4", longArray); + this.attributesMap.Add("MyAttributeKey5", stringArray); + this.attributesMap.Add("MyAttributeKey6", boolArray); + this.attributesMap.Add("MyAttributeKey7", doubleArray); this.tags = new SpanAttributes(); this.tags.Add("MyAttributeKey0", "MyStringAttribute"); this.tags.Add("MyAttributeKey1", 10L); this.tags.Add("MyAttributeKey2", true); this.tags.Add("MyAttributeKey3", 0.005); - this.tags.Add("MyAttributeKey4", new long[] { 1, 2 }); - this.tags.Add("MyAttributeKey5", new string[] { "a", "b" }); - this.tags.Add("MyAttributeKey6", new bool[] { true, false }); - this.tags.Add("MyAttributeKey7", new double[] { 0.1, -0.1 }); + this.tags.Add("MyAttributeKey4", [1L, 2L]); + this.tags.Add("MyAttributeKey5", ["a", "b"]); + this.tags.Add("MyAttributeKey6", [true, false]); + this.tags.Add("MyAttributeKey7", [0.1, -0.1]); } [Fact] @@ -69,18 +73,6 @@ public void Equality() Assert.True(link1.Equals(link3)); } - [Fact(Skip = "ActivityLink.Equals is broken in DS7 preview: https://github.com/dotnet/runtime/issues/74026")] - public void Equality_WithAttributes() - { - var link1 = new Link(this.spanContext, this.tags); - var link2 = new Link(this.spanContext, this.tags); - object link3 = new Link(this.spanContext, this.tags); - - Assert.Equal(link1, link2); - Assert.True(link1 == link2); - Assert.True(link1.Equals(link3)); - } - [Fact] public void NotEquality() { diff --git a/test/OpenTelemetry.Tests/Trace/Propagation/TraceContextPropagatorTest.cs b/test/OpenTelemetry.Tests/Trace/Propagation/TraceContextPropagatorTests.cs similarity index 97% rename from test/OpenTelemetry.Tests/Trace/Propagation/TraceContextPropagatorTest.cs rename to test/OpenTelemetry.Tests/Trace/Propagation/TraceContextPropagatorTests.cs index 9c3f566b380..f9ec9a60a4d 100644 --- a/test/OpenTelemetry.Tests/Trace/Propagation/TraceContextPropagatorTest.cs +++ b/test/OpenTelemetry.Tests/Trace/Propagation/TraceContextPropagatorTests.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Context.Propagation.Tests; -public class TraceContextPropagatorTest +public class TraceContextPropagatorTests { private const string TraceParent = "traceparent"; private const string TraceState = "tracestate"; @@ -220,13 +220,13 @@ public void Key_IllegalVendorFormat() public void MemberCountLimit() { // test_tracestate_member_count_limit - var output1 = CallTraceContextPropagator(new string[] - { + var output1 = CallTraceContextPropagator( + [ "bar01=01,bar02=02,bar03=03,bar04=04,bar05=05,bar06=06,bar07=07,bar08=08,bar09=09,bar10=10", "bar11=11,bar12=12,bar13=13,bar14=14,bar15=15,bar16=16,bar17=17,bar18=18,bar19=19,bar20=20", "bar21=21,bar22=22,bar23=23,bar24=24,bar25=25,bar26=26,bar27=27,bar28=28,bar29=29,bar30=30", - "bar31=31,bar32=32", - }); + "bar31=31,bar32=32" + ]); var expected = "bar01=01,bar02=02,bar03=03,bar04=04,bar05=05,bar06=06,bar07=07,bar08=08,bar09=09,bar10=10" + "," + "bar11=11,bar12=12,bar13=13,bar14=14,bar15=15,bar16=16,bar17=17,bar18=18,bar19=19,bar20=20" + "," + @@ -234,13 +234,13 @@ public void MemberCountLimit() "bar31=31,bar32=32"; Assert.Equal(expected, output1); - var output2 = CallTraceContextPropagator(new string[] - { + var output2 = CallTraceContextPropagator( + [ "bar01=01,bar02=02,bar03=03,bar04=04,bar05=05,bar06=06,bar07=07,bar08=08,bar09=09,bar10=10", "bar11=11,bar12=12,bar13=13,bar14=14,bar15=15,bar16=16,bar17=17,bar18=18,bar19=19,bar20=20", "bar21=21,bar22=22,bar23=23,bar24=24,bar25=25,bar26=26,bar27=27,bar28=28,bar29=29,bar30=30", - "bar31=31,bar32=32,bar33=33", - }); + "bar31=31,bar32=32,bar33=33" + ]); Assert.Empty(output2); } @@ -323,7 +323,7 @@ private static string CallTraceContextPropagator(string[] tracestate) { var headers = new Dictionary { - { TraceParent, new[] { $"00-{TraceId}-{SpanId}-01" } }, + { TraceParent, [$"00-{TraceId}-{SpanId}-01"] }, { TraceState, tracestate }, }; var f = new TraceContextPropagator(); diff --git a/test/OpenTelemetry.Tests/Trace/SamplersTest.cs b/test/OpenTelemetry.Tests/Trace/SamplersTests.cs similarity index 96% rename from test/OpenTelemetry.Tests/Trace/SamplersTest.cs rename to test/OpenTelemetry.Tests/Trace/SamplersTests.cs index 3851eaa31a8..9c7ae28ee29 100644 --- a/test/OpenTelemetry.Tests/Trace/SamplersTest.cs +++ b/test/OpenTelemetry.Tests/Trace/SamplersTests.cs @@ -7,17 +7,14 @@ namespace OpenTelemetry.Trace.Tests; -public class SamplersTest +public class SamplersTests { - private static readonly ActivityKind ActivityKindServer = ActivityKind.Server; private readonly ActivityTraceId traceId; - private readonly ActivitySpanId spanId; private readonly ActivitySpanId parentSpanId; - public SamplersTest() + public SamplersTests() { this.traceId = ActivityTraceId.CreateRandom(); - this.spanId = ActivitySpanId.CreateRandom(); this.parentSpanId = ActivitySpanId.CreateRandom(); } @@ -31,7 +28,7 @@ public void AlwaysOnSampler_AlwaysReturnTrue(ActivityTraceFlags flags) Assert.Equal( SamplingDecision.RecordAndSample, - new AlwaysOnSampler().ShouldSample(new SamplingParameters(parentContext, this.traceId, "Another name", ActivityKindServer, null, new List { link })).Decision); + new AlwaysOnSampler().ShouldSample(new SamplingParameters(parentContext, this.traceId, "Another name", ActivityKind.Server, null, new List { link })).Decision); } [Fact] @@ -50,7 +47,7 @@ public void AlwaysOffSampler_AlwaysReturnFalse(ActivityTraceFlags flags) Assert.Equal( SamplingDecision.Drop, - new AlwaysOffSampler().ShouldSample(new SamplingParameters(parentContext, this.traceId, "Another name", ActivityKindServer, null, new List { link })).Decision); + new AlwaysOffSampler().ShouldSample(new SamplingParameters(parentContext, this.traceId, "Another name", ActivityKind.Server, null, new List { link })).Decision); } [Fact] diff --git a/test/OpenTelemetry.Tests/Trace/SamplingResultTest.cs b/test/OpenTelemetry.Tests/Trace/SamplingResultTests.cs similarity index 99% rename from test/OpenTelemetry.Tests/Trace/SamplingResultTest.cs rename to test/OpenTelemetry.Tests/Trace/SamplingResultTests.cs index 16b52b97ec2..727557f9711 100644 --- a/test/OpenTelemetry.Tests/Trace/SamplingResultTest.cs +++ b/test/OpenTelemetry.Tests/Trace/SamplingResultTests.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.Trace.Tests; -public class SamplingResultTest +public class SamplingResultTests { [Theory] [InlineData(SamplingDecision.Drop)] diff --git a/test/OpenTelemetry.Tests/Trace/SimpleExportActivityProcessorTest.cs b/test/OpenTelemetry.Tests/Trace/SimpleExportActivityProcessorTests.cs similarity index 98% rename from test/OpenTelemetry.Tests/Trace/SimpleExportActivityProcessorTest.cs rename to test/OpenTelemetry.Tests/Trace/SimpleExportActivityProcessorTests.cs index 2743c48ca80..cc790d7cbf0 100644 --- a/test/OpenTelemetry.Tests/Trace/SimpleExportActivityProcessorTest.cs +++ b/test/OpenTelemetry.Tests/Trace/SimpleExportActivityProcessorTests.cs @@ -7,7 +7,7 @@ namespace OpenTelemetry.Trace.Tests; -public class SimpleExportActivityProcessorTest +public class SimpleExportActivityProcessorTests { [Fact] public void CheckNullExporter() diff --git a/test/OpenTelemetry.Tests/Trace/SpanContextTest.cs b/test/OpenTelemetry.Tests/Trace/SpanContextTests.cs similarity index 94% rename from test/OpenTelemetry.Tests/Trace/SpanContextTest.cs rename to test/OpenTelemetry.Tests/Trace/SpanContextTests.cs index 18b4bd8c73a..f6240870f9f 100644 --- a/test/OpenTelemetry.Tests/Trace/SpanContextTest.cs +++ b/test/OpenTelemetry.Tests/Trace/SpanContextTests.cs @@ -6,12 +6,12 @@ namespace OpenTelemetry.Trace.Tests; -public class SpanContextTest +public class SpanContextTests { - private static readonly byte[] FirstTraceIdBytes = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte)'a' }; - private static readonly byte[] SecondTraceIdBytes = { 0, 0, 0, 0, 0, 0, 0, (byte)'0', 0, 0, 0, 0, 0, 0, 0, 0 }; - private static readonly byte[] FirstSpanIdBytes = { 0, 0, 0, 0, 0, 0, 0, (byte)'a' }; - private static readonly byte[] SecondSpanIdBytes = { (byte)'0', 0, 0, 0, 0, 0, 0, 0 }; + private static readonly byte[] FirstTraceIdBytes = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0a"u8.ToArray(); + private static readonly byte[] SecondTraceIdBytes = "\0\0\0\0\0\0\00\0\0\0\0\0\0\0\0"u8.ToArray(); + private static readonly byte[] FirstSpanIdBytes = "\0\0\0\0\0\0\0a"u8.ToArray(); + private static readonly byte[] SecondSpanIdBytes = "0\0\0\0\0\0\0\0"u8.ToArray(); private static readonly SpanContext First = new( diff --git a/test/OpenTelemetry.Tests/Trace/TraceIdRatioBasedSamplerTest.cs b/test/OpenTelemetry.Tests/Trace/TraceIdRatioBasedSamplerTests.cs similarity index 60% rename from test/OpenTelemetry.Tests/Trace/TraceIdRatioBasedSamplerTest.cs rename to test/OpenTelemetry.Tests/Trace/TraceIdRatioBasedSamplerTests.cs index bfcdb21a3b9..2175cfc6123 100644 --- a/test/OpenTelemetry.Tests/Trace/TraceIdRatioBasedSamplerTest.cs +++ b/test/OpenTelemetry.Tests/Trace/TraceIdRatioBasedSamplerTests.cs @@ -6,10 +6,9 @@ namespace OpenTelemetry.Trace.Tests; -public class TraceIdRatioBasedSamplerTest +public class TraceIdRatioBasedSamplerTests { private const string ActivityDisplayName = "MyActivityName"; - private static readonly ActivityKind ActivityKindServer = ActivityKind.Server; [Fact] public void OutOfRangeHighProbability() @@ -32,55 +31,53 @@ public void SampleBasedOnTraceId() // is not less than probability * Long.MAX_VALUE; var notSampledtraceId = ActivityTraceId.CreateFromBytes( - new byte[] - { - 0x8F, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - }); + [ + 0x8F, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ]); Assert.Equal( SamplingDecision.Drop, - defaultProbability.ShouldSample(new SamplingParameters(default, notSampledtraceId, ActivityDisplayName, ActivityKindServer, null, null)).Decision); + defaultProbability.ShouldSample(new SamplingParameters(default, notSampledtraceId, ActivityDisplayName, ActivityKind.Server, null, null)).Decision); // This traceId will be sampled by the TraceIdRatioBasedSampler because the first 8 bytes as long // is less than probability * Long.MAX_VALUE; var sampledtraceId = ActivityTraceId.CreateFromBytes( - new byte[] - { - 0x00, - 0x00, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - }); + [ + 0x00, + 0x00, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ]); Assert.Equal( SamplingDecision.RecordAndSample, - defaultProbability.ShouldSample(new SamplingParameters(default, sampledtraceId, ActivityDisplayName, ActivityKindServer, null, null)).Decision); + defaultProbability.ShouldSample(new SamplingParameters(default, sampledtraceId, ActivityDisplayName, ActivityKind.Server, null, null)).Decision); } [Fact] diff --git a/test/OpenTelemetry.Tests/Trace/TracerProviderBuilderExtensionsTest.cs b/test/OpenTelemetry.Tests/Trace/TracerProviderBuilderExtensionsTests.cs similarity index 97% rename from test/OpenTelemetry.Tests/Trace/TracerProviderBuilderExtensionsTest.cs rename to test/OpenTelemetry.Tests/Trace/TracerProviderBuilderExtensionsTests.cs index c07e05fcb24..502d68faca5 100644 --- a/test/OpenTelemetry.Tests/Trace/TracerProviderBuilderExtensionsTest.cs +++ b/test/OpenTelemetry.Tests/Trace/TracerProviderBuilderExtensionsTests.cs @@ -11,7 +11,7 @@ namespace OpenTelemetry.Trace.Tests; -public class TracerProviderBuilderExtensionsTest +public class TracerProviderBuilderExtensionsTests { [Fact] public void SetErrorStatusOnExceptionEnabled() @@ -35,7 +35,7 @@ public void SetErrorStatusOnExceptionEnabled() { using (activity = activitySource.StartActivity("Activity")) { - throw new Exception("Oops!"); + throw new InvalidOperationException("Oops!"); } } catch (Exception) @@ -65,7 +65,7 @@ public void SetErrorStatusOnExceptionDisabled() { using (activity = activitySource.StartActivity("Activity")) { - throw new Exception("Oops!"); + throw new InvalidOperationException("Oops!"); } } catch (Exception) @@ -93,7 +93,7 @@ public void SetErrorStatusOnExceptionDefault() { using (activity = activitySource.StartActivity("Activity")) { - throw new Exception("Oops!"); + throw new InvalidOperationException("Oops!"); } } catch (Exception) @@ -337,7 +337,9 @@ public void AddInstrumentationTest() using (var provider = Sdk.CreateTracerProviderBuilder() .AddInstrumentation() .AddInstrumentation((sp, provider) => new MyInstrumentation() { Provider = provider }) +#pragma warning disable CA2000 // Dispose objects before losing scope .AddInstrumentation(new MyInstrumentation()) +#pragma warning restore CA2000 // Dispose objects before losing scope .AddInstrumentation(() => (object?)null) .Build() as TracerProviderSdk) { @@ -354,7 +356,7 @@ public void AddInstrumentationTest() Assert.Null(((MyInstrumentation)provider.Instrumentations[2]).Provider); Assert.False(((MyInstrumentation)provider.Instrumentations[2]).Disposed); - instrumentation = new List(provider.Instrumentations); + instrumentation = [.. provider.Instrumentations]; } Assert.NotNull(instrumentation); @@ -617,7 +619,7 @@ private static void RunBuilderServiceLifecycleTest( // Note: Services can't be configured at this stage Assert.Throws( - () => builder.ConfigureServices(services => services.TryAddSingleton())); + () => builder.ConfigureServices(services => services.TryAddSingleton())); builder.AddProcessor(sp.GetRequiredService()); @@ -678,14 +680,6 @@ protected override void Dispose(bool disposing) } } - private sealed class MyExporter : BaseExporter - { - public override ExportResult Export(in Batch batch) - { - return ExportResult.Success; - } - } - private sealed class MyTracerProviderBuilder : TracerProviderBuilder { public override TracerProviderBuilder AddInstrumentation(Func instrumentationFactory) diff --git a/test/OpenTelemetry.Tests/Trace/TracerProviderExtensionsTest.cs b/test/OpenTelemetry.Tests/Trace/TracerProviderExtensionsTests.cs similarity index 84% rename from test/OpenTelemetry.Tests/Trace/TracerProviderExtensionsTest.cs rename to test/OpenTelemetry.Tests/Trace/TracerProviderExtensionsTests.cs index 5722284db99..797d50ed3da 100644 --- a/test/OpenTelemetry.Tests/Trace/TracerProviderExtensionsTest.cs +++ b/test/OpenTelemetry.Tests/Trace/TracerProviderExtensionsTests.cs @@ -8,7 +8,7 @@ namespace OpenTelemetry.Trace.Tests; -public class TracerProviderExtensionsTest +public class TracerProviderExtensionsTests { [Fact] public void Verify_ForceFlush_HandlesException() @@ -21,7 +21,7 @@ public void Verify_ForceFlush_HandlesException() Assert.True(tracerProvider.ForceFlush()); - testProcessor.OnForceFlushFunc = (timeout) => throw new Exception("test exception"); + testProcessor.OnForceFlushFunc = _ => throw new InvalidOperationException("test exception"); Assert.False(tracerProvider.ForceFlush()); } @@ -44,7 +44,7 @@ public void Verify_Shutdown_HandlesException() { using var testProcessor = new DelegatingProcessor { - OnShutdownFunc = (timeout) => throw new Exception("test exception"), + OnShutdownFunc = _ => throw new InvalidOperationException("test exception"), }; using var tracerProvider = Sdk.CreateTracerProviderBuilder() diff --git a/test/OpenTelemetry.Tests/Trace/TracerProviderSdkTest.cs b/test/OpenTelemetry.Tests/Trace/TracerProviderSdkTests.cs similarity index 96% rename from test/OpenTelemetry.Tests/Trace/TracerProviderSdkTest.cs rename to test/OpenTelemetry.Tests/Trace/TracerProviderSdkTests.cs index f93e3d13373..a24d0869e3b 100644 --- a/test/OpenTelemetry.Tests/Trace/TracerProviderSdkTest.cs +++ b/test/OpenTelemetry.Tests/Trace/TracerProviderSdkTests.cs @@ -11,11 +11,11 @@ namespace OpenTelemetry.Trace.Tests; -public class TracerProviderSdkTest : IDisposable +public sealed class TracerProviderSdkTests : IDisposable { private static readonly Action SetActivitySourceProperty = CreateActivitySourceSetter(); - public TracerProviderSdkTest() + public TracerProviderSdkTests() { Activity.DefaultIdFormat = ActivityIdFormat.W3C; } @@ -112,7 +112,7 @@ public void TracerProviderSdkInvokesSamplingWithCorrectParameters() .Build(); // OpenTelemetry Sdk is expected to set default to W3C. - Assert.True(Activity.DefaultIdFormat == ActivityIdFormat.W3C); + Assert.Equal(ActivityIdFormat.W3C, Activity.DefaultIdFormat); using (var rootActivity = activitySource.StartActivity("root")) { @@ -185,11 +185,12 @@ public void TracerProviderSdkInvokesSamplingWithCorrectParameters() // Validate that when StartActivity is called using Parent as string, // Sampling is called correctly. - using var act = new Activity("anything").Start(); - act.Stop(); - var customContextAsString = act.Id; - var expectedTraceId = act.TraceId; - var expectedParentSpanId = act.SpanId; + using var activity = new Activity("anything"); + activity.Start(); + activity.Stop(); + var customContextAsString = activity.Id; + var expectedTraceId = activity.TraceId; + var expectedParentSpanId = activity.SpanId; using (var fromCustomContextAsString = activitySource.StartActivity("customContext", ActivityKind.Client, customContextAsString)) @@ -265,19 +266,18 @@ public void TracerSdkSetsActivitySamplingResultAsPropagationWhenParentIsRemote() ActivityContext ctx = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None, isRemote: true); - using (var activity = activitySource.StartActivity("root", ActivityKind.Server, ctx)) - { - // Even if sampling returns false, for activities with remote parent, - // activity is still created with PropagationOnly. - Assert.NotNull(activity); - Assert.False(activity.IsAllDataRequested); - Assert.False(activity.Recorded); + using var activity = activitySource.StartActivity("root", ActivityKind.Server, ctx); - // This is not a root activity and parent is not remote. - // If sampling returns false, no activity is created at all. - using var innerActivity = activitySource.StartActivity("inner"); - Assert.Null(innerActivity); - } + // Even if sampling returns false, for activities with remote parent, + // activity is still created with PropagationOnly. + Assert.NotNull(activity); + Assert.False(activity.IsAllDataRequested); + Assert.False(activity.Recorded); + + // This is not a root activity and parent is not remote. + // If sampling returns false, no activity is created at all. + using var innerActivity = activitySource.StartActivity("inner"); + Assert.Null(innerActivity); } [Fact] @@ -384,7 +384,8 @@ public void TracerSdkSetsActivityDataRequestedToFalseWhenSuppressInstrumentation using (SuppressInstrumentationScope.Begin(true)) { - using var activity = new Activity(operationNameForLegacyActivity).Start(); + using var activity = new Activity(operationNameForLegacyActivity); + activity.Start(); Assert.False(activity.IsAllDataRequested); } @@ -805,7 +806,7 @@ public void SdkSamplesLegacyActivityWithAlwaysOnSampler() // Validating ActivityTraceFlags is not enough as it does not get reflected on // Id, If the Id is accessed before the sampler runs. // https://github.com/open-telemetry/opentelemetry-dotnet/issues/2700 - Assert.EndsWith("-01", activity.Id); + Assert.EndsWith("-01", activity.Id, StringComparison.Ordinal); activity.Stop(); } @@ -828,7 +829,7 @@ public void SdkSamplesLegacyActivityWithAlwaysOffSampler() // Validating ActivityTraceFlags is not enough as it does not get reflected on // Id, If the Id is accessed before the sampler runs. // https://github.com/open-telemetry/opentelemetry-dotnet/issues/2700 - Assert.EndsWith("-00", activity.Id); + Assert.EndsWith("-00", activity.Id, StringComparison.Ordinal); activity.Stop(); } @@ -856,7 +857,7 @@ public void SdkSamplesLegacyActivityWithCustomSampler(SamplingDecision samplingD // Validating ActivityTraceFlags is not enough as it does not get reflected on // Id, If the Id is accessed before the sampler runs. // https://github.com/open-telemetry/opentelemetry-dotnet/issues/2700 - Assert.EndsWith(hasRecordedFlag ? "-01" : "-00", activity.Id); + Assert.EndsWith(hasRecordedFlag ? "-01" : "-00", activity.Id, StringComparison.Ordinal); activity.Stop(); } @@ -924,7 +925,8 @@ public void SdkSamplesLegacyActivityWithRemoteParentWithCustomSampler(SamplingDe // The sampling parameters are expected to be that of the // parent context i.e the remote parent. - using var activity = new Activity(operationNameForLegacyActivity).SetParentId(remoteParentId); + using var activity = new Activity(operationNameForLegacyActivity); + activity.SetParentId(remoteParentId); activity.TraceStateString = tracestate; // At this point SetParentId has set the ActivityTraceFlags to that of the parent activity. The activity is now passed to the sampler. @@ -935,7 +937,7 @@ public void SdkSamplesLegacyActivityWithRemoteParentWithCustomSampler(SamplingDe // Validating ActivityTraceFlags is not enough as it does not get reflected on // Id, If the Id is accessed before the sampler runs. // https://github.com/open-telemetry/opentelemetry-dotnet/issues/2700 - Assert.EndsWith(hasRecordedFlag ? "-01" : "-00", activity.Id); + Assert.EndsWith(hasRecordedFlag ? "-01" : "-00", activity.Id, StringComparison.Ordinal); activity.Stop(); } @@ -960,7 +962,8 @@ public void SdkSamplesLegacyActivityWithRemoteParentWithAlwaysOnSampler(Activity // The sampling parameters are expected to be that of the // parent context i.e the remote parent. - using var activity = new Activity(operationNameForLegacyActivity).SetParentId(remoteParentId); + using var activity = new Activity(operationNameForLegacyActivity); + activity.SetParentId(remoteParentId); // At this point SetParentId has set the ActivityTraceFlags to that of the parent activity. The activity is now passed to the sampler. activity.Start(); @@ -970,7 +973,7 @@ public void SdkSamplesLegacyActivityWithRemoteParentWithAlwaysOnSampler(Activity // Validating ActivityTraceFlags is not enough as it does not get reflected on // Id, If the Id is accessed before the sampler runs. // https://github.com/open-telemetry/opentelemetry-dotnet/issues/2700 - Assert.EndsWith("-01", activity.Id); + Assert.EndsWith("-01", activity.Id, StringComparison.Ordinal); activity.Stop(); } @@ -995,7 +998,8 @@ public void SdkSamplesLegacyActivityWithRemoteParentWithAlwaysOffSampler(Activit // The sampling parameters are expected to be that of the // parent context i.e the remote parent. - using var activity = new Activity(operationNameForLegacyActivity).SetParentId(remoteParentId); + using var activity = new Activity(operationNameForLegacyActivity); + activity.SetParentId(remoteParentId); // At this point SetParentId has set the ActivityTraceFlags to that of the parent activity. The activity is now passed to the sampler. activity.Start(); @@ -1005,7 +1009,7 @@ public void SdkSamplesLegacyActivityWithRemoteParentWithAlwaysOffSampler(Activit // Validating ActivityTraceFlags is not enough as it does not get reflected on // Id, If the Id is accessed before the sampler runs. // https://github.com/open-telemetry/opentelemetry-dotnet/issues/2700 - Assert.EndsWith("-00", activity.Id); + Assert.EndsWith("-00", activity.Id, StringComparison.Ordinal); activity.Stop(); } @@ -1132,8 +1136,8 @@ public void TracerProviderSdkBuildsWithDefaultResource() var attributes = resource.Attributes; Assert.Equal(4, attributes.Count()); - ResourceTest.ValidateDefaultAttributes(attributes); - ResourceTest.ValidateTelemetrySdkAttributes(attributes); + ResourceTests.ValidateDefaultAttributes(attributes); + ResourceTests.ValidateTelemetrySdkAttributes(attributes); } [Theory] @@ -1174,7 +1178,7 @@ public void TracerProviderSdkBuildsWithSDKResource(bool useConfigure) Assert.NotEqual(Resource.Empty, resource); Assert.Contains(new KeyValuePair("telemetry.sdk.name", "opentelemetry"), attributes); Assert.Contains(new KeyValuePair("telemetry.sdk.language", "dotnet"), attributes); - var versionAttribute = attributes.Where(pair => pair.Key.Equals("telemetry.sdk.version")); + var versionAttribute = attributes.Where(pair => pair.Key.Equals("telemetry.sdk.version", StringComparison.Ordinal)); Assert.Single(versionAttribute); } @@ -1243,7 +1247,11 @@ public void SdkSamplesAndProcessesLegacySourceWhenAddLegacySourceIsCalledWithWil foreach (var ns in legacySourceNamespaces) { +#if NET + var startOpName = ns.Replace("*", "Start", StringComparison.Ordinal); +#else var startOpName = ns.Replace("*", "Start"); +#endif using var startOperation = new Activity(startOpName); startOperation.Start(); startOperation.Stop(); @@ -1251,7 +1259,11 @@ public void SdkSamplesAndProcessesLegacySourceWhenAddLegacySourceIsCalledWithWil Assert.Contains(startOpName, onStartProcessedActivities); // Processor.OnStart is called since we added a legacy OperationName Assert.Contains(startOpName, onStopProcessedActivities); // Processor.OnEnd is called since we added a legacy OperationName +#if NET + var stopOpName = ns.Replace("*", "Stop", StringComparison.Ordinal); +#else var stopOpName = ns.Replace("*", "Stop"); +#endif using var stopOperation = new Activity(stopOpName); stopOperation.Start(); stopOperation.Stop(); @@ -1342,7 +1354,11 @@ private static Action CreateActivitySourceSetter() var setMethod = typeof(Activity).GetProperty("Source")?.SetMethod ?? throw new InvalidOperationException("Could not build Activity.Source setter delegate"); +#if NET + return setMethod.CreateDelegate>(); +#else return (Action)setMethod.CreateDelegate(typeof(Action)); +#endif } private sealed class TestTracerProviderBuilder : TracerProviderBuilderBase @@ -1353,7 +1369,7 @@ public TracerProviderBuilder AddInstrumentation() } } - private class TestInstrumentation : IDisposable + private sealed class TestInstrumentation : IDisposable { public bool IsDisposed; diff --git a/test/Shared/StrongNameTests.cs b/test/Shared/StrongNameTests.cs new file mode 100644 index 00000000000..d56435f3e3f --- /dev/null +++ b/test/Shared/StrongNameTests.cs @@ -0,0 +1,44 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Xunit; + +namespace OpenTelemetry.Tests; + +// The tests can only be strong named if they only depend on assemblies that are strong named, +// therefore if the tests are strong named then the libraries we ship are strong named. + +public static class StrongNameTests +{ + [Fact] + public static void Tests_Are_Strong_Named() + { + // Arrange + var assembly = typeof(StrongNameTests).Assembly; + var name = assembly.GetName(); + + // Act + var actual = name.GetPublicKey(); + + // Assert + Assert.NotNull(actual); + Assert.NotEmpty(actual); + +#if NET + Assert.Equal( + "002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898", + Convert.ToHexString(actual)); +#endif + + // Act + actual = name.GetPublicKeyToken(); + + // Assert + Assert.NotNull(actual); + Assert.NotEmpty(actual); + +#if NET + Assert.Equal("7BD6737FE5B67E3C", Convert.ToHexString(actual)); +#endif + } +} diff --git a/test/TestApp.AspNetCore/ActivityMiddleware.cs b/test/TestApp.AspNetCore/ActivityMiddleware.cs index 591bc388f34..4406df0c27d 100644 --- a/test/TestApp.AspNetCore/ActivityMiddleware.cs +++ b/test/TestApp.AspNetCore/ActivityMiddleware.cs @@ -3,42 +3,29 @@ namespace TestApp.AspNetCore; -public class ActivityMiddleware +internal sealed class ActivityMiddleware { - private readonly ActivityMiddlewareImpl impl; + private readonly ActivityMiddlewareCore core; private readonly RequestDelegate next; - public ActivityMiddleware(RequestDelegate next, ActivityMiddlewareImpl impl) + public ActivityMiddleware(RequestDelegate next, ActivityMiddlewareCore core) { this.next = next; - this.impl = impl; + this.core = core; } public async Task InvokeAsync(HttpContext context) { - if (this.impl != null) + if (this.core != null) { - this.impl.PreProcess(context); + this.core.PreProcess(context); } - await this.next(context); + await this.next(context).ConfigureAwait(true); - if (this.impl != null) + if (this.core != null) { - this.impl.PostProcess(context); - } - } - - public class ActivityMiddlewareImpl - { - public virtual void PreProcess(HttpContext context) - { - // Do nothing - } - - public virtual void PostProcess(HttpContext context) - { - // Do nothing + this.core.PostProcess(context); } } } diff --git a/test/TestApp.AspNetCore/ActivityMiddlewareCore.cs b/test/TestApp.AspNetCore/ActivityMiddlewareCore.cs new file mode 100644 index 00000000000..7a1df1eece9 --- /dev/null +++ b/test/TestApp.AspNetCore/ActivityMiddlewareCore.cs @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +namespace TestApp.AspNetCore; + +internal sealed class ActivityMiddlewareCore +{ + public void PreProcess(HttpContext context) + { + // Do nothing + } + + public void PostProcess(HttpContext context) + { + // Do nothing + } +} diff --git a/test/TestApp.AspNetCore/CallbackMiddleware.cs b/test/TestApp.AspNetCore/CallbackMiddleware.cs index fee8569d5d0..d2c8fce527a 100644 --- a/test/TestApp.AspNetCore/CallbackMiddleware.cs +++ b/test/TestApp.AspNetCore/CallbackMiddleware.cs @@ -3,30 +3,22 @@ namespace TestApp.AspNetCore; -public class CallbackMiddleware +internal sealed class CallbackMiddleware { - private readonly CallbackMiddlewareImpl impl; + private readonly CallbackMiddlewareCore core; private readonly RequestDelegate next; - public CallbackMiddleware(RequestDelegate next, CallbackMiddlewareImpl impl) + public CallbackMiddleware(RequestDelegate next, CallbackMiddlewareCore core) { this.next = next; - this.impl = impl; + this.core = core; } public async Task InvokeAsync(HttpContext context) { - if (this.impl == null || await this.impl.ProcessAsync(context)) + if (this.core == null || await this.core.ProcessAsync(context).ConfigureAwait(true)) { - await this.next(context); - } - } - - public class CallbackMiddlewareImpl - { - public virtual async Task ProcessAsync(HttpContext context) - { - return await Task.FromResult(true); + await this.next(context).ConfigureAwait(true); } } } diff --git a/test/TestApp.AspNetCore/CallbackMiddlewareCore.cs b/test/TestApp.AspNetCore/CallbackMiddlewareCore.cs new file mode 100644 index 00000000000..c45091b1951 --- /dev/null +++ b/test/TestApp.AspNetCore/CallbackMiddlewareCore.cs @@ -0,0 +1,12 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +namespace TestApp.AspNetCore; + +internal sealed class CallbackMiddlewareCore +{ + public Task ProcessAsync(HttpContext context) + { + return Task.FromResult(true); + } +} diff --git a/test/TestApp.AspNetCore/Controllers/ChildActivityController.cs b/test/TestApp.AspNetCore/Controllers/ChildActivityController.cs index b55927000f3..9cdbd3f326c 100644 --- a/test/TestApp.AspNetCore/Controllers/ChildActivityController.cs +++ b/test/TestApp.AspNetCore/Controllers/ChildActivityController.cs @@ -15,7 +15,7 @@ public class ChildActivityController : Controller public Dictionary GetChildActivityTraceContext() { var result = new Dictionary(); - var activity = new Activity("ActivityInsideHttpRequest"); + using var activity = new Activity("ActivityInsideHttpRequest"); activity.Start(); result["TraceId"] = activity.Context.TraceId.ToString(); result["ParentSpanId"] = activity.ParentSpanId.ToString(); diff --git a/test/TestApp.AspNetCore/Controllers/ErrorController.cs b/test/TestApp.AspNetCore/Controllers/ErrorController.cs index 24c904cfe9f..56d92a8697b 100644 --- a/test/TestApp.AspNetCore/Controllers/ErrorController.cs +++ b/test/TestApp.AspNetCore/Controllers/ErrorController.cs @@ -12,6 +12,6 @@ public class ErrorController : Controller [HttpGet] public string Get() { - throw new Exception("something's wrong!"); + throw new InvalidOperationException("something's wrong!"); } } diff --git a/test/TestApp.AspNetCore/Controllers/ValuesController.cs b/test/TestApp.AspNetCore/Controllers/ValuesController.cs index 27a9ab0d2d3..4c8a591980e 100644 --- a/test/TestApp.AspNetCore/Controllers/ValuesController.cs +++ b/test/TestApp.AspNetCore/Controllers/ValuesController.cs @@ -12,7 +12,7 @@ public class ValuesController : Controller [HttpGet] public IEnumerable Get() { - return new string[] { "value1", "value2" }; + return ["value1", "value2"]; } // GET api/values/5 diff --git a/test/TestApp.AspNetCore/Program.cs b/test/TestApp.AspNetCore/Program.cs index 06071eab4bd..cbcec9226d8 100644 --- a/test/TestApp.AspNetCore/Program.cs +++ b/test/TestApp.AspNetCore/Program.cs @@ -3,52 +3,44 @@ using TestApp.AspNetCore; -public class Program -{ - public static void Main(string[] args) - { - var builder = WebApplication.CreateBuilder(args); +var builder = WebApplication.CreateBuilder(args); - // Add services to the container. +// Add services to the container. - builder.Services.AddControllers(); +builder.Services.AddControllers(); - // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle - builder.Services.AddEndpointsApiExplorer(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen(); +builder.Services.AddSwaggerGen(); - builder.Services.AddMvc(); +builder.Services.AddMvc(); - builder.Services.AddSingleton(); +builder.Services.AddSingleton(); - builder.Services.AddSingleton( - new CallbackMiddleware.CallbackMiddlewareImpl()); +builder.Services.AddSingleton(new CallbackMiddlewareCore()); - builder.Services.AddSingleton( - new ActivityMiddleware.ActivityMiddlewareImpl()); +builder.Services.AddSingleton(new ActivityMiddlewareCore()); - var app = builder.Build(); +var app = builder.Build(); - // Configure the HTTP request pipeline. - if (app.Environment.IsDevelopment()) - { - app.UseSwagger(); - app.UseSwaggerUI(); - } +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} - app.UseHttpsRedirection(); +app.UseHttpsRedirection(); - app.UseAuthorization(); +app.UseAuthorization(); - app.MapControllers(); +app.MapControllers(); - app.UseMiddleware(); +app.UseMiddleware(); - app.UseMiddleware(); +app.UseMiddleware(); - app.AddTestMiddleware(); +app.AddTestMiddleware(); - app.Run(); - } -} +app.Run(); diff --git a/test/TestApp.AspNetCore/TestApp.AspNetCore.csproj b/test/TestApp.AspNetCore/TestApp.AspNetCore.csproj index 93a3a0a972d..372e89f04e4 100644 --- a/test/TestApp.AspNetCore/TestApp.AspNetCore.csproj +++ b/test/TestApp.AspNetCore/TestApp.AspNetCore.csproj @@ -2,6 +2,7 @@ $(TargetFrameworksForAspNetCoreTests) + $(NoWarn);CA1515;CA1822;CA1812 diff --git a/test/TestApp.AspNetCore/TestMiddleware.cs b/test/TestApp.AspNetCore/TestMiddleware.cs index 39acf58db3d..aa42598cfa9 100644 --- a/test/TestApp.AspNetCore/TestMiddleware.cs +++ b/test/TestApp.AspNetCore/TestMiddleware.cs @@ -3,7 +3,7 @@ namespace TestApp.AspNetCore; -public static class TestMiddleware +internal static class TestMiddleware { private static readonly AsyncLocal?> Current = new();