diff --git a/.editorconfig b/.editorconfig index 5bc89604c7..45d0806a20 100644 --- a/.editorconfig +++ b/.editorconfig @@ -41,7 +41,7 @@ csharp_indent_labels = flush_left # Modifier preferences csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion -dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion # this. preferences dotnet_style_qualification_for_field = true:suggestion @@ -53,8 +53,8 @@ dotnet_style_qualification_for_event = true:suggestion csharp_style_var_for_built_in_types = true:silent csharp_style_var_when_type_is_apparent = true:silent csharp_style_var_elsewhere = true:silent -dotnet_style_predefined_type_for_locals_parameters_members = true:silent -dotnet_style_predefined_type_for_member_access = true:silent +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion # name all constant fields using PascalCase dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion @@ -75,6 +75,7 @@ dotnet_style_readonly_field = true:suggestion csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion dotnet_style_prefer_simplified_interpolation = true:suggestion dotnet_style_object_initializer = true:suggestion +csharp_style_prefer_primary_constructors = false:none # Expression-level preferences dotnet_style_object_initializer = true:suggestion @@ -82,21 +83,23 @@ dotnet_style_collection_initializer = true:suggestion dotnet_style_explicit_tuple_names = true:suggestion dotnet_style_coalesce_expression = true:suggestion dotnet_style_null_propagation = true:suggestion -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion -dotnet_style_prefer_auto_properties = true:silent -dotnet_style_prefer_conditional_expression_over_assignment = true:silent -dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion csharp_prefer_simple_default_expression = true:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:none # Expression-bodied members -csharp_style_expression_bodied_methods = false:silent -csharp_style_expression_bodied_constructors = false:silent -csharp_style_expression_bodied_operators = false:silent -csharp_style_expression_bodied_properties = true:silent -csharp_style_expression_bodied_indexers = true:silent -csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_methods = true:suggestion +dotnet_diagnostic.IDE0022.severity = suggestion # dotnet format doesn't respect the suggestion in the line above +csharp_style_expression_bodied_constructors = false:warning +csharp_style_expression_bodied_operators = true:suggestion +csharp_style_expression_bodied_properties = true:suggestion +csharp_style_expression_bodied_indexers = true:suggestion +csharp_style_expression_bodied_accessors = true:suggestion # Pattern matching csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion @@ -113,6 +116,7 @@ csharp_style_prefer_range_operator = false:none csharp_style_pattern_local_over_anonymous_function = true:suggestion csharp_style_deconstructed_variable_declaration = true:suggestion csharp_style_namespace_declarations = file_scoped:warning +dotnet_style_namespace_match_folder = false:none # Space preferences csharp_space_after_cast = false @@ -128,10 +132,10 @@ csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false # Parentheses preferences -dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent -dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent -dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent -dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion # Code analyzers # CA1031: Do not catch general exception types diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3bad5b876a..d503e982d1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,7 @@ name: Bug report +title: "[bug] " description: Create a report to help us improve -labels: ["bug"] +labels: [bug,needs-triage] body: - type: markdown attributes: @@ -47,7 +48,7 @@ body: - type: input attributes: label: Runtime Version - description: What .NET runtime version did you use? (e.g. `net462`, `net48`, `netcoreapp3.1`, `net6.0` etc. You can find this information from the `*.csproj` file) + description: What .NET runtime version did you use? (e.g. `net462`, `net48`, `net8.0`, etc. You can find this information from the `*.csproj` file) validations: required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..3104c7c976 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ +# https://docs.github.com/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser + +blank_issues_enabled: false +contact_links: + - name: Question + url: https://github.com/open-telemetry/opentelemetry-dotnet/discussions/new?category=q-a + about: Ask a question to help us improve our knowledge base and documentation. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 7fd9b2f4ed..ab95873173 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,6 +1,7 @@ name: Feature request +title: "[feature request] " description: Suggest an idea for this project -labels: ["enhancement"] +labels: [enhancement,needs-triage] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml deleted file mode 100644 index 76eea55351..0000000000 --- a/.github/ISSUE_TEMPLATE/question.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Question -description: Ask a question to help us improve our knowledge base and documentation -labels: ["question"] -body: - - type: markdown - attributes: - value: | - > [!NOTE] - > Please ask questions using [GitHub Discussions](https://github.com/open-telemetry/opentelemetry-dotnet/discussions/new) instead of GitHub Issues. - - - type: textarea - attributes: - label: What is the question? - description: Describe the question you have. - validations: - required: true - - - type: textarea - attributes: - label: Additional context - description: Any additional information you think may be relevant to this question. diff --git a/.github/codecov.yml b/.github/codecov.yml index b20bf1b297..c381869ce7 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -33,22 +33,22 @@ ignore: - "test" flags: - unittests-Solution-Stable: + unittests-Solution: carryforward: true paths: - src - unittests-Solution-Experimental: + unittests-Project-Stable: carryforward: true paths: - src - unittests-Instrumentation-Stable: + unittests-Project-Experimental: carryforward: true paths: - src - unittests-Instrumentation-Experimental: + unittests-UnstableCoreLibraries-Experimental: carryforward: true paths: - src diff --git a/.github/workflows/Component.BuildTest.yml b/.github/workflows/Component.BuildTest.yml index f97c8b94f9..de7ebb3fc4 100644 --- a/.github/workflows/Component.BuildTest.yml +++ b/.github/workflows/Component.BuildTest.yml @@ -20,11 +20,11 @@ on: required: false type: string os-list: - default: '[ "windows-latest", "ubuntu-latest" ]' + default: '[ "windows-latest", "ubuntu-latest", "otel-linux-arm64" ]' required: false type: string tfm-list: - default: '[ "net462", "net6.0", "net7.0", "net8.0" ]' + default: '[ "net462", "net8.0", "net9.0" ]' required: false type: string @@ -39,6 +39,10 @@ jobs: exclude: - os: ubuntu-latest version: net462 + - os: otel-linux-arm64 + version: net462 + - os: otel-linux-arm64 + version: net8.0 runs-on: ${{ matrix.os }} steps: @@ -65,7 +69,7 @@ jobs: run: dotnet tool install -g dotnet-coverage - name: Merging test results - run: dotnet-coverage merge -r -f cobertura -o ./TestResults/Cobertura.xml ./TestResults/*.coverage + 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@v4 diff --git a/.github/workflows/add-labels.yml b/.github/workflows/add-labels.yml index 3566af9ec8..cae26b876b 100644 --- a/.github/workflows/add-labels.yml +++ b/.github/workflows/add-labels.yml @@ -1,25 +1,55 @@ -name: 'Add labels for area found in bug issue descriptions' +name: 'Add labels to issues and pull requests' on: issues: - types: [opened] + types: [ opened ] + + pull_request_target: + branches: [ 'main*' ] permissions: issues: write + pull-requests: write jobs: - add-labels: - if: ${{ !github.event.issue.pull_request }} + add-labels-on-issues: + if: github.event_name == 'issues' && !github.event.issue.pull_request runs-on: ubuntu-latest + steps: - name: check out code uses: actions/checkout@v4 - - name: Add labels for areas found in bug issue descriptions + - name: Add labels for package found in bug issue descriptions shell: pwsh run: | - .\build\scripts\add-labels.ps1 -issueNumber $env:ISSUE_NUMBER -issueBody $env:ISSUE_BODY + Import-Module .\build\scripts\add-labels.psm1 + + AddLabelsOnIssuesForPackageFoundInBody ` + -issueNumber ${{ github.event.issue.number }} ` + -issueBody $env:ISSUE_BODY env: GH_TOKEN: ${{ github.token }} - ISSUE_NUMBER: ${{ github.event.issue.number }} ISSUE_BODY: ${{ github.event.issue.body }} + + add-labels-on-pull-requests: + if: github.event_name == 'pull_request_target' + + runs-on: ubuntu-latest + + steps: + - name: check out code + uses: actions/checkout@v4 + 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 + + - name: Add labels for files changed on pull requests + shell: pwsh + run: | + Import-Module .\build\scripts\add-labels.psm1 + + AddLabelsOnPullRequestsBasedOnFilesChanged ` + -pullRequestNumber ${{ github.event.pull_request.number }} ` + -labelPackagePrefix 'pkg:' + env: + GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml new file mode 100644 index 0000000000..1385474892 --- /dev/null +++ b/.github/workflows/automation.yml @@ -0,0 +1,34 @@ +name: Resolve automation settings + +on: + workflow_call: + 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 }} + secrets: + OPENTELEMETRYBOT_GITHUB_TOKEN: + required: false + +jobs: + resolve-automation: + + runs-on: ubuntu-latest + + outputs: + enabled: ${{ steps.evaluate.outputs.enabled }} + token-secret-name: ${{ steps.evaluate.outputs.token-secret-name }} + + env: + OPENTELEMETRYBOT_GITHUB_TOKEN_EXISTS: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN != '' }} + + steps: + - id: evaluate + run: | + echo "enabled=${{ env.OPENTELEMETRYBOT_GITHUB_TOKEN_EXISTS == 'true' }}" >> "$GITHUB_OUTPUT" + echo "token-secret-name=OPENTELEMETRYBOT_GITHUB_TOKEN" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1a0d80bac..73ecbd165e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,7 @@ jobs: build: ['build/**', '.github/**/*.yml', '**/*.targets', '**/*.props'] shared: ['src/Shared/**'] code: ['**.cs', '**.csproj', '.editorconfig'] + solution: ['OpenTelemetry.sln'] packaged-code: ['src/**', '!**/*.md'] api-code: ['*/OpenTelemetry.Api*/**', '!**/*.md'] api-packages: ['src/OpenTelemetry.Api*/**', '!**/*.md'] @@ -50,19 +51,30 @@ jobs: || contains(needs.detect-changes.outputs.changes, 'build') uses: ./.github/workflows/dotnet-format.yml - build-test-solution-stable: + build-test-solution: needs: detect-changes if: | - contains(needs.detect-changes.outputs.changes, 'code') + contains(needs.detect-changes.outputs.changes, 'solution') || contains(needs.detect-changes.outputs.changes, 'build') || contains(needs.detect-changes.outputs.changes, 'shared') uses: ./.github/workflows/Component.BuildTest.yml with: project-name: 'OpenTelemetry.sln' + code-cov-name: 'Solution' + + build-test-project-stable: + needs: detect-changes + if: | + contains(needs.detect-changes.outputs.changes, 'code') + || contains(needs.detect-changes.outputs.changes, 'build') + || contains(needs.detect-changes.outputs.changes, 'shared') + uses: ./.github/workflows/Component.BuildTest.yml + with: + project-name: './build/OpenTelemetry.proj' project-build-commands: '-p:ExposeExperimentalFeatures=false' - code-cov-name: 'Solution-Stable' + code-cov-name: 'Project-Stable' - build-test-solution-experimental: + build-test-project-experimental: needs: detect-changes if: | contains(needs.detect-changes.outputs.changes, 'code') @@ -70,9 +82,9 @@ jobs: || contains(needs.detect-changes.outputs.changes, 'shared') uses: ./.github/workflows/Component.BuildTest.yml with: - project-name: 'OpenTelemetry.sln' + project-name: './build/OpenTelemetry.proj' project-build-commands: '-p:ExposeExperimentalFeatures=true' - code-cov-name: 'Solution-Experimental' + code-cov-name: 'Project-Experimental' # Build unstable core libraries using stable packages released to NuGet build-test-unstable-core: @@ -85,7 +97,7 @@ jobs: with: project-name: './build/UnstableCoreLibraries.proj' project-build-commands: '-p:RunningDotNetPack=true -p:ExposeExperimentalFeatures=true' - code-cov-name: 'Unstable-Core' + code-cov-name: 'UnstableCoreLibraries-Experimental' otlp-integration-test: needs: detect-changes @@ -99,7 +111,7 @@ jobs: strategy: fail-fast: false matrix: - version: [ net6.0, net7.0, net8.0 ] + version: [ net8.0, net9.0 ] steps: - uses: actions/checkout@v4 - name: Run OTLP Exporter docker compose @@ -117,7 +129,7 @@ jobs: strategy: fail-fast: false matrix: - version: [ net6.0, net7.0 ] + version: [ net8.0, net9.0 ] steps: - uses: actions/checkout@v4 - name: Run W3C Trace Context docker compose @@ -159,12 +171,13 @@ jobs: build-test: needs: [ - lint-misspell-sanitycheck, detect-changes, + lint-misspell-sanitycheck, lint-md, lint-dotnet-format, - build-test-solution-stable, - build-test-solution-experimental, + build-test-solution, + build-test-project-stable, + build-test-project-experimental, build-test-unstable-core, otlp-integration-test, w3c-trace-context-integration-test, @@ -173,7 +186,8 @@ jobs: verify-aot-compat, concurrency-tests ] - if: always() && !cancelled() && !contains(needs.*.result, 'failure') - runs-on: windows-latest + if: always() && !cancelled() + runs-on: ubuntu-latest steps: - - run: echo 'build complete' + - run: | + if ( ${{ contains(needs.*.result, 'failure') }} == true ); then echo 'build failed'; exit 1; else echo 'build complete'; fi diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c091dc8518..3cdd6a57b6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,8 +39,8 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v4 - - name: dotnet pack OpenTelemetry.proj - run: dotnet pack OpenTelemetry.proj --configuration Release + - name: dotnet pack + run: dotnet pack ./build/OpenTelemetry.proj --configuration Release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/docfx.yml b/.github/workflows/docfx.yml index 4c2031c930..76ab4f2e1e 100644 --- a/.github/workflows/docfx.yml +++ b/.github/workflows/docfx.yml @@ -13,6 +13,9 @@ jobs: - name: check out code uses: actions/checkout@v4 + - name: Setup dotnet + uses: actions/setup-dotnet@v4 + - name: install docfx run: dotnet tool install -g docfx diff --git a/.github/workflows/dotnet-format.yml b/.github/workflows/dotnet-format.yml index b6baf18aab..2ea4834570 100644 --- a/.github/workflows/dotnet-format.yml +++ b/.github/workflows/dotnet-format.yml @@ -17,10 +17,10 @@ jobs: uses: actions/setup-dotnet@v4 - name: dotnet restore - run: dotnet restore + run: dotnet restore OpenTelemetry.sln - name: dotnet format - run: dotnet format OpenTelemetry.sln --no-restore --verify-no-changes + run: dotnet format OpenTelemetry.sln --no-restore --verify-no-changes # Note: .proj files are currently not supported by dotnet format env: ExposeExperimentalFeatures: false @@ -35,9 +35,9 @@ jobs: uses: actions/setup-dotnet@v4 - name: dotnet restore - run: dotnet restore + run: dotnet restore OpenTelemetry.sln - name: dotnet format - run: dotnet format OpenTelemetry.sln --no-restore --verify-no-changes + run: dotnet format OpenTelemetry.sln --no-restore --verify-no-changes # Note: .proj files are currently not supported by dotnet format env: ExposeExperimentalFeatures: true diff --git a/.github/workflows/package-validation.yml b/.github/workflows/package-validation.yml index 757b38c18a..d1f7658ee2 100644 --- a/.github/workflows/package-validation.yml +++ b/.github/workflows/package-validation.yml @@ -20,8 +20,8 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v4 - - name: Pack - run: dotnet pack OpenTelemetry.proj --configuration Release /p:EnablePackageValidation=true /p:ExposeExperimentalFeatures=false /p:RunningDotNetPack=true + - name: dotnet pack + run: dotnet pack ./build/OpenTelemetry.proj --configuration Release /p:EnablePackageValidation=true /p:ExposeExperimentalFeatures=false /p:RunningDotNetPack=true run-package-validation-experimental: runs-on: windows-latest @@ -37,5 +37,5 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v4 - - name: Pack - run: dotnet pack OpenTelemetry.proj --configuration Release /p:EnablePackageValidation=true /p:ExposeExperimentalFeatures=true /p:RunningDotNetPack=true + - name: dotnet pack + run: dotnet pack ./build/OpenTelemetry.proj --configuration Release /p:EnablePackageValidation=true /p:ExposeExperimentalFeatures=true /p:RunningDotNetPack=true diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml new file mode 100644 index 0000000000..249fd360da --- /dev/null +++ b/.github/workflows/post-release.yml @@ -0,0 +1,122 @@ +name: Complete release + +on: + workflow_dispatch: + inputs: + tag: + required: true + description: 'Release tag' + type: string + + release: + types: + - published + + issue_comment: + types: + - created + +jobs: + automation: + uses: ./.github/workflows/automation.yml + secrets: inherit + + push-packages-and-publish-release: + runs-on: ubuntu-latest + + 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, '/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] }} + + steps: + - name: check out code + uses: actions/checkout@v4 + with: + token: ${{ secrets[needs.automation.outputs.token-secret-name] }} + ref: ${{ github.event.repository.default_branch }} + + - name: Push packages and publish release + shell: pwsh + env: + NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }} + run: | + Import-Module .\build\scripts\post-release.psm1 + + 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 != '' }}' + + post-release-published: + runs-on: ubuntu-latest + + 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] }} + + steps: + - uses: actions/checkout@v4 + 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] }} + + - name: Setup dotnet + uses: actions/setup-dotnet@v4 + + - 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 + 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 }}' + + - name: Invoke core version update workflow in opentelemetry-dotnet-contrib repository + if: vars.CONTRIB_REPO + shell: pwsh + run: | + Import-Module .\build\scripts\post-release.psm1 + + InvokeCoreVersionUpdateWorkflowInRemoteRepository ` + -remoteGitRepository '${{ vars.CONTRIB_REPO }}' ` + -tag '${{ inputs.tag || github.ref_name }}' + + - name: Post notice when release is published + shell: pwsh + run: | + Import-Module .\build\scripts\post-release.psm1 + + TryPostReleasePublishedNoticeOnPrepareReleasePullRequest ` + -gitRepository '${{ github.repository }}' ` + -botUserName '${{ needs.automation.outputs.username }}' ` + -tag '${{ inputs.tag || github.ref_name }}' diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index fb54dfd04f..a731bbd405 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -23,19 +23,26 @@ on: types: - created -permissions: - contents: write - pull-requests: write - jobs: + automation: + uses: ./.github/workflows/automation.yml + secrets: inherit + prepare-release-pr: - if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + + needs: automation + + if: github.event_name == 'workflow_dispatch' && needs.automation.outputs.enabled - runs-on: windows-latest + env: + GH_TOKEN: ${{ secrets[needs.automation.outputs.token-secret-name] }} steps: - name: check out code uses: actions/checkout@v4 + with: + token: ${{ secrets[needs.automation.outputs.token-secret-name] }} - name: Create GitHub Pull Request to prepare release shell: pwsh @@ -43,25 +50,35 @@ jobs: Import-Module .\build\scripts\prepare-release.psm1 CreatePullRequestToUpdateChangelogsAndPublicApis ` + -gitRepository '${{ github.repository }}' ` -minVerTagPrefix '${{ inputs.tag-prefix }}' ` -version '${{ inputs.version }}' ` - -targetBranch '${{ github.ref_name }}' - env: - GH_TOKEN: ${{ github.token }} + -requestedByUserName '${{ github.event.sender.login }}' ` + -targetBranch '${{ github.ref_name }}' ` + -gitUserName '${{ needs.automation.outputs.username }}' ` + -gitUserEmail '${{ needs.automation.outputs.email }}' lock-pr-and-post-notice-to-create-release-tag: + runs-on: ubuntu-latest + + needs: automation + if: | github.event_name == 'pull_request' && github.event.action == 'closed' - && github.event.pull_request.user.login == 'github-actions[bot]' + && github.event.pull_request.user.login == needs.automation.outputs.username && github.event.pull_request.merged == true - && startsWith(github.event.pull_request.title, '[repo] Prepare release ') + && startsWith(github.event.pull_request.title, '[release] Prepare release ') + && needs.automation.outputs.enabled - runs-on: windows-latest + env: + GH_TOKEN: ${{ secrets[needs.automation.outputs.token-secret-name] }} steps: - name: check out code uses: actions/checkout@v4 + with: + token: ${{ secrets[needs.automation.outputs.token-secret-name] }} - name: Lock GitHub Pull Request to prepare release shell: pwsh @@ -69,23 +86,27 @@ jobs: Import-Module .\build\scripts\prepare-release.psm1 LockPullRequestAndPostNoticeToCreateReleaseTag ` - -pullRequestNumber '${{ github.event.pull_request.number }}' - env: - GH_TOKEN: ${{ github.token }} + -gitRepository '${{ github.repository }}' ` + -pullRequestNumber '${{ github.event.pull_request.number }}' ` + -botUserName '${{ needs.automation.outputs.username }}' + + create-release-tag-pr-post-notice: + runs-on: ubuntu-latest + + needs: automation - create-release-tag-unlock-pr-post-notice: 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, '[repo] Prepare release ') + && startsWith(github.event.issue.title, '[release] Prepare release ') && github.event.issue.pull_request.merged_at + && needs.automation.outputs.enabled - runs-on: windows-latest - - outputs: - tag: ${{ steps.create-tag.outputs.tag }} + env: + GH_TOKEN: ${{ secrets[needs.automation.outputs.token-secret-name] }} steps: - name: check out code @@ -93,6 +114,7 @@ jobs: 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] }} - name: Create release tag id: create-tag @@ -100,40 +122,50 @@ jobs: run: | Import-Module .\build\scripts\prepare-release.psm1 - $tag = '' - - CreateReleaseTag ` + CreateReleaseTagAndPostNoticeOnPullRequest ` + -gitRepository '${{ github.repository }}' ` -pullRequestNumber '${{ github.event.issue.number }}' ` - -actionRunId '${{ github.run_id }}' ` - -tag ([ref]$tag) - - echo "tag=$tag" >> $env:GITHUB_OUTPUT - env: - GH_TOKEN: ${{ github.token }} - - invoke-package-workflow: - needs: create-release-tag-unlock-pr-post-notice - uses: ./.github/workflows/publish-packages-1.0.yml - with: - tag: ${{ needs.create-release-tag-unlock-pr-post-notice.outputs.tag }} - - post-packages-ready-notice: - needs: - - create-release-tag-unlock-pr-post-notice - - invoke-package-workflow - runs-on: windows-latest + -botUserName '${{ needs.automation.outputs.username }}' ` + -gitUserName '${{ needs.automation.outputs.username }}' ` + -gitUserEmail '${{ needs.automation.outputs.email }}' + + update-changelog-release-dates-on-prepare-pr-post-notice: + runs-on: ubuntu-latest + + 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] }} + steps: - name: check out code uses: actions/checkout@v4 + 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] }} - - name: Post notice when packages are ready + - name: Update release date + id: create-tag shell: pwsh run: | Import-Module .\build\scripts\prepare-release.psm1 - PostPackagesReadyNotice ` + UpdateChangelogReleaseDatesAndPostNoticeOnPullRequest ` + -gitRepository '${{ github.repository }}' ` -pullRequestNumber '${{ github.event.issue.number }}' ` - -tag '${{ needs.create-release-tag-unlock-pr-post-notice.outputs.tag }}' ` - -packagesUrl '${{ needs.invoke-package-workflow.outputs.artifact-url }}' - env: - GH_TOKEN: ${{ github.token }} + -botUserName '${{ needs.automation.outputs.username }}' ` + -commentUserName '${{ github.event.comment.user.login }}' ` + -gitUserName '${{ needs.automation.outputs.username }}' ` + -gitUserEmail '${{ needs.automation.outputs.email }}' + diff --git a/.github/workflows/publish-packages-1.0.yml b/.github/workflows/publish-packages-1.0.yml index dd2e568f2d..df29e3af55 100644 --- a/.github/workflows/publish-packages-1.0.yml +++ b/.github/workflows/publish-packages-1.0.yml @@ -13,30 +13,26 @@ on: tags: - 'core-*' - 'coreunstable-*' - workflow_call: - inputs: - tag: - required: true - type: string - outputs: - artifact-id: - value: ${{ jobs.build-pack-publish.outputs.artifact-id }} - artifact-url: - value: ${{ jobs.build-pack-publish.outputs.artifact-url }} schedule: - cron: '0 0 * * *' # once in a day at 00:00 -permissions: - contents: write - pull-requests: write - jobs: + automation: + uses: ./.github/workflows/automation.yml + secrets: inherit + build-pack-publish: runs-on: windows-latest + permissions: + contents: read + id-token: write + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COSIGN_YES: "yes" outputs: - artifact-id: ${{ steps.upload-artifacts.outputs.artifact-id }} artifact-url: ${{ steps.upload-artifacts.outputs.artifact-url }} + artifact-id: ${{ steps.upload-artifacts.outputs.artifact-id }} steps: - uses: actions/checkout@v4 @@ -45,26 +41,46 @@ jobs: # the version tag which is typically NOT on the first commit so we # retrieve them all. fetch-depth: 0 - ref: ${{ inputs.tag || github.ref || 'main' }} - name: Setup dotnet uses: actions/setup-dotnet@v4 + - name: Install Cosign + uses: sigstore/cosign-installer@v3 + with: + cosign-release: v2.4.0 + - name: dotnet restore - run: dotnet restore OpenTelemetry.proj -p:RunningDotNetPack=true + run: dotnet restore ./build/OpenTelemetry.proj -p:RunningDotNetPack=true - name: dotnet build - run: dotnet build OpenTelemetry.proj --configuration Release --no-restore -p:Deterministic=true -p:BuildNumber=${{ github.run_number }} -p:RunningDotNetPack=true + run: dotnet build ./build/OpenTelemetry.proj --configuration Release --no-restore -p:Deterministic=true -p:BuildNumber=${{ github.run_number }} -p:RunningDotNetPack=true + + - name: Sign DLLs with Cosign Keyless + shell: pwsh + run: | + $projectFiles = Get-ChildItem -Path src/*/*.csproj -File + + foreach ($projectFile in $projectFiles) { + $projectName = [System.IO.Path]::GetFileNameWithoutExtension($projectFile) + + Get-ChildItem -Path src/$projectName/bin/Release/*/$projectName.dll -File | ForEach-Object { + $fileFullPath = $_.FullName + Write-Host "Signing $fileFullPath" + + cosign.exe sign-blob $fileFullPath --yes --output-signature $fileFullPath-keyless.sig --output-certificate $fileFullPath-keyless.pem + } + } - name: dotnet pack - run: dotnet pack OpenTelemetry.proj --configuration Release --no-restore --no-build -p:PackTag=${{ github.ref_type == 'tag' && github.ref_name || inputs.tag || '' }} + run: dotnet pack ./build/OpenTelemetry.proj --configuration Release --no-restore --no-build -p:PackTag=${{ github.ref_type == 'tag' && github.ref_name || '' }} - name: Publish Artifacts id: upload-artifacts uses: actions/upload-artifact@v4 with: - name: ${{ inputs.tag || github.ref_name }}-packages - path: '**/bin/**/*.*nupkg' + name: ${{ github.ref_name }}-packages + path: 'src/**/*.*nupkg' - name: Publish MyGet env: @@ -72,28 +88,56 @@ jobs: 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 **/bin/**/*.nupkg -Source https://www.myget.org/F/opentelemetry/api/v2/package + nuget push src/**/*.nupkg -Source https://www.myget.org/F/opentelemetry/api/v2/package + + post-build: + runs-on: ubuntu-latest + + needs: + - automation + - build-pack-publish + + 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 + with: + token: ${{ secrets[needs.automation.outputs.token-secret-name] }} + + - name: Download Artifacts + run: | + curl \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: token ${{ github.token }}" \ + -L \ + -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" - name: Create GitHub Release draft - if: github.ref_type == 'tag' || inputs.tag shell: pwsh run: | Import-Module .\build\scripts\post-release.psm1 CreateDraftRelease ` - -tag '${{ inputs.tag || github.ref_name }}' - env: - GH_TOKEN: ${{ github.token }} + -gitRepository '${{ github.repository }}' ` + -tag '${{ github.ref_name }}' ` + -releaseFiles '${{ github.workspace }}/artifacts/${{ github.ref_name }}-packages.zip#Packages' - - name: Create GitHub draft 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')) + - name: Post notice when packages are ready shell: pwsh run: | Import-Module .\build\scripts\post-release.psm1 - CreateStableVersionUpdatePullRequest ` - -tag '${{ inputs.tag || github.ref_name }}' - env: - GH_TOKEN: ${{ github.token }} + TryPostPackagesReadyNoticeOnPrepareReleasePullRequest ` + -gitRepository '${{ github.repository }}' ` + -tag '${{ github.ref_name }}' ` + -tagSha '${{ github.sha }}' ` + -packagesUrl '${{ needs.build-pack-publish.outputs.artifact-url }}' ` + -botUserName '${{ needs.automation.outputs.username }}' + + diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index f5657aa9f5..9be0713edb 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,7 +1,7 @@ # Syntax: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions # Github Actions Stale: https://github.com/actions/stale -name: "Close stale pull requests" +name: "Manage stale issues and pull requests" on: schedule: - cron: "12 3 * * *" # arbitrary time not to DDOS GitHub @@ -12,10 +12,14 @@ jobs: steps: - uses: actions/stale@v9 with: - stale-pr-message: 'This PR was marked stale due to lack of activity and will be closed in 7 days. Commenting or Pushing will instruct the bot to automatically remove the label. This bot runs once per day.' + 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.' + stale-pr-message: 'This PR was marked stale due to lack of activity and will be closed in 7 days. Commenting or pushing will instruct the bot to automatically remove the label. This bot runs once per day.' close-pr-message: 'Closed as inactive. Feel free to reopen if this PR is still being worked on.' operations-per-run: 400 days-before-pr-stale: 7 - days-before-issue-stale: -1 + days-before-issue-stale: 300 days-before-pr-close: 7 - days-before-issue-close: -1 + days-before-issue-close: 7 + exempt-all-issue-milestones: true + exempt-issue-labels: needs-triage diff --git a/.github/workflows/verifyaotcompat.yml b/.github/workflows/verifyaotcompat.yml index 24991b9fe3..3bdc42e3e7 100644 --- a/.github/workflows/verifyaotcompat.yml +++ b/.github/workflows/verifyaotcompat.yml @@ -12,7 +12,7 @@ jobs: fail-fast: false # ensures the entire test matrix is run, even if one permutation fails matrix: os: [ ubuntu-latest, windows-latest ] - version: [ net8.0 ] + version: [ net8.0, net9.0 ] runs-on: ${{ matrix.os }} steps: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7a9590b068..02f8d2dd57 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,8 +10,8 @@ for a summary description of past meetings. To request edit access, join the meeting or get in touch on [Slack](https://cloud-native.slack.com/archives/C01N3BC2W7Q). -Even though, anybody can contribute, there are benefits of being a member of our -community. See to the [community membership +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), @@ -19,7 +19,7 @@ on how to become a and [**Maintainer**](https://github.com/open-telemetry/community/blob/main/community-membership.md#maintainer). -## Find a Buddy and Get Started Quickly +## Find a buddy and get started quickly If you are looking for someone to help you find a starting point and be a resource for your first contribution, join our Slack channel and find a buddy! @@ -34,17 +34,24 @@ resource for your first contribution, join our Slack channel and find a buddy! Your OpenTelemetry buddy is your resource to talk to directly on all aspects of contributing to OpenTelemetry: providing context, reviewing PRs, and helping -those get merged. Buddies will not be available 24/7, but is committed to -responding during their normal contribution hours. +those get merged. Buddies will not be available 24/7, but are committed to +responding during their normal working hours. ## Development Environment -You can contribute to this project from a Windows, macOS or Linux machine. +You can contribute to this project from a Windows, macOS, or Linux machine. On all platforms, the minimum requirements are: -* Git client and command line tools. -* .NET 8.0 +* Git client and command line tools + +* [.NET SDK (latest stable version)](https://dotnet.microsoft.com/download) + + > [!NOTE] + > At times a pre-release version of the .NET SDK may be required to build code + in this repository. Check + [global.json](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/global.json) + to verify the current required version. ### Linux or MacOS @@ -59,42 +66,97 @@ of Windows. * Visual Studio 2022+ or Visual Studio Code * .NET Framework 4.6.2+ -### Public API - -It is critical to keep public API surface small and clean. This repository is -using `Microsoft.CodeAnalysis.PublicApiAnalyzers` to validate the public APIs. -This analyzer will check if you changed a public property/method so the change -will be easily spotted in pull request. It will also ensure that OpenTelemetry -doesn't expose APIs outside of the library primary concerns like a generic -helper methods. - -#### How to enable and configure - -* Create a folder in your project called `.publicApi` with the frameworks that - as folders you target. -* Create two files called `PublicAPI.Shipped.txt` and `PublicAPI.Unshipped.txt` - in each framework that you target. -* Add the following lines to your csproj: - -```xml - - - - -``` - -* Use - [IntelliSense](https://docs.microsoft.com/visualstudio/ide/using-intellisense) - to update the publicApi files. +## Public API validation + +It is critical to **NOT** make breaking changes to public APIs which have been +released in stable builds. We also strive to keep a minimal public API surface. +This repository is using +[Microsoft.CodeAnalysis.PublicApiAnalyzers](https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md) +and [Package +validation](https://learn.microsoft.com/dotnet/fundamentals/apicompat/package-validation/overview) +to validate public APIs. + +* `Microsoft.CodeAnalysis.PublicApiAnalyzers` will validate public API + changes/additions against a set of "public API files" which capture the + shipped/unshipped public APIs. These files must be maintained manually (not + recommended) or by using tooling/code fixes built into the package (see below + for details). + + Public API files are also used to perform public API reviews by repo + approvers/maintainers before releasing stable builds. + +* `Package validation` will validate public API changes/additions against + previously released NuGet packages. + + This is performed automatically by the build/CI + [package-validation](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/.github/workflows/package-validation.yml) + workflow. + + By default package validation is **NOT** run for local builds. To enable + package validation in local builds set the `EnablePackageValidation` property + to `true` in + [Common.prod.props](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/build/Common.prod.props) + (please do not check in this change). + +### Working with Microsoft.CodeAnalysis.PublicApiAnalyzers + +#### Update public API files when writing code + +[IntelliSense](https://docs.microsoft.com/visualstudio/ide/using-intellisense) +will [suggest +modifications](https://github.com/dotnet/roslyn-analyzers/issues/3322#issuecomment-591031429) +to the `PublicAPI.Unshipped.txt` file when you make changes. After reviewing +these changes, ensure they are reflected across all targeted frameworks. You can +do this by: + +* Using the "Fix all occurrences in Project" feature in Visual Studio. + +* Manually cycling through each framework using Visual Studio's target framework + dropdown (in the upper right corner) and applying the IntelliSense + suggestions. + +> [!IMPORTANT] +> Do **NOT** modify `PublicAPI.Shipped.txt` files. New features and bug fixes + **SHOULD** only require changes to `PublicAPI.Unshipped.txt` files. If you + have to modify a "shipped" file it likely means you made a mistake and broke a + stable API. Typically only maintainers modify the `PublicAPI.Shipped.txt` file + while performing stable releases. If you need help reach out to an approver or + maintainer on Slack or open a draft PR. + +#### Enable public API validation in new projects + +1. If you are **NOT** using experimental APIs: + * If your API is the same across all target frameworks: + * You only need two files: `.publicApi/PublicAPI.Shipped.txt` and + `.publicApi/PublicAPI.Unshipped.txt`. + * If your APIs differ between target frameworks: + * Place the shared APIs in `.publicApi/PublicAPI.Shipped.txt` and + `.publicApi/PublicAPI.Unshipped.txt`. + * Create framework-specific files for API differences (e.g., + `.publicApi/net462/PublicAPI.Shipped.txt` and + `.publicApi/net462/PublicAPI.Unshipped.txt`). + +2. If you are using experimental APIs: + * Follow the rules above, but create an additional layer in your folder + structure: + * For stable APIs: `.publicApi/Stable/*`. + * For experimental APIs: `.publicApi/Experimental/*`. + * The `Experimental` folder should contain APIs that are public only in + pre-release builds. Typically the `Experimental` folder only contains + `PublicAPI.Unshipped.txt` files as experimental APIs are never shipped + stable. + + Example folder structure can be found + [here](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/src/OpenTelemetry.Api/.publicApi). ## Pull Requests -### How to Send Pull Requests +### How to create pull requests Everyone is welcome to contribute code to `opentelemetry-dotnet` via GitHub pull requests (PRs). -To create a new PR, fork the project in GitHub and clone the upstream repo: +To create a new PR, fork the project on GitHub and clone the upstream repo: ```sh git clone https://github.com/open-telemetry/opentelemetry-dotnet.git @@ -125,7 +187,7 @@ If you made changes to the Markdown documents (`*.md` files), install the latest markdownlint . ``` -Check out a new branch, make modifications and push the branch to your fork: +Check out a new branch, make modifications, and push the branch to your fork: ```sh $ git checkout -b feature @@ -136,20 +198,26 @@ $ git push fork feature Open a pull request against the main `opentelemetry-dotnet` repo. -### How to Receive Comments +#### Tips and best practices for pull requests * If the PR is not ready for review, please mark it as [`draft`](https://github.blog/2019-02-14-introducing-draft-pull-requests/). * Make sure CLA is signed and all required CI checks are clear. -* Submit small, focused PRs addressing a single - concern/issue. +* Submit small, focused PRs addressing a single concern/issue. * Make sure the PR title reflects the contribution. * Write a summary that helps understand the change. * Include usage examples in the summary, where applicable. * Include benchmarks (before/after) in the summary, for contributions that are performance enhancements. +* We are open to bot generated PRs or AI/LLM assisted PRs. Actually, we are + using + [dependabot](https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/configuring-dependabot-security-updates) + to automate the security updates. However, if you use bots to generate spam + PRs (e.g. incorrect, noisy, non-improvements, unintelligible, trying to sell + your product, etc.), we might close the PR right away with a warning, and if + you keep doing so, we might block your user account. -### How to Get PRs Merged +### How to get pull requests merged A PR is considered to be **ready to merge** when: @@ -157,18 +225,21 @@ A PR is considered to be **ready to merge** when: [Approvers](https://github.com/open-telemetry/community/blob/main/community-membership.md#approver). / [Maintainers](https://github.com/open-telemetry/community/blob/main/community-membership.md#maintainer). -* Major feedbacks are resolved. +* Major feedback/comments are resolved. * It has been open for review for at least one working day. This gives people reasonable time to review. -* Trivial change (typo, cosmetic, doc, etc.) doesn't have to wait for one day. -* Urgent fix can take exception as long as it has been actively communicated. + * Trivial change (typo, cosmetic, doc, etc.) doesn't have to wait for one day. + * Urgent fix can take exception as long as it has been actively communicated. -Any Maintainer can merge the PR once it is **ready to merge**. Note, that some -PRs may not be merged immediately if the repo is in the process of a release and -the maintainers decided to defer the PR to the next release train. +Any maintainer can merge PRs once they are **ready to merge** however +maintainers might decide to wait on merging changes until there are more +approvals and/or dicussion, or based on other factors such as release timing and +risk to users. For example if a stable release is planned and a new change is +introduced adding public API(s) or behavioral changes it might be held until the +next alpha/beta release. -If a PR has been stuck (e.g. there are lots of debates and people couldn't agree -on each other), the owner should try to get people aligned by: +If a PR has become stuck (e.g. there is a lot of debate and people couldn't +agree on the direction), the owner should try to get people aligned by: * Consolidating the perspectives and putting a summary in the PR. It is recommended to add a link into the PR description, which points to a comment @@ -183,7 +254,7 @@ on each other), the owner should try to get people aligned by: the owner should bring it to the OpenTelemetry .NET SIG [meeting](README.md#contributing). -## Design Choices +## Design choices As with other OpenTelemetry clients, opentelemetry-dotnet follows the [opentelemetry-specification](https://github.com/open-telemetry/opentelemetry-specification). @@ -191,7 +262,7 @@ As with other OpenTelemetry clients, opentelemetry-dotnet follows the It's especially valuable to read through the [library guidelines](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/library-guidelines.md). -### Focus on Capabilities, Not Structure Compliance +### Focus on capabilities not structural compliance OpenTelemetry is an evolving specification, one where the desires and use cases are clear, but the method to satisfy those uses cases are not. @@ -205,10 +276,10 @@ than conform to specific API names or argument patterns in the spec. For a deeper discussion, see [this spec issue](https://github.com/open-telemetry/opentelemetry-specification/issues/165). -## Style Guide +## Style guide This project includes a [`.editorconfig`](./.editorconfig) file which is -supported by all the IDEs/editor mentioned above. It works with the IDE/editor +supported by all the IDEs/editors mentioned above. It works with the IDE/editor only and does not affect the actual build of the project. This repository also includes StyleCop ruleset files under the `./build` folder. @@ -237,23 +308,6 @@ analysis](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/overview [Common.props](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/build/Common.props) new projects must NOT manually override these settings. -## New code - -New code files MUST enable [nullable reference -types](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/nullable-reference-types) -manually in projects where it is not automatically enabled project-wide. This is -done by specifying `#nullable enable` towards the top of the file (usually after -the copyright header). We are currently working towards enabling nullable -context in every project by updating code as it is worked on, this requirement -is to make sure the surface area of code needing updates is shrinking and not -expanding. - -> [!NOTE] -> The first time a project is updated to use nullable context in public APIs -some housekeeping needs to be done in public API definitions (`.publicApi` -folder). This can be done automatically via a code fix offered by the public API -analyzer. - ## License requirements OpenTelemetry .NET is licensed under the [Apache License, Version diff --git a/Directory.Packages.props b/Directory.Packages.props index 6469f47b89..7678397d0e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,7 +1,19 @@ + true - 1.8.1 + 1.9.0 + + + 9.0.0-rc.1.24431.7 + + + 8.0.0 + 8.0.5 - - - - - - + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + - + - - + + + + + + + + - + + - - + + + - - + - - - - - + + + + + + - - + + - - - - - + + + + + + - - - + + + + - - - - - - - - - - - - - + + + + + + diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index 5150016a12..1b3928c116 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -17,8 +17,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution global.json = global.json LICENSE.TXT = LICENSE.TXT NuGet.config = NuGet.config - OpenTelemetry.proj = OpenTelemetry.proj README.md = README.md + RELEASENOTES.md = RELEASENOTES.md THIRD-PARTY-NOTICES.TXT = THIRD-PARTY-NOTICES.TXT VERSIONING.md = VERSIONING.md EndProjectSection @@ -28,16 +28,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{7CB2F02E build\Common.nonprod.props = build\Common.nonprod.props build\Common.prod.props = build\Common.prod.props build\Common.props = build\Common.props + build\Common.targets = build\Common.targets build\debug.snk = build\debug.snk Directory.Packages.props = Directory.Packages.props build\docfx.cmd = build\docfx.cmd - build\docker-compose.net6.0.yml = build\docker-compose.net6.0.yml - build\docker-compose.net7.0.yml = build\docker-compose.net7.0.yml build\docker-compose.net8.0.yml = build\docker-compose.net8.0.yml + build\docker-compose.net9.0.yml = build\docker-compose.net9.0.yml build\GlobalAttrExclusions.txt = build\GlobalAttrExclusions.txt build\opentelemetry-icon-color.png = build\opentelemetry-icon-color.png build\OpenTelemetry.prod.loose.ruleset = build\OpenTelemetry.prod.loose.ruleset build\OpenTelemetry.prod.ruleset = build\OpenTelemetry.prod.ruleset + build\OpenTelemetry.proj = build\OpenTelemetry.proj build\OpenTelemetry.test.ruleset = build\OpenTelemetry.test.ruleset build\RELEASING.md = build\RELEASING.md build\stylecop.json = build\stylecop.json @@ -80,12 +81,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEM ProjectSection(SolutionItems) = preProject .github\ISSUE_TEMPLATE\bug_report.yml = .github\ISSUE_TEMPLATE\bug_report.yml .github\ISSUE_TEMPLATE\feature_request.yml = .github\ISSUE_TEMPLATE\feature_request.yml - .github\ISSUE_TEMPLATE\question.yml = .github\ISSUE_TEMPLATE\question.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{E69578EB-B456-4062-A645-877CD964528B}" ProjectSection(SolutionItems) = preProject .github\workflows\add-labels.yml = .github\workflows\add-labels.yml + .github\workflows\automation.yml = .github\workflows\automation.yml .github\workflows\ci.yml = .github\workflows\ci.yml .github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml .github\workflows\Component.BuildTest.yml = .github\workflows\Component.BuildTest.yml @@ -94,6 +95,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ .github\workflows\dotnet-format.yml = .github\workflows\dotnet-format.yml .github\workflows\markdownlint.yml = .github\workflows\markdownlint.yml .github\workflows\package-validation.yml = .github\workflows\package-validation.yml + .github\workflows\post-release.yml = .github\workflows\post-release.yml .github\workflows\prepare-release.yml = .github\workflows\prepare-release.yml .github\workflows\publish-packages-1.0.yml = .github\workflows\publish-packages-1.0.yml .github\workflows\sanitycheck.yml = .github\workflows\sanitycheck.yml @@ -111,7 +113,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{D2E73927-5 ProjectSection(SolutionItems) = preProject test\Directory.Build.props = test\Directory.Build.props test\Directory.Build.targets = test\Directory.Build.targets - test\Directory.Packages.props = test\Directory.Packages.props EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.Console", "examples\Console\Examples.Console.csproj", "{FF3E6E08-E8E4-4523-B526-847CD989279F}" @@ -121,14 +122,20 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "test\Benchmarks\Benchmarks.csproj", "{DE9130A4-F30A-49D7-8834-41DE3021218B}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{7C87CAF9-79D7-4C26-9FFB-F3F1FB6911F1}" + ProjectSection(SolutionItems) = preProject + docs\README.md = docs\README.md + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{2C7DD1DA-C229-4D9E-9AF0-BCD5CD3E4948}" ProjectSection(SolutionItems) = preProject examples\Directory.Build.props = examples\Directory.Build.props - examples\Directory.Packages.props = examples\Directory.Packages.props + examples\Directory.Build.targets = examples\Directory.Build.targets EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "trace", "trace", "{5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}" + ProjectSection(SolutionItems) = preProject + docs\trace\README.md = docs\trace\README.md + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "metrics", "metrics", "{3277B1C0-BDFE-4460-9B0D-D9A661FB48DB}" ProjectSection(SolutionItems) = preProject @@ -274,6 +281,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shims", "Shims", "{A0CB9A10-F22D-4E66-A449-74B3D0361A9C}" ProjectSection(SolutionItems) = preProject 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 EndProjectSection EndProject @@ -297,7 +305,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "experimental-apis", "experi ProjectSection(SolutionItems) = preProject docs\diagnostics\experimental-apis\OTEL1000.md = docs\diagnostics\experimental-apis\OTEL1000.md docs\diagnostics\experimental-apis\OTEL1001.md = docs\diagnostics\experimental-apis\OTEL1001.md - docs\diagnostics\experimental-apis\OTEL1003.md = docs\diagnostics\experimental-apis\OTEL1003.md docs\diagnostics\experimental-apis\OTEL1004.md = docs\diagnostics\experimental-apis\OTEL1004.md docs\diagnostics\experimental-apis\README.md = docs\diagnostics\experimental-apis\README.md EndProjectSection @@ -326,7 +333,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TagWriter", "TagWriter", "{ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{44982E0D-C8C6-42DC-9F8F-714981F27CE6}" ProjectSection(SolutionItems) = preProject - build\scripts\add-labels.ps1 = build\scripts\add-labels.ps1 + build\scripts\add-labels.psm1 = build\scripts\add-labels.psm1 build\scripts\finalize-publicapi.ps1 = build\scripts\finalize-publicapi.ps1 build\scripts\post-release.psm1 = build\scripts\post-release.psm1 build\scripts\prepare-release.psm1 = build\scripts\prepare-release.psm1 @@ -336,6 +343,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{4498 build\scripts\update-changelogs.ps1 = build\scripts\update-changelogs.ps1 EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "exemplars", "docs\metrics\exemplars\exemplars.csproj", "{79C12C80-B27B-41FB-AE79-A3BB74CFA782}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -590,6 +599,10 @@ Global {19545B37-8518-4BDD-AD49-00C031FB3C2A}.Debug|Any CPU.Build.0 = Debug|Any CPU {19545B37-8518-4BDD-AD49-00C031FB3C2A}.Release|Any CPU.ActiveCfg = Release|Any CPU {19545B37-8518-4BDD-AD49-00C031FB3C2A}.Release|Any CPU.Build.0 = Release|Any CPU + {79C12C80-B27B-41FB-AE79-A3BB74CFA782}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79C12C80-B27B-41FB-AE79-A3BB74CFA782}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79C12C80-B27B-41FB-AE79-A3BB74CFA782}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79C12C80-B27B-41FB-AE79-A3BB74CFA782}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -647,6 +660,7 @@ Global {87A20A76-D524-4AAC-AF92-8725BFED0415} = {A49299FB-C5CD-4E0E-B7E1-B7867BBD67CC} {993E65E5-E71B-40FD-871C-60A9EBD59724} = {A49299FB-C5CD-4E0E-B7E1-B7867BBD67CC} {44982E0D-C8C6-42DC-9F8F-714981F27CE6} = {7CB2F02E-03FA-4FFF-89A5-C51F107623FD} + {79C12C80-B27B-41FB-AE79-A3BB74CFA782} = {3277B1C0-BDFE-4460-9B0D-D9A661FB48DB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521} diff --git a/README.md b/README.md index 8a2695da6d..f0c69d1e1e 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,27 @@ [![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) -The .NET [OpenTelemetry](https://opentelemetry.io/) client. +The .NET [OpenTelemetry](https://opentelemetry.io/) implementation. -## Supported .NET Versions +
+Table of Contents + +* [Supported .NET versions](#supported-net-versions) +* [Project status](#project-status) +* [Getting started](#getting-started) + * [Getting started with Logging](#getting-started-with-logging) + * [Getting started with Metrics](#getting-started-with-metrics) + * [Getting started with Tracing](#getting-started-with-tracing) +* [Repository structure](#repository-structure) +* [Troubleshooting](#troubleshooting) +* [Extensibility](#extensibility) +* [Releases](#releases) +* [Contributing](#contributing) +* [References](#references) + +
+ +## Supported .NET versions Packages shipped from this repository generally support all the officially supported versions of [.NET](https://dotnet.microsoft.com/download/dotnet) and @@ -17,36 +35,88 @@ older Windows-based .NET implementation), except `.NET Framework 3.5`. Any exceptions to this are noted in the individual `README.md` files. -## Project Status +## Project status + +**Stable** across all 3 signals (`Logs`, `Metrics`, and `Traces`). -**Stable** across all 3 signals i.e. `Logs`, `Metrics`, and `Traces`. +> [!CAUTION] +> Certain components, marked as +[pre-release](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/VERSIONING.md#pre-releases), +are still work in progress and can undergo breaking changes before stable +release. Check the individual `README.md` file for each component to understand its +current state. -See [Spec Compliance -Matrix](https://github.com/open-telemetry/opentelemetry-specification/blob/main/spec-compliance-matrix.md) -to understand which portions of the specification has been implemented in this -repo. +To understand which portions of the [OpenTelemetry +Specification](https://github.com/open-telemetry/opentelemetry-specification) +have been implemented in OpenTelemetry .NET see: [Spec Compliance +Matrix](https://github.com/open-telemetry/opentelemetry-specification/blob/main/spec-compliance-matrix.md). -## Getting Started +## Getting started If you are new here, please read the getting started docs: -* [Logs](./docs/logs/README.md) -* [Metrics](./docs/metrics/README.md) -* [Traces](./docs/trace/README.md) - -This repository includes multiple installable components, available on -[NuGet](https://www.nuget.org/profiles/OpenTelemetry). Each component has its -individual `README.md` file, which covers the instruction on how to install and -how to get started. To find all the available components, please take a look at -the `src` folder. +### Getting started with Logging + +If you are new to +[logging](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/README.md), +it is recommended to first follow the [getting started in 5 minutes - ASP.NET +Core Application](./docs/logs/getting-started-aspnetcore/README.md) guide or +the [getting started in 5 minutes - Console +Application](./docs/logs/getting-started-console/README.md) guide to get up +and running. + +For general information and best practices see: [OpenTelemetry .NET +Logs](./docs/logs/README.md). For a more detailed explanation of SDK logging +features see: [Customizing OpenTelemetry .NET SDK for +Logs](./docs/logs/customizing-the-sdk/README.md). + +### Getting started with Metrics + +If you are new to +[metrics](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/README.md), +it is recommended to first follow the [getting started in 5 minutes - ASP.NET +Core Application](./docs/metrics/getting-started-aspnetcore/README.md) guide +or the [getting started in 5 minutes - Console +Application](./docs/metrics/getting-started-console/README.md) guide to get +up and running. + +For general information and best practices see: [OpenTelemetry .NET +Metrics](./docs/metrics/README.md). For a more detailed explanation of SDK +metric features see: [Customizing OpenTelemetry .NET SDK for +Metrics](./docs/metrics/customizing-the-sdk/README.md). + +### Getting started with Tracing + +If you are new to +[traces](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/README.md), +it is recommended to first follow the [getting started in 5 minutes - ASP.NET +Core Application](./docs/trace/getting-started-aspnetcore/README.md) guide +or the [getting started in 5 minutes - Console +Application](./docs/trace/getting-started-console/README.md) guide to get up +and running. + +For general information and best practices see: [OpenTelemetry .NET +Traces](./docs/trace/README.md). For a more detailed explanation of SDK tracing +features see: [Customizing OpenTelemetry .NET SDK for +Tracing](./docs/trace/customizing-the-sdk/README.md). + +## Repository structure + +This repository includes only what is defined in the [OpenTelemetry +Specification](https://github.com/open-telemetry/opentelemetry-specification) +and is shipped as separate packages through +[NuGet](https://www.nuget.org/profiles/OpenTelemetry). Each component has an +individual `README.md` and `CHANGELOG.md` file which covers the instructions on +how to install and get started, and details about the individual changes made +(respectively). To find all the available components, please take a look at the +`src` folder. Here are the most commonly used components: -* [OpenTelemetry .NET API](./src/OpenTelemetry.Api/README.md) -* [OpenTelemetry .NET SDK](./src/OpenTelemetry/README.md) - -[Instrumentation libraries](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library) -can be found in [contrib repository](https://github.com/open-telemetry/opentelemetry-dotnet-contrib). +* [OpenTelemetry API](./src/OpenTelemetry.Api/README.md) +* [OpenTelemetry SDK](./src/OpenTelemetry/README.md) +* [OpenTelemetry Hosting + Extensions](./src/OpenTelemetry.Extensions.Hosting/README.md) Here are the [exporter libraries](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#exporter-library): @@ -59,16 +129,19 @@ libraries](https://github.com/open-telemetry/opentelemetry-specification/blob/ma * [Prometheus HttpListener](./src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md) * [Zipkin](./src/OpenTelemetry.Exporter.Zipkin/README.md) -See the [OpenTelemetry -registry](https://opentelemetry.io/ecosystem/registry/?language=dotnet) and -[OpenTelemetry .NET Contrib -repo](https://github.com/open-telemetry/opentelemetry-dotnet-contrib) for more -components. +Additional packages including [instrumentation +libraries](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library), +exporters, resource detectors, and extensions can be found in the +[opentelemetry-dotnet-contrib +repository](https://github.com/open-telemetry/opentelemetry-dotnet-contrib) +and/or the [OpenTelemetry +registry](https://opentelemetry.io/ecosystem/registry/?language=dotnet). ## Troubleshooting -See [Troubleshooting](./src/OpenTelemetry/README.md#troubleshooting). -Additionally check readme file for the individual components for any additional +For general instructions see: +[Troubleshooting](./src/OpenTelemetry/README.md#troubleshooting). Additionally +`README.md` files for individual components may contain more detailed troubleshooting information. ## Extensibility @@ -80,7 +153,7 @@ extension scenarios: library](./docs/trace/extending-the-sdk/README.md#instrumentation-library). * Building a custom exporter for [logs](./docs/logs/extending-the-sdk/README.md#exporter), - [metrics](./docs/metrics/extending-the-sdk/README.md#exporter) and + [metrics](./docs/metrics/extending-the-sdk/README.md#exporter), and [traces](./docs/trace/extending-the-sdk/README.md#exporter). * Building a custom processor for [logs](./docs/logs/extending-the-sdk/README.md#processor) and @@ -88,14 +161,33 @@ extension scenarios: * Building a custom sampler for [traces](./docs/trace/extending-the-sdk/README.md#sampler). +## Releases + +For details about upcoming planned releases see: +[Milestones](https://github.com/open-telemetry/opentelemetry-dotnet/milestones). +The dates and features described in issues and milestones are estimates and +subject to change. + +For highlights and annoucements for stable releases see: [Release +Notes](./RELEASENOTES.md). + +To access packages, source code, and/or view a list of changes for all +components in a release see: +[Releases](https://github.com/open-telemetry/opentelemetry-dotnet/releases). + +Nightly builds from this repo are published to [MyGet](https://www.myget.org), +and can be installed using the +`https://www.myget.org/F/opentelemetry/api/v3/index.json` source. + ## Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md) +For information about contributing to the project see: +[CONTRIBUTING.md](CONTRIBUTING.md). We meet weekly on Tuesdays, and the time of the meeting alternates between 9AM PT and 4PM PT. The meeting is subject to change depending on contributors' availability. Check the [OpenTelemetry community -calendar](https://calendar.google.com/calendar/embed?src=google.com_b79e3e90j7bbsa2n2p5an5lf60%40group.calendar.google.com) +calendar](https://github.com/open-telemetry/community?tab=readme-ov-file#calendar) for specific dates and for Zoom meeting links. Meeting notes are available as a public [Google @@ -108,28 +200,28 @@ 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/community-membership.md#maintainer) +[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)): * [Alan West](https://github.com/alanwest), New Relic * [Mikel Blanchard](https://github.com/CodeBlanch), Microsoft -[Approvers](https://github.com/open-telemetry/community/blob/main/community-membership.md#approver) +[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)): * [Cijo Thomas](https://github.com/cijothomas), Microsoft +* [Piotr Kiełkowicz](https://github.com/Kielek), Splunk * [Reiley Yang](https://github.com/reyang), Microsoft * [Utkarsh Umesan Pillai](https://github.com/utpilla), Microsoft -* [Vishwesh Bankwar](https://github.com/vishweshbankwar), Microsoft -[Triagers](https://github.com/open-telemetry/community/blob/main/community-membership.md#triager) +[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 -* [Piotr Kiełkowicz](https://github.com/Kielek), Splunk +* [Timothy "Mothra" Lee](https://github.com/TimothyMothra), Microsoft [Emeritus -Maintainer/Approver/Triager](https://github.com/open-telemetry/community/blob/main/community-membership.md#emeritus-maintainerapprovertriager): +Maintainer/Approver/Triager](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager): * [Bruno Garcia](https://github.com/bruno-garcia) * [Eddy Nakamura](https://github.com/eddynaka) @@ -139,28 +231,13 @@ Maintainer/Approver/Triager](https://github.com/open-telemetry/community/blob/ma * [Robert Pająk](https://github.com/pellared) * [Sergey Kanzhelev](https://github.com/SergeyKanzhelev) * [Victor Lu](https://github.com/victlu) +* [Vishwesh Bankwar](https://github.com/vishweshbankwar) ### 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) -## Release Schedule - -See the [project -milestones](https://github.com/open-telemetry/opentelemetry-dotnet/milestones) -for details on upcoming releases. The dates and features described in issues and -milestones are estimates, and subject to change. - -See the [release -notes](https://github.com/open-telemetry/opentelemetry-dotnet/releases) for -existing releases. - -> [!CAUTION] -> Certain components, marked as -[pre-release](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/VERSIONING.md#pre-releases), -are still work in progress and can undergo breaking changes before stable -release. Check the individual `README.md` file for each component to understand its -current state. +## References -Daily builds from this repo are published to MyGet, and can be installed from -[this source](https://www.myget.org/F/opentelemetry/api/v3/index.json). +* [OpenTelemetry Project](https://opentelemetry.io/) +* [OpenTelemetry Specification](https://github.com/open-telemetry/opentelemetry-specification) diff --git a/RELEASENOTES.md b/RELEASENOTES.md new file mode 100644 index 0000000000..365d9dc876 --- /dev/null +++ b/RELEASENOTES.md @@ -0,0 +1,88 @@ +# Release Notes + +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.10.0 + +* Bumped the package versions of `System.Diagnostic.DiagnosticSource` and other + Microsoft.Extensions.* packages to `9.0.0`. + +* Added support for new APIs introduced in `System.Diagnostics.DiagnosticSource` + `9.0.0`: + + * [InstrumentAdvice<T>](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.instrumentadvice-1) + + * [Gauge<T>](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.gauge-1) + + * [ActivitySource.Tags](https://learn.microsoft.com/dotnet/api/system.diagnostics.activitysource.tags) + (supported in OtlpExporter & ConsoleExporter) + +* Experimental features promoted to stable: + + * `CardinalityLimit` can now be managed for individual metrics via the View + API. For details see: [Changing cardinality limit for a + Metric](./docs/metrics/customizing-the-sdk/README.md#changing-the-cardinality-limit-for-a-metric). + + * The [overflow + attribute](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#overflow-attribute) + (`otel.metric.overflow`) behavior is now enabled by default. The + `OTEL_DOTNET_EXPERIMENTAL_METRICS_EMIT_OVERFLOW_ATTRIBUTE` environment + variable is no longer required. For details see: [Cardinality + Limits](./docs/metrics/README.md#cardinality-limits). + +* Added `OpenTelemetrySdk.Create` API for configuring OpenTelemetry .NET signals + (logging, tracing, and metrics) via a single builder. This new API simplifies + bootstrap and teardown, and supports cross-cutting extensions targeting + `IOpenTelemetryBuilder`. + +* Removed out of support `net6.0` target and added `net9.0` target. + +## 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). + +* `WithLogging` is now part of the stable API! Logging, Metrics, and Tracing can + now all be configured using the `With` style and the builders finally have + parity in their APIs. + +## 1.8.0 + +* `TracerProvider` sampler can now be configured via the `OTEL_TRACES_SAMPLER` & + `OTEL_TRACES_SAMPLER_ARG` envvars. + +* A new `UseOtlpExporter` cross-cutting extension has been added to register the + `OtlpExporter` and enable all signals in a single call. + +* `exception.type`, `exception.message`, `exception.stacktrace` will now + automatically be included by the `OtlpLogExporter` when logging exceptions. + Previously an experimental environment variable had to be set. + +## 1.7.0 + +* Bumped the package versions of System.Diagnostic.DiagnosticSource and other + Microsoft.Extensions.* packages to `8.0.0`. + +* Added `net8.0` targets to all the components. + +* OTLP Exporter + * Updated to use `ILogger` `CategoryName` as the instrumentation scope for + logs. + * Added named options support for OTLP Log Exporter. + * Added support for instrumentation scope attributes in metrics. + * Added support under an experimental flag to emit log exception attributes. + * Added support under an experimental flag to emit log eventId and eventName. + attributes. + +* Added support for the + [IMetricsBuilder](https://learn.microsoft.com/dotnet/api/microsoft.extensions.diagnostics.metrics.imetricsbuilder) + API. + +* Added an experimental opt-in metrics feature to reclaim unused MetricPoints + which enables a higher number of unique dimension combinations to be emitted. + See [reclaim unused metric + points](https://github.com/open-telemetry/opentelemetry-dotnet/blob/32c64d04defb5c92d056fd8817638151168b10da/docs/metrics/README.md#cardinality-limits) + for more details. diff --git a/build/Common.nonprod.props b/build/Common.nonprod.props index 7f617c4549..ce99895ccb 100644 --- a/build/Common.nonprod.props +++ b/build/Common.nonprod.props @@ -8,7 +8,7 @@ - net8.0 + net9.0 @@ -44,4 +44,24 @@ + + + + + + + + + + $(DefineConstants);@(CompilerConstantsForDependenciesWithExposedExperimentalFeatures) + + diff --git a/build/Common.prod.props b/build/Common.prod.props index 011f7d9b31..832306e140 100644 --- a/build/Common.prod.props +++ b/build/Common.prod.props @@ -3,6 +3,9 @@ $(MSBuildThisFileDirectory)/OpenTelemetry.prod.ruleset + + + true false @@ -22,6 +25,9 @@ true $(RepoRoot)\LICENSE.TXT $(RepoRoot)\THIRD-PARTY-NOTICES.TXT + README.md + CHANGELOG.md + $(RepoRoot)\RELEASENOTES.md @@ -30,6 +36,13 @@ true + + + + + @@ -39,11 +52,28 @@ + - + + + + + + + + + + + + + + + + + + + + + + + + + + + \[([^]]+?)\]\(\.(.+?)\) + $(GitOriginConsoleOutput.Replace('.git','')) + $(GitHubRepoUrl)/blob/$(PackTag) + $(GitHubRepoUrl)/blob/$(GitCommitConsoleOutput) + + + + + + + + + + + + + + + + <_PackageReleaseNotesFilePath>$([System.IO.Path]::GetFullPath('$(PackageReleaseNotesFile)').Replace('$(RepoRoot)', '').Replace('\', '/')) + <_PackageChangelogFilePath>$([System.IO.Path]::GetFullPath('$(PackageChangelogFile)').Replace('$(RepoRoot)', '').Replace('\', '/')) + + For highlights and announcements see: $(GitHubPermalinkUrl)$(_PackageReleaseNotesFilePath). + + For detailed changes see: $(GitHubPermalinkUrl)$(_PackageChangelogFilePath). + + + + + + + + + + diff --git a/build/Common.props b/build/Common.props index 7c1c5b5a47..c1a4874c57 100644 --- a/build/Common.props +++ b/build/Common.props @@ -9,30 +9,38 @@ true enable enable + true + all + low - $(NoWarn);OTEL1000;OTEL1001;OTEL1002;OTEL1003;OTEL1004 + $(NoWarn);OTEL1000;OTEL1001;OTEL1002;OTEL1004 + + false + + net462 net481;net48;net472;net471;net47;net462 - net8.0;net6.0;netstandard2.0;$(NetFrameworkMinimumSupportedVersion) - net8.0;net6.0;netstandard2.1;netstandard2.0;$(NetFrameworkMinimumSupportedVersion) - net8.0;net6.0 + net9.0;net8.0;netstandard2.0;$(NetFrameworkMinimumSupportedVersion) + net9.0;net8.0;netstandard2.1;netstandard2.0;$(NetFrameworkMinimumSupportedVersion) + net9.0;net8.0 + net8.0;netstandard2.1;netstandard2.0;$(NetFrameworkMinimumSupportedVersion) - net8.0;net7.0;net6.0 - net8.0 - net8.0;net7.0;net6.0 + net9.0;net8.0 + net9.0;net8.0 + net9.0;net8.0 $(TargetFrameworksForDocs);$(NetFrameworkSupportedVersions) - net8.0;net7.0;net6.0 + net9.0;net8.0 $(TargetFrameworksForTests);$(NetFrameworkMinimumSupportedVersion) diff --git a/build/Common.targets b/build/Common.targets new file mode 100644 index 0000000000..63286efffb --- /dev/null +++ b/build/Common.targets @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/OpenTelemetry.proj b/build/OpenTelemetry.proj similarity index 61% rename from OpenTelemetry.proj rename to build/OpenTelemetry.proj index b55859fe01..86987bbbe2 100644 --- a/OpenTelemetry.proj +++ b/build/OpenTelemetry.proj @@ -1,16 +1,18 @@ + + $([System.IO.Directory]::GetParent($(MSBuildThisFileDirectory)).Parent.FullName) + + - - - + + + - - - + + + diff --git a/build/RELEASING.md b/build/RELEASING.md index 035aad38d6..28fdc9a078 100644 --- a/build/RELEASING.md +++ b/build/RELEASING.md @@ -1,6 +1,7 @@ # Release process -**Only for Maintainers.** +**Note: Approvers (collaborators) can perform much of the release process but +Maintainers (admins) are needed to merge PRs and for the push to NuGet.** 1. Decide the component(s) and tag name (version name) to be released. We use [MinVer](https://github.com/adamralph/minver) to do versioning, which @@ -40,15 +41,13 @@ * `OpenTelemetry.Shims.OpenTracing` - Defined by spec (stable but incomplete implementation) - * As of the `1.9.0` release cycle instrumentation packages and core - unstable packages always depend on the stable versions of core - packages. Before releasing a non-core component ensure the - `OTelLatestStableVer` property in `Directory.Packages.props` has been - updated to the latest stable core version. + * As of the `1.9.0` release cycle core unstable packages always depend on + the stable versions of core packages. Before releasing a non-core + component ensure the `OTelLatestStableVer` property in + `Directory.Packages.props` has been updated to the latest stable core + version. - 2. Prepare for release - - Run the [Prepare for a + 2. Run the [Prepare for a release](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/prepare-release.yml) workflow. Specify the `tag-prefix` and the `version` for the release. Make sure to run the workflow on the branch being released. This is typically @@ -91,11 +90,10 @@ comment and lock the PR. Post a comment with "/CreateReleaseTag" in the body. This will tell the [Prepare for a release](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/prepare-release.yml) - workflow to push the tag for the merge commit of the PR and to call the - [Build, pack, and publish to + workflow to push the tag for the merge commit of the PR which will trigger + the [Build, pack, and publish to MyGet](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/publish-packages-1.0.yml) - workflow. Once packages are available a comment will be posted on the PR - opened in step 2 with a link to the artifacts. + workflow.
Instructions for pushing tags manually @@ -127,56 +125,85 @@ 5. :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. + workflow to complete. When complete a trigger will automatically add a + comment on the PR opened by [Prepare for a + release](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/prepare-release.yml) + workflow in step 2. Use MyGet or download the packages using the provided + link to validate locally everything works. After validation has been + performed have a maintainer post a comment with "/PushPackages" in the body. + This will trigger the [Complete + release](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/prepare-release.yml) + workflow to push the packages to NuGet and publish the draft release created + by the [Build, pack, and publish to + MyGet](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/publish-packages-1.0.yml) + workflow. Comments will automatically be added on the PR opened by [Prepare + for a + release](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/prepare-release.yml) + workflow in step 2 as the process is run and the PR will be unlocked. - 6. Validate locally everything works using the MyGet packages pushed from the - release. Basic sanity checks :) +
+ Instructions for pushing packages to NuGet manually - 7. Download the artifacts from the drop attached to the workflow run. The - artifacts archive (`.zip`) contains all the NuGet packages (`.nupkg`) and - symbols (`.snupkg`) from the build which were pushed to MyGet. + 1. The [Build, pack, and publish to + MyGet](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/publish-packages-1.0.yml) + workflow pushes the packages to MyGet and attaches them as artifacts on + the workflow run. - 8. Extract the artifacts from the archive (`.zip`) into a local folder. + 2. Validate locally everything works using the packages pushed to MyGet or + downloaded from the drop attached to the workflow run. Basic sanity + checks :) - 9. Download latest [nuget.exe](https://www.nuget.org/downloads) into the same - folder from Step 8. + 3. Download the artifacts from the drop attached to the workflow run. The + artifacts archive (`.zip`) contains all the NuGet packages (`.nupkg`) and + symbols (`.snupkg`) from the build which were pushed to MyGet. -10. Create or regenerate an API key from nuget.org (only maintainers have - access). When creating API keys make sure it is set to expire in 1 day or - less. + 4. Extract the artifacts from the archive (`.zip`) into a local folder. -11. Run the following commands from PowerShell from local folder used in Step 8: + 5. Download latest [nuget.exe](https://www.nuget.org/downloads) into the + same folder from step 4. - ```powershell - .\nuget.exe setApiKey + 6. Create or regenerate an API key from nuget.org (only maintainers have + access). When creating API keys make sure it is set to expire in 1 day or + less. - get-childitem -Recurse | where {$_.extension -eq ".nupkg"} | foreach ($_) {.\nuget.exe push $_.fullname -Source https://api.nuget.org/v3/index.json} - ``` + 7. Run the following commands from PowerShell from local folder used in step + 4: -12. Validate that the package(s) are uploaded. Packages are available - immediately to maintainers on nuget.org but aren't publicly visible until - scanning completes. This process usually takes a few minutes. + ```powershell + .\nuget.exe setApiKey -13. Open the - [Releases](https://github.com/open-telemetry/opentelemetry-dotnet/releases) - page on the GitHub repository. The [Build, pack, and publish to - MyGet](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/publish-packages-1.0.yml) - workflow creates a draft release for the tag which was pushed. Edit the - draft Release and click `Publish release`. + get-childitem -Recurse | where {$_.extension -eq ".nupkg"} | foreach ($_) {.\nuget.exe push $_.fullname -Source https://api.nuget.org/v3/index.json} + ``` -14. If a new stable version of the core packages was released, a draft PR should - have been automatically created by the [Build, pack, and publish to - MyGet](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/publish-packages-1.0.yml) + 8. Validate that the package(s) are uploaded. Packages are available + immediately to maintainers on nuget.org but aren't publicly visible until + scanning completes. This process usually takes a few minutes. + + 9. Open the + [Releases](https://github.com/open-telemetry/opentelemetry-dotnet/releases) + page on the GitHub repository. The [Build, pack, and publish to + MyGet](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/publish-packages-1.0.yml) + workflow creates a draft release for the tag which was pushed. Edit the + draft Release and click `Publish release`. +
+ + 6. 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. Mark that PR - `Ready for review` and then merge it once the build passes (this requires - the packages be available on NuGet). - -15. If a new stable version of the core packages was released, open an issue in - the - [opentelemetry-dotnet-contrib](https://github.com/open-telemetry/opentelemetry-dotnet-contrib) - repo to notify maintainers to begin upgrading dependencies. - -16. Post an announcement in the [Slack - channel](https://cloud-native.slack.com/archives/C01N3BC2W7Q). Note any big - or interesting new features as part of the announcement. + `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 + 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) + workflow on the + [opentelemetry-dotnet-contrib](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/) + 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. diff --git a/build/UnstableCoreLibraries.proj b/build/UnstableCoreLibraries.proj index 8d333db2d9..7686376563 100644 --- a/build/UnstableCoreLibraries.proj +++ b/build/UnstableCoreLibraries.proj @@ -1,12 +1,16 @@ + + $([System.IO.Directory]::GetParent($(MSBuildThisFileDirectory)).Parent.FullName) + + - - - + + + - - - + + + diff --git a/build/docker-compose.net6.0.yml b/build/docker-compose.net6.0.yml deleted file mode 100644 index 099f100727..0000000000 --- a/build/docker-compose.net6.0.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: '3.7' - -services: - tests: - build: - args: - PUBLISH_FRAMEWORK: net6.0 - TEST_SDK_VERSION: "6.0" - BUILD_SDK_VERSION: "8.0" diff --git a/build/docker-compose.net7.0.yml b/build/docker-compose.net7.0.yml deleted file mode 100644 index 48a2589cda..0000000000 --- a/build/docker-compose.net7.0.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: '3.7' - -services: - tests: - build: - args: - PUBLISH_FRAMEWORK: net7.0 - TEST_SDK_VERSION: "7.0" - BUILD_SDK_VERSION: "8.0" diff --git a/build/docker-compose.net8.0.yml b/build/docker-compose.net8.0.yml index a5ac999e43..22787bff66 100644 --- a/build/docker-compose.net8.0.yml +++ b/build/docker-compose.net8.0.yml @@ -6,4 +6,4 @@ services: args: PUBLISH_FRAMEWORK: net8.0 TEST_SDK_VERSION: "8.0" - BUILD_SDK_VERSION: "8.0" + BUILD_SDK_VERSION: "9.0" diff --git a/build/docker-compose.net9.0.yml b/build/docker-compose.net9.0.yml new file mode 100644 index 0000000000..29663b3246 --- /dev/null +++ b/build/docker-compose.net9.0.yml @@ -0,0 +1,9 @@ +version: '3.7' + +services: + tests: + build: + args: + PUBLISH_FRAMEWORK: net9.0 + TEST_SDK_VERSION: "9.0" + BUILD_SDK_VERSION: "9.0" diff --git a/build/scripts/add-labels.ps1 b/build/scripts/add-labels.ps1 deleted file mode 100644 index 9b91ad9df8..0000000000 --- a/build/scripts/add-labels.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -param( - [Parameter(Mandatory=$true)][int]$issueNumber, - [Parameter(Mandatory=$true)][string]$issueBody -) - -$match = [regex]::Match($issueBody, '^[#]+ Package\s*(OpenTelemetry(?:\.\w+)*)') -if ($match.Success -eq $false) -{ - Return -} - -gh issue edit $issueNumber --add-label $("pkg:" + $match.Groups[1].Value) diff --git a/build/scripts/add-labels.psm1 b/build/scripts/add-labels.psm1 new file mode 100644 index 0000000000..1b14854adb --- /dev/null +++ b/build/scripts/add-labels.psm1 @@ -0,0 +1,148 @@ +function AddLabelsOnIssuesForPackageFoundInBody { + param( + [Parameter(Mandatory=$true)][int]$issueNumber, + [Parameter(Mandatory=$true)][string]$issueBody + ) + + $match = [regex]::Match($issueBody, '^[#]+ Package\s*(OpenTelemetry(?:\.\w+)*)') + if ($match.Success -eq $false) + { + Return + } + + gh issue edit $issueNumber --add-label $("pkg:" + $match.Groups[1].Value) +} + +Export-ModuleMember -Function AddLabelsOnIssuesForPackageFoundInBody + +function AddLabelsOnPullRequestsBasedOnFilesChanged { + param( + [Parameter(Mandatory=$true)][int]$pullRequestNumber, + [Parameter(Mandatory=$true)][string]$labelPackagePrefix # 'pkg:' on main repo, 'comp:' on contrib repo + ) + + # Note: This function is intended to work on main repo and on contrib. Please + # keep them in sync. + + $repoLabels = gh label list --json name,id -L 200 | ConvertFrom-Json + + $filesChangedOnPullRequest = gh pr diff $pullRequestNumber --name-only + + $labelsOnPullRequest = (gh pr view $pullRequestNumber --json labels | ConvertFrom-Json).labels + + $visitedProjects = New-Object System.Collections.Generic.HashSet[string] + $labelsToAdd = New-Object System.Collections.Generic.HashSet[string] + $labelsToRemove = New-Object System.Collections.Generic.HashSet[string] + + # Note: perf label may be added but it is kind of a guess so we don't remove + # it automatically in order to also allow manual inclusion after reviewing files + $managedLabels = 'infra', 'documentation', 'dependencies' + $rootInfraFiles = 'global.json', 'NuGet.config', 'codeowners' + $documentationFiles = 'readme.md', 'contributing.md', 'releasing.md', 'versioning.md', 'releasenotes.md' + + foreach ($fileChanged in $filesChangedOnPullRequest) + { + $fileChanged = $fileChanged.ToLower() + $fullFileName = [System.IO.Path]::GetFileName($fileChanged) + $fileName = [System.IO.Path]::GetFileNameWithoutExtension($fileChanged) + $fileExtension = [System.IO.Path]::GetExtension($fileChanged) + + if ($fileChanged.StartsWith('src/') -or $fileChanged.StartsWith('test/')) + { + $match = [regex]::Match($fileChanged, '^(?:(?:src)|(?:test))\/(.*?)\/.+$') + if ($match.Success -eq $false) + { + continue + } + $rawProjectName = $match.Groups[1].Value + if ($rawProjectName.Contains(".benchmarks") -or $rawProjectName.Contains(".stress")) + { + $added = $labelsToAdd.Add("perf") + } + + $projectName = $rawProjectName.Replace(".tests", "").Replace(".benchmarks", "").Replace(".stress", "") + if ($visitedProjects.Contains($projectName)) + { + continue + } + + $added = $visitedProjects.Add($projectName); + + foreach ($repoLabel in $repoLabels) + { + if ($repoLabel.name.StartsWith($labelPackagePrefix)) + { + $package = $repoLabel.name.Substring($labelPackagePrefix.Length).ToLower() + if ($package.StartsWith('opentelemetry') -eq $false) + { + # Note: On contrib labels don't have "OpenTelemetry." prefix + $package = 'opentelemetry.' + $package + } + if ($package -eq $projectName) + { + $added = $labelsToAdd.Add($repoLabel.name) + break + } + } + } + } + + if ($documentationFiles.Contains($fullFileName) -or + $fileChanged.StartsWith('docs/') -or + $fileChanged.StartsWith('examples/')) + { + $added = $labelsToAdd.Add("documentation") + } + + if ($fileChanged.StartsWith('build/') -or + $fileChanged.StartsWith('.github/') -or + $rootInfraFiles.Contains($fullFileName) -or + $fileExtension -eq ".props" -or + $fileExtension -eq ".targets" -or + $fileChanged.StartsWith('test/openTelemetry.aotcompatibility')) + { + $added = $labelsToAdd.Add("infra") + } + + if ($fileChanged.StartsWith('test/benchmarks')) + { + $added = $labelsToAdd.Add("perf") + } + + if ($fullFileName -eq 'directory.packages.props') + { + $added = $labelsToAdd.Add("dependencies") + } + } + + foreach ($labelOnPullRequest in $labelsOnPullRequest) + { + if ($labelsToAdd.Contains($labelOnPullRequest.name)) + { + $removed = $labelsToAdd.Remove($labelOnPullRequest.name) + } + elseif ($labelOnPullRequest.name.StartsWith($labelPackagePrefix) -or + $managedLabels.Contains($labelOnPullRequest.name)) + { + $added = $labelsToRemove.Add($labelOnPullRequest.name) + } + } + + if ($labelsToAdd.Count -gt 0) + { + foreach ($label in $labelsToAdd) + { + gh pr edit $pullRequestNumber --add-label $label + } + } + + if ($labelsToRemove.Count -gt 0) + { + foreach ($label in $labelsToRemove) + { + gh pr edit $pullRequestNumber --remove-label $label + } + } +} + +Export-ModuleMember -Function AddLabelsOnPullRequestsBasedOnFilesChanged diff --git a/build/scripts/post-release.psm1 b/build/scripts/post-release.psm1 index 39250d38d1..ae20f939e2 100644 --- a/build/scripts/post-release.psm1 +++ b/build/scripts/post-release.psm1 @@ -1,39 +1,42 @@ -$gitHubBotUserName="github-actions[bot]" -$gitHubBotEmail="41898282+github-actions[bot]@users.noreply.github.com" - -$repoViewResponse = gh repo view --json nameWithOwner | ConvertFrom-Json - -$gitRepository = $repoViewResponse.nameWithOwner - function CreateDraftRelease { param( - [Parameter(Mandatory=$true)][string]$tag + [Parameter(Mandatory=$true)][string]$gitRepository, + [Parameter(Mandatory=$true)][string]$tag, + [Parameter()][string]$releaseFiles ) - $packages = (Get-ChildItem -Path src/*/bin/Release/*.nupkg).Name + $match = [regex]::Match($tag, '^(.*?-)(.*)$') + if ($match.Success -eq $false) + { + throw 'Could not parse prefix or version from tag' + } + + $tagPrefix = $match.Groups[1].Value + $version = $match.Groups[2].Value + $isPrerelease = $version -match '-alpha' -or $version -match '-beta' -or $version -match '-rc' - $notes = '' - $firstPackageVersion = '' + $projects = @(Get-ChildItem -Path src/**/*.csproj | Select-String "$tagPrefix" -List | Select Path) - foreach ($package in $packages) + if ($projects.Length -eq 0) { - $match = [regex]::Match($package, '(.*)\.(\d+\.\d+\.\d+.*?)\.nupkg') - $packageName = $match.Groups[1].Value - $packageVersion = $match.Groups[2].Value + throw 'No projects found with MinVerTagPrefix matching prefix from tag' + } - if ($firstPackageVersion -eq '') - { - $firstPackageVersion = $packageVersion - } + $notes = '' + $previousVersion = '' - $changelogContent = Get-Content -Path "src/$packageName/CHANGELOG.md" + foreach ($project in $projects) + { + $projectName = [System.IO.Path]::GetFileNameWithoutExtension($project.Path) + + $changelogContent = Get-Content -Path "src/$projectName/CHANGELOG.md" $started = $false $content = "" foreach ($line in $changelogContent) { - if ($line -like "## $packageVersion" -and $started -ne $true) + if ($line -like "## $version" -and $started -ne $true) { $started = $true } @@ -43,6 +46,11 @@ function CreateDraftRelease { } elseif ($line -like "## *" -and $started -eq $true) { + $match = [regex]::Match($line, '^##\s*(.*)$') + if ($match.Success -eq $true) + { + $previousVersion = $match.Groups[1].Value + } break } else @@ -56,25 +64,33 @@ function CreateDraftRelease { if ([string]::IsNullOrWhitespace($content) -eq $true) { - $content = " No notable changes." + $content = " No notable changes." } $content = $content.trimend() $notes += @" -* NuGet: [$packageName v$packageVersion](https://www.nuget.org/packages/$packageName/$packageVersion) +* NuGet: [$projectName v$version](https://www.nuget.org/packages/$projectName/$version) $content - See [CHANGELOG](https://github.com/$gitRepository/blob/$tag/src/$packageName/CHANGELOG.md) for details. + See [CHANGELOG](https://github.com/$gitRepository/blob/$tag/src/$projectName/CHANGELOG.md) for details. + "@ } - if ($firstPackageVersion -match '-alpha' -or $firstPackageVersion -match '-beta' -or $firstPackageVersion -match '-rc') + if ($isPrerelease -eq $true) { - gh release create $tag ` + $notes = +@" +The following changes are from the previous release [$previousVersion](https://github.com/$gitRepository/releases/tag/$tagPrefix$previousVersion). + + +"@ + $notes + + gh release create $tag $releaseFiles ` --title $tag ` --verify-tag ` --notes $notes ` @@ -83,7 +99,16 @@ $content } else { - gh release create $tag ` + $notes = +@" +For highlights and announcements pertaining to this release see: [Release Notes > $version](https://github.com/$gitRepository/blob/main/RELEASENOTES.md#$($version.Replace('.',''))). + +The following changes are from the previous release [$previousVersion](https://github.com/$gitRepository/releases/tag/$tagPrefix$previousVersion). + + +"@ + $notes + + gh release create $tag $releaseFiles ` --title $tag ` --verify-tag ` --notes $notes ` @@ -94,12 +119,153 @@ $content Export-ModuleMember -Function CreateDraftRelease +function TryPostPackagesReadyNoticeOnPrepareReleasePullRequest { + param( + [Parameter(Mandatory=$true)][string]$gitRepository, + [Parameter(Mandatory=$true)][string]$tag, + [Parameter(Mandatory=$true)][string]$tagSha, + [Parameter(Mandatory=$true)][string]$packagesUrl, + [Parameter(Mandatory=$true)][string]$botUserName + ) + + $prListResponse = gh pr list --search $tagSha --state merged --json number,author,title,comments | ConvertFrom-Json + + if ($prListResponse.Length -eq 0) + { + Write-Host 'No prepare release PR found for tag & commit skipping post notice' + return + } + + foreach ($pr in $prListResponse) + { + if ($pr.author.login -ne $botUserName -or $pr.title -ne "[release] Prepare release $tag") + { + continue + } + + $foundComment = $false + foreach ($comment in $pr.comments) + { + if ($comment.author.login -eq $botUserName -and $comment.body.StartsWith("I just pushed the [$tag]")) + { + $foundComment = $true + break + } + } + + if ($foundComment -eq $false) + { + continue + } + + $body = +@" +The packages for [$tag](https://github.com/$gitRepository/releases/tag/$tag) are now available: $packagesUrl. + +Once these packages have been validated have a maintainer post a comment with "/PushPackages" in the body if you would like me to push to NuGet. +"@ + + $pullRequestNumber = $pr.number + + gh pr comment $pullRequestNumber --body $body + return + } + + Write-Host 'No prepare release PR found matched author and title with a valid comment' +} + +Export-ModuleMember -Function TryPostPackagesReadyNoticeOnPrepareReleasePullRequest + +function PushPackagesPublishReleaseUnlockAndPostNoticeOnPrepareReleasePullRequest { + param( + [Parameter(Mandatory=$true)][string]$gitRepository, + [Parameter(Mandatory=$true)][string]$pullRequestNumber, + [Parameter(Mandatory=$true)][string]$botUserName, + [Parameter(Mandatory=$true)][string]$commentUserName, + [Parameter(Mandatory=$true)][string]$artifactDownloadPath, + [Parameter(Mandatory=$true)][string]$pushToNuget + ) + + $prViewResponse = gh pr view $pullRequestNumber --json author,title,comments | ConvertFrom-Json + + if ($prViewResponse.author.login -ne $botUserName) + { + throw 'PR author was unexpected' + } + + $match = [regex]::Match($prViewResponse.title, '^\[release\] Prepare release (.*)$') + if ($match.Success -eq $false) + { + throw 'Could not parse tag from PR title' + } + + $tag = $match.Groups[1].Value + + $commentUserPermission = gh api "repos/$gitRepository/collaborators/$commentUserName/permission" | ConvertFrom-Json + if ($commentUserPermission.permission -ne 'admin') + { + gh pr comment $pullRequestNumber ` + --body "I'm sorry @$commentUserName but you don't have permission to push packages. Only maintainers can push to NuGet." + return + } + + $foundComment = $false + $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:")) + { + $foundComment = $true + break + } + } + + if ($foundComment -eq $false) + { + throw 'Could not find package push comment on pr' + } + + gh release download $tag ` + -p "$tag-packages.zip" ` + -D "$artifactDownloadPath" + + Expand-Archive -LiteralPath "$artifactDownloadPath/$tag-packages.zip" -DestinationPath "$artifactDownloadPath\" + + if ($pushToNuget -eq 'true') + { + 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" + + if ($LASTEXITCODE -gt 0) + { + gh pr comment $pullRequestNumber ` + --body "Something went wrong uploading the packages for ``$tag`` to NuGet." + + throw 'nuget push failure' + } + } + else { + gh pr comment $pullRequestNumber ` + --body "I am publishing the release without uploading the packages to NuGet because a token wasn't configured." + } + + gh release edit $tag --draft=false + + gh pr unlock $pullRequestNumber +} + +Export-ModuleMember -Function PushPackagesPublishReleaseUnlockAndPostNoticeOnPrepareReleasePullRequest + function CreateStableVersionUpdatePullRequest { param( + [Parameter(Mandatory=$true)][string]$gitRepository, [Parameter(Mandatory=$true)][string]$tag, - [Parameter()][string]$gitUserName=$gitHubBotUserName, - [Parameter()][string]$gitUserEmail=$gitHubBotEmail, - [Parameter()][string]$targetBranch="main" + [Parameter()][string]$targetBranch="main", + [Parameter()][string]$lineEnding="`n", + [Parameter()][string]$gitUserName, + [Parameter()][string]$gitUserEmail ) $match = [regex]::Match($tag, '.*?-(.*)') @@ -108,12 +274,18 @@ function CreateStableVersionUpdatePullRequest { throw 'Could not parse version from tag' } - $packageVersion = $match.Groups[1].Value + $version = $match.Groups[1].Value $branch="release/post-stable-${tag}-update" - git config user.name $gitUserName - git config user.email $gitUserEmail + if ([string]::IsNullOrEmpty($gitUserName) -eq $false) + { + git config user.name $gitUserName + } + if ([string]::IsNullOrEmpty($gitUserEmail) -eq $false) + { + git config user.email $gitUserEmail + } git switch --create $branch origin/$targetBranch --no-track 2>&1 | % ToString if ($LASTEXITCODE -gt 0) @@ -121,17 +293,38 @@ function CreateStableVersionUpdatePullRequest { throw 'git switch failure' } + $projectsAndDependenciesBefore = GetCoreDependenciesForProjects + (Get-Content Directory.Packages.props) ` - -replace '.*<\/OTelLatestStableVer>', "$packageVersion" | + -replace '.*<\/OTelLatestStableVer>', "$version" | Set-Content Directory.Packages.props + $projectsAndDependenciesAfter = GetCoreDependenciesForProjects + + $changedProjects = @{} + + $projectsAndDependenciesBefore.GetEnumerator() | ForEach-Object { + $projectDir = $_.Key + $projectDependenciesBefore = $_.Value + $projectDependenciesAfter = $projectsAndDependenciesAfter[$projectDir] + + $projectDependenciesBefore.GetEnumerator() | ForEach-Object { + $packageName = $_.Key + $packageVersionBefore = $_.Value + if ($projectDependenciesAfter[$packageName] -ne $packageVersionBefore) + { + $changedProjects[$projectDir] = $true + } + } + } + git add Directory.Packages.props 2>&1 | % ToString if ($LASTEXITCODE -gt 0) { throw 'git add failure' } - git commit -m "Update OTelLatestStableVer in Directory.Packages.props to $packageVersion." 2>&1 | % ToString + git commit -m "Update OTelLatestStableVer in Directory.Packages.props to $version." 2>&1 | % ToString if ($LASTEXITCODE -gt 0) { throw 'git commit failure' @@ -145,22 +338,261 @@ function CreateStableVersionUpdatePullRequest { $body = @" -Note: This PR was opened automatically by the [package workflow](https://github.com/$gitRepository/actions/workflows/publish-packages-1.0.yml). +Note: This PR was opened automatically by the [post-release workflow](https://github.com/$gitRepository/actions/workflows/post-release.yml). Merge once packages are available on NuGet and the build passes. ## Changes -* Sets ``OTelLatestStableVer`` in ``Directory.Packages.props`` to ``$packageVersion``. +* Sets ``OTelLatestStableVer`` in ``Directory.Packages.props`` to ``$version``. "@ - gh pr create ` - --title "[repo] Core stable release $packageVersion updates" ` + $createPullRequestResponse = gh pr create ` + --title "[release] Core stable release $version updates" ` --body $body ` --base $targetBranch ` --head $branch ` - --label infra ` - --draft + --label release + + Write-Host $createPullRequestResponse + + $match = [regex]::Match($createPullRequestResponse, "\/pull\/(.*)$") + if ($match.Success -eq $false) + { + throw 'Could not parse pull request number from gh pr create response' + } + + $pullRequestNumber = $match.Groups[1].Value + + if ($changedProjects.Count -eq 0) + { + Return + } + + $entry = @" +* Updated OpenTelemetry core component version(s) to ``$version``. + ([#$pullRequestNumber](https://github.com/$gitRepository/pull/$pullRequestNumber)) + + +"@ + + $lastLineBlank = $true + $changelogFilesUpdated = 0 + + foreach ($projectDir in $changedProjects.Keys) + { + $path = Join-Path -Path $projectDir -ChildPath "CHANGELOG.md" + + if ([System.IO.File]::Exists($path) -eq $false) + { + Write-Host "No CHANGELOG found in $projectDir" + continue + } + + $changelogContent = Get-Content -Path $path + + $started = $false + $isRemoving = $false + $content = "" + + foreach ($line in $changelogContent) + { + if ($line -like "## Unreleased" -and $started -ne $true) + { + $started = $true + } + elseif ($line -like "## *" -and $started -eq $true) + { + if ($lastLineBlank -eq $false) + { + $content += $lineEnding + } + $content += $entry + $started = $false + $isRemoving = $false + } + elseif ($started -eq $true -and ($line -like '*Update* OpenTelemetry SDK version to*' -or $line -like '*Updated OpenTelemetry core component version(s) to*')) + { + $isRemoving = $true + continue + } + + if ($line.StartsWith('* ')) + { + if ($isRemoving -eq $true) + { + $isRemoving = $false + } + + if ($lastLineBlank -eq $false) + { + $content += $lineEnding + } + } + + if ($isRemoving -eq $true) + { + continue + } + + $content += $line + $lineEnding + + $lastLineBlank = [string]::IsNullOrWhitespace($line) + } + + if ($started -eq $true) + { + # Note: If we never wrote the entry it means the file ended in the unreleased section + if ($lastLineBlank -eq $false) + { + $content += $lineEnding + } + $content += $entry + } + + Set-Content -Path $path -Value $content.TrimEnd() + + git add $path 2>&1 | % ToString + if ($LASTEXITCODE -gt 0) + { + throw 'git add failure' + } + + $changelogFilesUpdated++ + } + + if ($changelogFilesUpdated -gt 0) + { + git commit -m "Update CHANGELOGs for projects using OTelLatestStableVer." 2>&1 | % ToString + if ($LASTEXITCODE -gt 0) + { + throw 'git commit failure' + } + + git push -u origin $branch 2>&1 | % ToString + if ($LASTEXITCODE -gt 0) + { + throw 'git push failure' + } + } } Export-ModuleMember -Function CreateStableVersionUpdatePullRequest + +function GetCoreDependenciesForProjects { + $projects = @(Get-ChildItem -Path 'src/*/*.csproj') + + $projectsAndDependencies = @{} + + foreach ($project in $projects) + { + # Note: dotnet restore may fail if the core packages aren't available yet but that is fine, we just want to generate project.assets.json for these projects. + $output = dotnet restore $project -p:RunningDotNetPack=true + + $projectDir = $project | Split-Path -Parent + + $content = (Get-Content "$projectDir/obj/project.assets.json" -Raw) + + $projectDependencies = @{} + + $matches = [regex]::Matches($content, '"(OpenTelemetry(?:.*))?": {[\S\s]*?"target": "Package",[\S\s]*?"version": "(.*)"[\S\s]*?}') + foreach ($match in $matches) + { + $packageName = $match.Groups[1].Value + $packageVersion = $match.Groups[2].Value + if ($packageName -eq 'OpenTelemetry' -or + $packageName -eq 'OpenTelemetry.Api' -or + $packageName -eq 'OpenTelemetry.Api.ProviderBuilderExtensions' -or + $packageName -eq 'OpenTelemetry.Extensions.Hosting' -or + $packageName -eq 'OpenTelemetry.Extensions.Propagators') + { + $projectDependencies[$packageName.ToString()] = $packageVersion.ToString() + } + } + $projectsAndDependencies[$projectDir.ToString()] = $projectDependencies + } + + return $projectsAndDependencies +} + +function InvokeCoreVersionUpdateWorkflowInRemoteRepository { + param( + [Parameter(Mandatory=$true)][string]$remoteGitRepository, + [Parameter(Mandatory=$true)][string]$tag, + [Parameter()][string]$targetBranch="main" + ) + + $match = [regex]::Match($tag, '^(.*?-)(.*)$') + if ($match.Success -eq $false) + { + throw 'Could not parse prefix or version from tag' + } + + gh workflow run "core-version-update.yml" ` + --repo $remoteGitRepository ` + --ref $targetBranch ` + --field "tag=$tag" +} + +Export-ModuleMember -Function InvokeCoreVersionUpdateWorkflowInRemoteRepository + +function TryPostReleasePublishedNoticeOnPrepareReleasePullRequest { + param( + [Parameter(Mandatory=$true)][string]$gitRepository, + [Parameter(Mandatory=$true)][string]$botUserName, + [Parameter(Mandatory=$true)][string]$tag + ) + + $tagSha = git rev-list -n 1 $tag 2>&1 | % ToString + if ($LASTEXITCODE -gt 0) + { + throw 'git rev-list failure' + } + + $prListResponse = gh pr list --search $tagSha --state merged --json number,author,title,comments | ConvertFrom-Json + + if ($prListResponse.Length -eq 0) + { + Write-Host 'No prepare release PR found for tag & commit skipping post notice' + return + } + + foreach ($pr in $prListResponse) + { + if ($pr.author.login -ne $botUserName -or $pr.title -ne "[release] Prepare release $tag") + { + continue + } + + $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:")) + { + $foundComment = $true + break + } + } + + if ($foundComment -eq $false) + { + continue + } + + $body = +@" +The release [$tag](https://github.com/$gitRepository/releases/tag/$tag) has been published and packages should be available on NuGet momentarily. + +Have a nice day! +"@ + + $pullRequestNumber = $pr.number + + gh pr comment $pullRequestNumber --body $body + return + } + + Write-Host 'No prepare release PR found matched author and title with a valid comment' +} + +Export-ModuleMember -Function TryPostReleasePublishedNoticeOnPrepareReleasePullRequest diff --git a/build/scripts/prepare-release.psm1 b/build/scripts/prepare-release.psm1 index ad2b0b53e7..44ba4f4130 100644 --- a/build/scripts/prepare-release.psm1 +++ b/build/scripts/prepare-release.psm1 @@ -1,24 +1,31 @@ -$gitHubBotUserName="github-actions[bot]" -$gitHubBotEmail="41898282+github-actions[bot]@users.noreply.github.com" - -$repoViewResponse = gh repo view --json nameWithOwner | ConvertFrom-Json - -$gitRepository = $repoViewResponse.nameWithOwner - function CreatePullRequestToUpdateChangelogsAndPublicApis { param( + [Parameter(Mandatory=$true)][string]$gitRepository, [Parameter(Mandatory=$true)][string]$minVerTagPrefix, [Parameter(Mandatory=$true)][string]$version, - [Parameter()][string]$gitUserName=$gitHubBotUserName, - [Parameter()][string]$gitUserEmail=$gitHubBotEmail, - [Parameter()][string]$targetBranch="main" + [Parameter(Mandatory=$true)][string]$requestedByUserName, + [Parameter()][string]$targetBranch="main", + [Parameter()][string]$gitUserName, + [Parameter()][string]$gitUserEmail ) + $match = [regex]::Match($version, '^(\d+\.\d+\.\d+)(?:-((?:alpha)|(?:beta)|(?:rc))\.(\d+))?$') + if ($match.Success -eq $false) + { + throw 'Input version did not match expected format' + } + $tag="${minVerTagPrefix}${version}" $branch="release/prepare-${tag}-release" - git config user.name $gitUserName - git config user.email $gitUserEmail + if ([string]::IsNullOrEmpty($gitUserName) -eq $false) + { + git config user.name $gitUserName + } + if ([string]::IsNullOrEmpty($gitUserEmail) -eq $false) + { + git config user.email $gitUserEmail + } git switch --create $branch 2>&1 | % ToString if ($LASTEXITCODE -gt 0) @@ -30,6 +37,8 @@ function CreatePullRequestToUpdateChangelogsAndPublicApis { @" Note: This PR was opened automatically by the [prepare release workflow](https://github.com/$gitRepository/actions/workflows/prepare-release.yml). +Requested by: @$requestedByUserName + ## Changes * CHANGELOG files updated for projects being released. @@ -46,6 +55,16 @@ Note: This PR was opened automatically by the [prepare release workflow](https:/ $body += "`r`n* Public API files updated for projects being released (only performed for stable releases)." } + $body += +@" + +## Commands + +``/UpdateReleaseDates``: Use to update release dates in CHANGELOGs before merging [``approvers``, ``maintainers``] +``/CreateReleaseTag``: Use after merging to push the release tag and trigger the job to create packages [``approvers``, ``maintainers``] +``/PushPackages``: Use after the created packages have been validated to push to NuGet [``maintainers``] +"@ + git commit -a -m "Prepare repo to release $tag." 2>&1 | % ToString if ($LASTEXITCODE -gt 0) { @@ -59,33 +78,30 @@ Note: This PR was opened automatically by the [prepare release workflow](https:/ } gh pr create ` - --title "[repo] Prepare release $tag" ` + --title "[release] Prepare release $tag" ` --body $body ` --base $targetBranch ` --head $branch ` - --label infra + --label release } Export-ModuleMember -Function CreatePullRequestToUpdateChangelogsAndPublicApis function LockPullRequestAndPostNoticeToCreateReleaseTag { param( + [Parameter(Mandatory=$true)][string]$gitRepository, [Parameter(Mandatory=$true)][string]$pullRequestNumber, - [Parameter()][string]$gitUserName=$gitHubBotUserName, - [Parameter()][string]$gitUserEmail=$gitHubBotEmail + [Parameter(Mandatory=$true)][string]$botUserName ) - git config user.name $gitUserName - git config user.email $gitUserEmail - $prViewResponse = gh pr view $pullRequestNumber --json mergeCommit,author,title | ConvertFrom-Json - if ($prViewResponse.author.is_bot -eq $false -or $prViewResponse.author.login -ne 'app/github-actions') + if ($prViewResponse.author.login -ne $botUserName) { throw 'PR author was unexpected' } - $match = [regex]::Match($prViewResponse.title, '^\[repo\] Prepare release (.*)$') + $match = [regex]::Match($prViewResponse.title, '^\[release\] Prepare release (.*)$') if ($match.Success -eq $false) { throw 'Could not parse tag from PR title' @@ -103,7 +119,7 @@ function LockPullRequestAndPostNoticeToCreateReleaseTag { @" I noticed this PR was merged. -Post a comment with "/CreateReleaseTag" in the body if you would like me to create the release tag ``$tag`` for [the merge commit](https://github.com/$gitRepository/commit/$commit) and then trigger the package workflow. +Post a comment with "/CreateReleaseTag" in the body if you would like me to create the release tag ``$tag`` for [the merge commit](https://github.com/$gitRepository/commit/$commit) which will trigger the package workflow. "@ gh pr comment $pullRequestNumber --body $body @@ -113,32 +129,29 @@ Post a comment with "/CreateReleaseTag" in the body if you would like me to crea Export-ModuleMember -Function LockPullRequestAndPostNoticeToCreateReleaseTag -function CreateReleaseTag { +function CreateReleaseTagAndPostNoticeOnPullRequest { param( + [Parameter(Mandatory=$true)][string]$gitRepository, [Parameter(Mandatory=$true)][string]$pullRequestNumber, - [Parameter(Mandatory=$true)][string]$actionRunId, - [Parameter()][string]$gitUserName=$gitHubBotUserName, - [Parameter()][string]$gitUserEmail=$gitHubBotEmail, - [Parameter()][ref]$tag + [Parameter(Mandatory=$true)][string]$botUserName, + [Parameter()][string]$gitUserName, + [Parameter()][string]$gitUserEmail ) - git config user.name $gitUserName - git config user.email $gitUserEmail - $prViewResponse = gh pr view $pullRequestNumber --json mergeCommit,author,title | ConvertFrom-Json - if ($prViewResponse.author.is_bot -eq $false -or $prViewResponse.author.login -ne 'app/github-actions') + if ($prViewResponse.author.login -ne $botUserName) { throw 'PR author was unexpected' } - $match = [regex]::Match($prViewResponse.title, '^\[repo\] Prepare release (.*)$') + $match = [regex]::Match($prViewResponse.title, '^\[release\] Prepare release (.*)$') if ($match.Success -eq $false) { throw 'Could not parse tag from PR title' } - $tagValue = $match.Groups[1].Value + $tag = $match.Groups[1].Value $commit = $prViewResponse.mergeCommit.oid if ([string]::IsNullOrEmpty($commit) -eq $true) @@ -146,49 +159,138 @@ function CreateReleaseTag { throw 'Could not find merge commit' } - git tag -a $tagValue -m "$tagValue" $commit 2>&1 | % ToString + if ([string]::IsNullOrEmpty($gitUserName) -eq $false) + { + git config user.name $gitUserName + } + if ([string]::IsNullOrEmpty($gitUserEmail) -eq $false) + { + git config user.email $gitUserEmail + } + + git tag -a $tag -m "$tag" $commit 2>&1 | % ToString if ($LASTEXITCODE -gt 0) { throw 'git tag failure' } - git push origin $tagValue 2>&1 | % ToString + git push origin $tag 2>&1 | % ToString if ($LASTEXITCODE -gt 0) { throw 'git push failure' } - gh pr unlock $pullRequestNumber - $body = @" -I just pushed the [$tagValue](https://github.com/$gitRepository/releases/tag/$tagValue) tag. +I just pushed the [$tag](https://github.com/$gitRepository/releases/tag/$tag) tag. -The [package workflow](https://github.com/$gitRepository/actions/runs/$actionRunId) should begin momentarily. +The [package workflow](https://github.com/$gitRepository/actions/workflows/publish-packages-1.0.yml) should begin momentarily. "@ gh pr comment $pullRequestNumber --body $body - - $tag.value = $tagValue } -Export-ModuleMember -Function CreateReleaseTag +Export-ModuleMember -Function CreateReleaseTagAndPostNoticeOnPullRequest -function PostPackagesReadyNotice { +function UpdateChangelogReleaseDatesAndPostNoticeOnPullRequest { param( + [Parameter(Mandatory=$true)][string]$gitRepository, [Parameter(Mandatory=$true)][string]$pullRequestNumber, - [Parameter(Mandatory=$true)][string]$tag, - [Parameter(Mandatory=$true)][string]$packagesUrl + [Parameter(Mandatory=$true)][string]$botUserName, + [Parameter(Mandatory=$true)][string]$commentUserName, + [Parameter()][string]$gitUserName, + [Parameter()][string]$gitUserEmail ) - $body = + $prViewResponse = gh pr view $pullRequestNumber --json headRefName,author,title | ConvertFrom-Json + + if ($prViewResponse.author.login -ne $botUserName) + { + throw 'PR author was unexpected' + } + + $match = [regex]::Match($prViewResponse.title, '^\[release\] Prepare release (.*)$') + if ($match.Success -eq $false) + { + throw 'Could not parse tag from PR title' + } + + $tag = $match.Groups[1].Value + + $match = [regex]::Match($tag, '^(.*?-)(.*)$') + if ($match.Success -eq $false) + { + throw 'Could not parse prefix or version from tag' + } + + $tagPrefix = $match.Groups[1].Value + $version = $match.Groups[2].Value + + $commentUserPermission = gh api "repos/$gitRepository/collaborators/$commentUserName/permission" | ConvertFrom-Json + if ($commentUserPermission.permission -ne 'admin' -and $commentUserPermission.permission -ne 'write') + { + gh pr comment $pullRequestNumber ` + --body "I'm sorry @$commentUserName but you don't have permission to update this PR. Only maintainers and approvers can update this PR." + return + } + + if ([string]::IsNullOrEmpty($gitUserName) -eq $false) + { + git config user.name $gitUserName + } + if ([string]::IsNullOrEmpty($gitUserEmail) -eq $false) + { + git config user.email $gitUserEmail + } + + git switch $prViewResponse.headRefName 2>&1 | % ToString + if ($LASTEXITCODE -gt 0) + { + throw 'git switch failure' + } + + $updatedFiles = 0 + $newHeader = @" -The packages for [$tag](https://github.com/$gitRepository/releases/tag/$tag) are now available: $packagesUrl. +## $version -Have a nice day! +Released $(Get-Date -UFormat '%Y-%b-%d') "@ - gh pr comment $pullRequestNumber --body $body + $projectDirs = Get-ChildItem -Path src/**/*.csproj | Select-String "$tagPrefix" -List | Select Path | Split-Path -Parent + + foreach ($projectDir in $projectDirs) + { + $content = (Get-Content "$projectDir/CHANGELOG.md" -Raw) + + $newContent = $content -replace "## $version\s*Released .*", $newHeader + + if ($content -ne $newContent) + { + $updatedFiles++ + Set-Content -Path "$projectDir/CHANGELOG.md" $newContent.Trim() + } + } + + if ($updatedFiles -eq 0) + { + gh pr comment $pullRequestNumber --body "All of the CHANGELOG files have valid release dates." + return + } + + git commit -a -m "Update CHANGELOG release dates for $tag." 2>&1 | % ToString + if ($LASTEXITCODE -gt 0) + { + throw 'git commit failure' + } + + git push -u origin $prViewResponse.headRefName 2>&1 | % ToString + if ($LASTEXITCODE -gt 0) + { + throw 'git push failure' + } + + gh pr comment $pullRequestNumber --body "I updated the CHANGELOG release dates." } -Export-ModuleMember -Function PostPackagesReadyNotice +Export-ModuleMember -Function UpdateChangelogReleaseDatesAndPostNoticeOnPullRequest diff --git a/build/scripts/test-aot-compatibility.ps1 b/build/scripts/test-aot-compatibility.ps1 index c7fc3acf7d..895055a1b4 100644 --- a/build/scripts/test-aot-compatibility.ps1 +++ b/build/scripts/test-aot-compatibility.ps1 @@ -44,7 +44,7 @@ $testPassed = 0 if ($actualWarningCount -ne $expectedWarningCount) { $testPassed = 1 - Write-Host "Actual warning count:", actualWarningCount, "is not as expected. Expected warning count is:", $expectedWarningCount + Write-Host "Actual warning count:", $actualWarningCount, "is not as expected. Expected warning count is:", $expectedWarningCount } Exit $testPassed diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..23d80c8a57 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,134 @@ +# OpenTelemetry .NET SDK + +## Initialize the SDK + +There are two different common initialization styles supported by OpenTelemetry. + +### Initialize the SDK using a host + +Users building applications based on +[Microsoft.Extensions.Hosting](https://www.nuget.org/packages/Microsoft.Extensions.Hosting) +should utilize the +[OpenTelemetry.Extensions.Hosting](../src/OpenTelemetry.Extensions.Hosting/README.md) +package to initialize OpenTelemetry. This style provides a deep integration +between the host infrastructure (`IServiceCollection`, `IServiceProvider`, +`IConfiguration`, etc.) and OpenTelemetry. + +[AspNetCore](https://learn.microsoft.com/aspnet/core/fundamentals/host/web-host) +applications are the most common to use the hosting model but there is also a +[Generic Host](https://learn.microsoft.com/dotnet/core/extensions/generic-host) +which may be used in console, service, and worker applications. + +> [!NOTE] +> When using `OpenTelemetry.Extensions.Hosting` only a single pipeline will be +> created for each configured signal (logging, metrics, and/or tracing). Users +> who need more granular control can create additional pipelines using the +> manual style below. + +First install the +[OpenTelemetry.Extensions.Hosting](../src/OpenTelemetry.Extensions.Hosting/README.md) +package. + +Second call the `AddOpenTelemetry` extension using the host +`IServiceCollection`: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +// Clear the default logging providers added by the host +builder.Logging.ClearProviders(); + +// Initialize OpenTelemetry +builder.Services.AddOpenTelemetry() + .ConfigureResource(resource => /* Resource configuration goes here */) + .WithLogging(logging => /* Logging configuration goes here */) + .WithMetrics(metrics => /* Metrics configuration goes here */) + .WithTracing(tracing => /* Tracing configuration goes here */)); +``` + +> [!NOTE] +> Calling `WithLogging` automatically registers the OpenTelemetry +> `ILoggerProvider` and enables `ILogger` integration. + +### Initialize the SDK manually + +Users running on .NET Framework or running without a host may initialize +OpenTelemetry manually. + +> [!IMPORTANT] +> When initializing OpenTelemetry manually make sure to ALWAYS dispose the SDK +> and/or providers when the application is shutting down. Disposing +> OpenTelemetry gives the SDK a chance to flush any telemetry held in memory. +> Skipping this step may result in data loss. + +First install the [OpenTelemetry SDK](../src/OpenTelemetry/README.md) package or +an exporter package such as +[OpenTelemetry.Exporter.OpenTelemetryProtocol](../src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md). +Exporter packages typically reference the SDK and will make it available via +transitive reference. + +Second use one of the following initialization APIs (depending on the SDK +version being used): + +#### Using 1.10.0 or newer + +The `OpenTelemetrySdk.Create` API can be used to initialize all signals off a +single root builder and supports cross-cutting extensions such as +`ConfigureResource` which configures a `Resource` to be used by all enabled +signals. An `OpenTelemetrySdk` instance is returned which may be used to access +providers for each signal. Calling `Dispose` on the returned instance will +gracefully shutdown the SDK and flush any telemetry held in memory. + +> [!NOTE] +> When calling `OpenTelemetrySdk.Create` a dedicated `IServiceCollection` and +> `IServiceProvider` will be created for the SDK and shared by all signals. An +> `IConfiguration` is created automatically from environment variables. + +```csharp +using OpenTelemetry; + +var sdk = OpenTelemetrySdk.Create(builder => builder + .ConfigureResource(resource => /* Resource configuration goes here */) + .WithLogging(logging => /* Logging configuration goes here */) + .WithMetrics(metrics => /* Metrics configuration goes here */) + .WithTracing(tracing => /* Tracing configuration goes here */)); + +// During application shutdown +sdk.Dispose(); +``` + +To obtain an `ILogger` instance for emitting logs when using the +`OpenTelemetrySdk.Create` API call the `GetLoggerFactory` extension method using +the returned `OpenTelemetrySdk` instance: + +```csharp +var logger = sdk.GetLoggerFactory().CreateLogger(); +logger.LogInformation("Application started"); +``` + +#### Using 1.9.0 or older + +The following shows how to create providers for each individual signal. Each +provider is independent and must be managed and disposed explicitly. There is no +mechanism using this style to perform cross-cutting actions across signals. + +```csharp +using Microsoft.Extensions.Logging; +using OpenTelemetry; + +var tracerProvider = Sdk.CreateTracerProviderBuilder() + /* Tracing configuration goes here */ + .Build(); + +var meterProvider = Sdk.CreateMeterProviderBuilder() + /* Metrics configuration goes here */ + .Build(); + +var loggerFactory = LoggerFactory.Create(builder => builder + .AddOpenTelemetry(options => /* Logging configuration goes here */)); + +// During application shutdown +tracerProvider.Dispose(); +meterProvider.Dispose(); +loggerFactory.Dispose(); +``` diff --git a/docs/diagnostics/experimental-apis/OTEL1000.md b/docs/diagnostics/experimental-apis/OTEL1000.md index 1ea4be023a..28b8147293 100644 --- a/docs/diagnostics/experimental-apis/OTEL1000.md +++ b/docs/diagnostics/experimental-apis/OTEL1000.md @@ -4,11 +4,17 @@ This is an Experimental API diagnostic covering the following APIs: +* `ILoggingBuilder.UseOpenTelemetry` + +Experimental APIs may be changed or removed in the future. + +The following portions of `OTEL1000` were released stable in `1.9.0` and are no +longer considered experimental: + * `LoggerProviderBuilder` * `LoggerProvider` * `IDeferredLoggerProviderBuilder` - -Experimental APIs may be changed or removed in the future. +* `OpenTelemetryBuilder.WithLogging` ## Details diff --git a/docs/diagnostics/experimental-apis/OTEL1001.md b/docs/diagnostics/experimental-apis/OTEL1001.md index 5386726e64..aeb8952630 100644 --- a/docs/diagnostics/experimental-apis/OTEL1001.md +++ b/docs/diagnostics/experimental-apis/OTEL1001.md @@ -9,6 +9,7 @@ This is an Experimental API diagnostic covering the following APIs: * `LogRecordAttributeList` * `LogRecordData` * `LogRecordSeverity` +* `Sdk.CreateLoggerProviderBuilder` Experimental APIs may be changed or removed in the future. diff --git a/docs/diagnostics/experimental-apis/OTEL1003.md b/docs/diagnostics/experimental-apis/OTEL1003.md deleted file mode 100644 index 5f62f03575..0000000000 --- a/docs/diagnostics/experimental-apis/OTEL1003.md +++ /dev/null @@ -1,47 +0,0 @@ -# OpenTelemetry .NET Diagnostic: OTEL1003 - -## Overview - -This is an Experimental API diagnostic covering the following API: - -* `MetricStreamConfiguration.CardinalityLimit.get` -* `MetricStreamConfiguration.CardinalityLimit.set` - -Experimental APIs may be changed or removed in the future. - -## Details - -From the specification: - -> The cardinality limit for an aggregation is defined in one of three ways: -> -> 1. A view with criteria matching the instrument an aggregation is created for -> has an `aggregation_cardinality_limit` value defined for the stream, that -> value SHOULD be used. -> 2. If there is no matching view, but the `MetricReader` defines a default -> cardinality limit value based on the instrument an aggregation is created -> for, that value SHOULD be used. -> 3. If none of the previous values are defined, the default value of 2000 -> SHOULD be used. - -We are exposing these APIs experimentally until the specification declares them -stable. - -### Setting cardinality limit for a specific Metric via the View API - -The OpenTelemetry Specification defines the [cardinality -limit](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#cardinality-limits) -of a metric can be set by the matching view. - -```csharp -using var meterProvider = Sdk.CreateMeterProviderBuilder() - .AddView( - instrumentName: "MyFruitCounter", - new MetricStreamConfiguration { CardinalityLimit = 10 }) - .Build(); -``` - -### Setting cardinality limit for a specific MetricReader - -[This is not currently supported by OpenTelemetry -.NET.](https://github.com/open-telemetry/opentelemetry-dotnet/issues/5331) diff --git a/docs/diagnostics/experimental-apis/README.md b/docs/diagnostics/experimental-apis/README.md index 6c581030b4..daa80d34b3 100644 --- a/docs/diagnostics/experimental-apis/README.md +++ b/docs/diagnostics/experimental-apis/README.md @@ -27,12 +27,6 @@ Description: Logs Bridge API Details: [OTEL1001](./OTEL1001.md) -### OTEL1003 - -Description: MetricStreamConfiguration CardinalityLimit Support - -Details: [OTEL1003](./OTEL1003.md) - ### OTEL1004 Description: ExemplarReservoir Support @@ -58,3 +52,11 @@ Description: Metrics Exemplar Support Details: [OTEL1002](https://github.com/open-telemetry/opentelemetry-dotnet/blob/b8ea807bae1a5d9b0f3d6d23b1e1e10f5e096a25/docs/diagnostics/experimental-apis/OTEL1002.md) Released stable: `1.9.0` + +### OTEL1003 + +Description: MetricStreamConfiguration CardinalityLimit Support + +Details: [OTEL1003](https://github.com/open-telemetry/opentelemetry-dotnet/blob/9f41eadf03f3dcc5e76c686b61fb39849f046312/docs/diagnostics/experimental-apis/OTEL1003.md) + +Released stable: `1.10.0` diff --git a/docs/logs/dedicated-pipeline/DedicatedLogging/DedicatedLoggingServiceCollectionExtensions.cs b/docs/logs/dedicated-pipeline/DedicatedLogging/DedicatedLoggingServiceCollectionExtensions.cs index 538f23b5cb..c9343185e2 100644 --- a/docs/logs/dedicated-pipeline/DedicatedLogging/DedicatedLoggingServiceCollectionExtensions.cs +++ b/docs/logs/dedicated-pipeline/DedicatedLogging/DedicatedLoggingServiceCollectionExtensions.cs @@ -11,20 +11,25 @@ public static class DedicatedLoggingServiceCollectionExtensions public static IServiceCollection AddDedicatedLogging( this IServiceCollection services, IConfiguration configuration, - Action configureOpenTelemetry) + Action configureOpenTelemetry) { ArgumentNullException.ThrowIfNull(configureOpenTelemetry); - services.TryAddSingleton(sp => + services.TryAddSingleton(_ => { - var loggerFactory = LoggerFactory.Create(builder => + var services = new ServiceCollection(); + services.AddLogging(builder => { builder.AddConfiguration(configuration); - builder.AddOpenTelemetry(configureOpenTelemetry); + builder.AddOpenTelemetry(); }); - return new DedicatedLoggerFactory(loggerFactory); + services.ConfigureOpenTelemetryLoggerProvider(configureOpenTelemetry); + + var sp = services.BuildServiceProvider(); + + return new DedicatedLoggerFactory(sp); }); services.TryAdd(ServiceDescriptor.Singleton(typeof(IDedicatedLogger<>), typeof(DedicatedLogger<>))); @@ -54,11 +59,13 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except private sealed class DedicatedLoggerFactory : ILoggerFactory { + private readonly ServiceProvider serviceProvider; private readonly ILoggerFactory innerLoggerFactory; - public DedicatedLoggerFactory(ILoggerFactory loggerFactory) + public DedicatedLoggerFactory(ServiceProvider serviceProvider) { - this.innerLoggerFactory = loggerFactory; + this.serviceProvider = serviceProvider; + this.innerLoggerFactory = serviceProvider.GetRequiredService(); } public void AddProvider(ILoggerProvider provider) @@ -68,6 +75,6 @@ public ILogger CreateLogger(string categoryName) => this.innerLoggerFactory.CreateLogger(categoryName); public void Dispose() - => this.innerLoggerFactory.Dispose(); + => this.serviceProvider.Dispose(); } } diff --git a/docs/logs/dedicated-pipeline/Program.cs b/docs/logs/dedicated-pipeline/Program.cs index ad671445cd..c465c77d0d 100644 --- a/docs/logs/dedicated-pipeline/Program.cs +++ b/docs/logs/dedicated-pipeline/Program.cs @@ -8,18 +8,19 @@ builder.Logging.ClearProviders(); -builder.Logging.AddOpenTelemetry(options => -{ - // Set up primary pipeline for common app logs - options.AddConsoleExporter(); -}); +builder.Services.AddOpenTelemetry() + .WithLogging(logging => + { + // Set up primary pipeline for common app logs + logging.AddConsoleExporter(); + }); builder.Services.AddDedicatedLogging( builder.Configuration.GetSection("DedicatedLogging"), // Bind configuration for dedicated logging pipeline - options => + logging => { // Set up secondary pipeline for dedicated logs - options.AddConsoleExporter(); + logging.AddConsoleExporter(); }); var app = builder.Build(); diff --git a/docs/logs/getting-started-aspnetcore/Program.cs b/docs/logs/getting-started-aspnetcore/Program.cs index 179d9d2237..123bf54a2c 100644 --- a/docs/logs/getting-started-aspnetcore/Program.cs +++ b/docs/logs/getting-started-aspnetcore/Program.cs @@ -6,25 +6,21 @@ var builder = WebApplication.CreateBuilder(args); -// Remove default providers and add OpenTelemetry logging provider. -// For instructional purposes only, disable the default .NET console logging provider to -// use the verbose OpenTelemetry console exporter instead. For most development -// and production scenarios the default console provider works well and there is no need to +// For instructional purposes only, disable the default .NET logging providers. +// We remove the console logging provider in this demo to use the verbose +// OpenTelemetry console exporter instead. For most development and production +// scenarios the default console provider works well and there is no need to // clear these providers. builder.Logging.ClearProviders(); -builder.Logging.AddOpenTelemetry(logging => -{ - var resourceBuilder = ResourceBuilder - .CreateDefault() - .AddService(builder.Environment.ApplicationName); - - logging.SetResourceBuilder(resourceBuilder) - - // ConsoleExporter is used for demo purpose only. - // In production environment, ConsoleExporter should be replaced with other exporters (e.g. OTLP Exporter). - .AddConsoleExporter(); -}); +// Add OpenTelemetry logging provider by calling the WithLogging extension. +builder.Services.AddOpenTelemetry() + .ConfigureResource(r => r.AddService(builder.Environment.ApplicationName)) + .WithLogging(logging => logging + /* Note: ConsoleExporter is used for demo purpose only. In production + environment, ConsoleExporter should be replaced with other exporters + (e.g. OTLP Exporter). */ + .AddConsoleExporter()); var app = builder.Build(); diff --git a/docs/logs/redaction/MyRedactionProcessor.cs b/docs/logs/redaction/MyRedactionProcessor.cs index 7959faf3d6..cec1edc450 100644 --- a/docs/logs/redaction/MyRedactionProcessor.cs +++ b/docs/logs/redaction/MyRedactionProcessor.cs @@ -15,18 +15,18 @@ public override void OnEnd(LogRecord logRecord) } } - internal sealed class MyClassWithRedactionEnumerator : IReadOnlyList> + internal sealed class MyClassWithRedactionEnumerator : IReadOnlyList> { - private readonly IReadOnlyList> state; + private readonly IReadOnlyList> state; - public MyClassWithRedactionEnumerator(IReadOnlyList> state) + public MyClassWithRedactionEnumerator(IReadOnlyList> state) { this.state = state; } public int Count => this.state.Count; - public KeyValuePair this[int index] + public KeyValuePair this[int index] { get { @@ -34,14 +34,14 @@ public KeyValuePair this[int index] var entryVal = item.Value?.ToString(); if (entryVal != null && entryVal.Contains("")) { - return new KeyValuePair(item.Key, "newRedactedValueHere"); + return new KeyValuePair(item.Key, "newRedactedValueHere"); } return item; } } - public IEnumerator> GetEnumerator() + public IEnumerator> GetEnumerator() { for (var i = 0; i < this.Count; i++) { diff --git a/docs/logs/redaction/redaction.csproj b/docs/logs/redaction/redaction.csproj index 2dc5d8deb6..19aa979143 100644 --- a/docs/logs/redaction/redaction.csproj +++ b/docs/logs/redaction/redaction.csproj @@ -1,8 +1,4 @@ - - - disable - diff --git a/docs/metrics/README.md b/docs/metrics/README.md index a96ade061b..0394f63451 100644 --- a/docs/metrics/README.md +++ b/docs/metrics/README.md @@ -10,11 +10,13 @@ * [Instruments](#instruments) * [MeterProvider Management](#meterprovider-management) * [Memory Management](#memory-management) + * [Example](#example) * [Pre-Aggregation](#pre-aggregation) * [Cardinality Limits](#cardinality-limits) * [Memory Preallocation](#memory-preallocation) * [Metrics Correlation](#metrics-correlation) * [Metrics Enrichment](#metrics-enrichment) +* [Common issues that lead to missing metrics](#common-issues-that-lead-to-missing-metrics)
@@ -86,7 +88,7 @@ static readonly Meter MyMeter = new("MyCompany.MyProduct.MyLibrary", "1.0"); | [Asynchronous Gauge](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#asynchronous-gauge) | [`ObservableGauge`](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.observablegauge-1) | | [Asynchronous UpDownCounter](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#asynchronous-updowncounter) | [`ObservableUpDownCounter`](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.observableupdowncounter-1) | | [Counter](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#counter) | [`Counter`](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.counter-1) | - | [Gauge](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#gauge) (experimental) | N/A | + | [Gauge](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#gauge) | [`Gauge`](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.gauge-1) | | [Histogram](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#histogram) | [`Histogram`](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.histogram-1) | | [UpDownCounter](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#updowncounter) | [`UpDownCounter`](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.updowncounter-1) | @@ -127,7 +129,7 @@ There are two different ways of passing tags to an instrument API: * Pass the tags directly to the instrument API: ```csharp - counter.Add(100, ("Key1", "Value1"), ("Key2", "Value2")); + counter.Add(100, new("Key1", "Value1"), new("Key2", "Value2")); ``` * Use @@ -386,22 +388,13 @@ and the `MetricStreamConfiguration.CardinalityLimit` setting. Refer to this [doc](../../docs/metrics/customizing-the-sdk/README.md#changing-the-cardinality-limit-for-a-metric) for more information. -Given a metric, once the cardinality limit is reached, any new measurement which -cannot be independently aggregated because of the limit will be dropped or -aggregated using the [overflow -attribute](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#overflow-attribute) -(if enabled). When NOT using the overflow attribute feature a warning is written -to the [self-diagnostic log](../../src/OpenTelemetry/README.md#self-diagnostics) -the first time an overflow is detected for a given metric. - -> [!NOTE] -> Overflow attribute was introduced in OpenTelemetry .NET - [1.6.0-rc.1](../../src/OpenTelemetry/CHANGELOG.md#160-rc1). It is currently an - experimental feature which can be turned on by setting the environment - variable `OTEL_DOTNET_EXPERIMENTAL_METRICS_EMIT_OVERFLOW_ATTRIBUTE=true`. Once - the [OpenTelemetry - Specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#overflow-attribute) - become stable, this feature will be turned on by default. +Given a metric, once the cardinality limit is reached, any new measurement +that could not be independently aggregated will be aggregated using the +[overflow attribute](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#overflow-attribute). +In versions prior to 1.10.0, the default behavior when cardinality limit was +reached was to drop the measurement. Users had the ability to opt-in to use +overflow attribute instead, but this behavior is the default and the only +allowed behavior starting with version 1.10.0. When [Delta Aggregation Temporality](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/data-model.md#temporality) diff --git a/docs/metrics/customizing-the-sdk/Program.cs b/docs/metrics/customizing-the-sdk/Program.cs index c88a295fdd..bbfd4bea98 100644 --- a/docs/metrics/customizing-the-sdk/Program.cs +++ b/docs/metrics/customizing-the-sdk/Program.cs @@ -40,6 +40,12 @@ public static void Main() // 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" }) + + // Use Base2 Exponential Bucket Histogram aggregation and new name. + .AddView(instrumentName: "histogramWithMultipleAggregations", new Base2ExponentialBucketHistogramConfiguration() { Name = "MyHistogramWithBase2ExponentialBucketHistogram" }) + // An instrument which does not match any views // gets processed with default behavior. (SDK default) // Uncommenting the following line will @@ -70,6 +76,12 @@ public static void Main() exponentialBucketHistogram.Record(random.Next(1, 1000), new("tag1", "value1"), new("tag2", "value2")); } + var histogramWithMultipleAggregations = Meter1.CreateHistogram("histogramWithMultipleAggregations"); + for (int i = 0; i < 20000; i++) + { + histogramWithMultipleAggregations.Record(random.Next(1, 1000), new("tag1", "value1"), new("tag2", "value2")); + } + var counterCustomTags = Meter1.CreateCounter("MyCounterCustomTags"); for (int i = 0; i < 20000; i++) { diff --git a/docs/metrics/customizing-the-sdk/README.md b/docs/metrics/customizing-the-sdk/README.md index 830ad66957..560149e20e 100644 --- a/docs/metrics/customizing-the-sdk/README.md +++ b/docs/metrics/customizing-the-sdk/README.md @@ -203,7 +203,7 @@ used. By default, the boundaries used for a Histogram are [`{ 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}`](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.14.0/specification/metrics/sdk.md#explicit-bucket-histogram-aggregation). Views can be used to provide custom boundaries for a Histogram. The measurements -are then aggregated using the custom boundaries provided instead of the the +are then aggregated using the custom boundaries provided instead of the default boundaries. This requires the use of `ExplicitBucketHistogramConfiguration`. @@ -245,6 +245,90 @@ within the maximum number of buckets defined by `MaxSize`. The default new Base2ExponentialBucketHistogramConfiguration { MaxSize = 40 }) ``` +#### Produce multiple metrics from single instrument + +When an instrument matches multiple views, it can generate multiple metrics. For +instance, if an instrument is matched by two different view configurations, it +will result in two separate metrics being produced from that single instrument. +Below is an example demonstrating how to leverage this capability to create two +independent metrics from a single instrument. In this example, a histogram +instrument is used to report measurements, and views are configured to produce +two metrics : one aggregated using `ExplicitBucketHistogramConfiguration` and the +other using `Base2ExponentialBucketHistogramConfiguration`. + +```csharp + var histogramWithMultipleAggregations = meter.CreateHistogram("HistogramWithMultipleAggregations"); + + // Configure the Explicit Bucket Histogram aggregation with custom boundaries and new name. + .AddView(instrumentName: "HistogramWithMultipleAggregations", new ExplicitBucketHistogramConfiguration() { Boundaries = new double[] { 10, 20 }, Name = "MyHistogramWithExplicitHistogram" }) + + // Use Base2 Exponential Bucket Histogram aggregation and new name. + .AddView(instrumentName: "HistogramWithMultipleAggregations", new Base2ExponentialBucketHistogramConfiguration() { Name = "MyHistogramWithBase2ExponentialBucketHistogram" }) + + // Both views rename the metric to avoid name conflicts. However, in this case, + // renaming one would be sufficient. + + // This measurement will be aggregated into two separate metrics. + histogramWithMultipleAggregations.Record(10, new("tag1", "value1"), new("tag2", "value2")); +``` + +When using views that produce multiple metrics from single instrument, it's +crucial to rename the metric to prevent conflicts. In the event of conflict, +OpenTelemetry will emit an internal warning but will still export both metrics. +The impact of this behavior depends on the backend or receiver being used. You +can refer to [OpenTelemetry's +specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/data-model.md#opentelemetry-protocol-data-model-consumer-recommendations) +for more details. + +Below example is showing the *BAD* practice. DO NOT FOLLOW it. + +```csharp + var histogram = meter.CreateHistogram("MyHistogram"); + + // Configure a view to aggregate based only on the "location" tag. + .AddView(instrumentName: "MyHistogram", metricStreamConfiguration: new MetricStreamConfiguration + { + TagKeys = new string[] { "location" }, + }) + + // Configure another view to aggregate based only on the "status" tag. + .AddView(instrumentName: "MyHistogram", metricStreamConfiguration: new MetricStreamConfiguration + { + TagKeys = new string[] { "status" }, + }) + + // The measurement below will be aggregated into two metric streams, but both will have the same name. + // OpenTelemetry will issue a warning about this conflict and pass both streams to the exporter. + // However, this may cause issues depending on the backend. + histogram.Record(10, new("location", "seattle"), new("status", "OK")); +``` + +The modified version, avoiding name conflict is shown below: + +```csharp + var histogram = meter.CreateHistogram("MyHistogram"); + + // Configure a view to aggregate based only on the "location" tag, + // and rename the metric. + .AddView(instrumentName: "MyHistogram", metricStreamConfiguration: new MetricStreamConfiguration + { + Name = "MyHistogramWithLocation", + TagKeys = new string[] { "location" }, + }) + + // Configure a view to aggregate based only on the "status" tag, + // and rename the metric. + .AddView(instrumentName: "MyHistogram", metricStreamConfiguration: new MetricStreamConfiguration + { + Name = "MyHistogramWithStatus", + TagKeys = new string[] { "status" }, + }) + + // The measurement below will be aggregated into two separate metrics, "MyHistogramWithLocation" + // and "MyHistogramWithStatus". + histogram.Record(10, new("location", "seattle"), new("status", "OK")); +``` + > [!NOTE] > The SDK currently does not support any changes to `Aggregation` type by using Views. @@ -319,9 +403,8 @@ metrics managed by a given `MeterProvider`, use the > [!CAUTION] > `MeterProviderBuilder.SetMaxMetricPointsPerMetricStream` is marked `Obsolete` - in pre-release builds and has been replaced by - `MetricStreamConfiguration.CardinalityLimit`. For details see: - [OTEL1003](../../diagnostics/experimental-apis/OTEL1003.md). + in stable builds since 1.10.0 and has been replaced by + `MetricStreamConfiguration.CardinalityLimit`. ```csharp using var meterProvider = Sdk.CreateMeterProviderBuilder() @@ -337,11 +420,6 @@ To set the [cardinality limit](../README.md#cardinality-limits) for an individual metric, use the `MetricStreamConfiguration.CardinalityLimit` property on the View API: -> [!NOTE] -> `MetricStreamConfiguration.CardinalityLimit` is an experimental API only - available in pre-release builds. For details see: - [OTEL1003](../../diagnostics/experimental-apis/OTEL1003.md). - ```csharp var meterProvider = Sdk.CreateMeterProviderBuilder() .AddMeter("MyCompany.MyProduct.MyLibrary") diff --git a/docs/metrics/getting-started-prometheus-grafana/README.md b/docs/metrics/getting-started-prometheus-grafana/README.md index 7f39b121a7..dfc3e09c1a 100644 --- a/docs/metrics/getting-started-prometheus-grafana/README.md +++ b/docs/metrics/getting-started-prometheus-grafana/README.md @@ -1,11 +1,16 @@ # Getting Started with Prometheus and Grafana -- [Export metrics from the application](#export-metrics-from-the-application) -- [Collect metrics using Prometheus](#collect-metrics-using-prometheus) - - [Install and run Prometheus](#install-and-run-prometheus) - - [View results in Prometheus](#view-results-in-prometheus) -- [Explore metrics using Grafana](#explore-metrics-using-grafana) -- [Learn more](#learn-more) +
+Table of Contents + +* [Export metrics from the application](#export-metrics-from-the-application) +* [Collect metrics using Prometheus](#collect-metrics-using-prometheus) + * [Install and run Prometheus](#install-and-run-prometheus) + * [View results in Prometheus](#view-results-in-prometheus) +* [Explore metrics using Grafana](#explore-metrics-using-grafana) +* [Learn more](#learn-more) + +
## Export metrics from the application @@ -140,8 +145,8 @@ UI](https://user-images.githubusercontent.com/17327289/151636769-138ecb4f-b44f-4 ## Learn more -- [What is Prometheus?](https://prometheus.io/docs/introduction/overview/) -- [Prometheus now supports OpenTelemetry +* [What is Prometheus?](https://prometheus.io/docs/introduction/overview/) +* [Prometheus now supports OpenTelemetry Metrics](https://horovits.medium.com/prometheus-now-supports-opentelemetry-metrics-83f85878e46a) -- [Grafana support for +* [Grafana support for Prometheus](https://prometheus.io/docs/visualization/grafana/#creating-a-prometheus-graph) diff --git a/docs/trace/customizing-the-sdk/customizing-the-sdk.csproj b/docs/trace/customizing-the-sdk/customizing-the-sdk.csproj index 2dc5d8deb6..19aa979143 100644 --- a/docs/trace/customizing-the-sdk/customizing-the-sdk.csproj +++ b/docs/trace/customizing-the-sdk/customizing-the-sdk.csproj @@ -1,8 +1,4 @@ - - - disable - diff --git a/docs/trace/extending-the-sdk/README.md b/docs/trace/extending-the-sdk/README.md index 4f7380ee64..db75bad713 100644 --- a/docs/trace/extending-the-sdk/README.md +++ b/docs/trace/extending-the-sdk/README.md @@ -320,10 +320,10 @@ cases, it is recommended to use that option as it offers higher performance. OpenTelemetry .NET SDK has provided the following built-in samplers: -* [AlwaysOffSampler](../../../src/OpenTelemetry/Trace/AlwaysOffSampler.cs) -* [AlwaysOnSampler](../../../src/OpenTelemetry/Trace/AlwaysOnSampler.cs) -* [ParentBasedSampler](../../../src/OpenTelemetry/Trace/ParentBasedSampler.cs) -* [TraceIdRatioBasedSampler](../../../src/OpenTelemetry/Trace/TraceIdRatioBasedSampler.cs) +* [AlwaysOffSampler](../../../src/OpenTelemetry/Trace/Sampler/AlwaysOffSampler.cs) +* [AlwaysOnSampler](../../../src/OpenTelemetry/Trace/Sampler/AlwaysOnSampler.cs) +* [ParentBasedSampler](../../../src/OpenTelemetry/Trace/Sampler/ParentBasedSampler.cs) +* [TraceIdRatioBasedSampler](../../../src/OpenTelemetry/Trace/Sampler/TraceIdRatioBasedSampler.cs) Custom samplers can be implemented to cover more scenarios: @@ -338,7 +338,7 @@ class MySampler : Sampler { public override SamplingResult ShouldSample(in SamplingParameters samplingParameters) { - return new SamplingResult(SamplingDecision.RecordAndSampled); + return new SamplingResult(SamplingDecision.RecordAndSample); } } ``` diff --git a/docs/trace/getting-started-aspnetcore/README.md b/docs/trace/getting-started-aspnetcore/README.md index a9b7adf0b3..7febcf4d55 100644 --- a/docs/trace/getting-started-aspnetcore/README.md +++ b/docs/trace/getting-started-aspnetcore/README.md @@ -30,30 +30,30 @@ in the console for your application (ex `http://localhost:5154`). You should see the trace output from the console. ```text -Activity.TraceId: c1572aa14ee9c0ac037dbdc3e91e5dd7 -Activity.SpanId: 45406137f33cc279 +Activity.TraceId: c28f7b480d5c7dfc30cfbd80ad29028d +Activity.SpanId: 27e478bbf9fdec10 Activity.TraceFlags: Recorded -Activity.ActivitySourceName: OpenTelemetry.Instrumentation.AspNetCore -Activity.DisplayName: / +Activity.ActivitySourceName: Microsoft.AspNetCore +Activity.DisplayName: GET / Activity.Kind: Server -Activity.StartTime: 2023-01-13T19:38:11.5417593Z -Activity.Duration: 00:00:00.0167407 +Activity.StartTime: 2024-07-04T13:03:37.3318740Z +Activity.Duration: 00:00:00.3693734 Activity.Tags: - net.host.name: localhost - net.host.port: 5154 - http.method: GET - http.scheme: http - http.target: / - http.url: http://localhost:5154/ - http.flavor: 1.1 - http.user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.76 - http.status_code: 200 + server.address: localhost + server.port: 5154 + http.request.method: GET + url.scheme: https + url.path: / + network.protocol.version: 2 + user_agent.original: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 + http.route: / + http.response.status_code: 200 Resource associated with Activity: service.name: getting-started-aspnetcore - service.instance.id: 32c9371c-ed9d-474c-a698-b169e87a5577 + service.instance.id: a388466b-4969-4bb0-ad96-8f39527fa66b telemetry.sdk.name: opentelemetry telemetry.sdk.language: dotnet - telemetry.sdk.version: 1.5.1 + telemetry.sdk.version: 1.9.0 ``` Congratulations! You are now collecting traces using OpenTelemetry. diff --git a/docs/trace/getting-started-jaeger/README.md b/docs/trace/getting-started-jaeger/README.md index 4f8e36c387..92752ff66d 100644 --- a/docs/trace/getting-started-jaeger/README.md +++ b/docs/trace/getting-started-jaeger/README.md @@ -1,10 +1,15 @@ # Getting Started with Jaeger -- [Export traces from the application](#export-traces-from-the-application) - - [Check results in the console](#check-results-in-the-console) -- [Collect and visualize traces using Jaeger](#collect-and-visualize-traces-using-jaeger) -- [Final cleanup](#final-cleanup) -- [Learn more](#learn-more) +
+Table of Contents + +* [Export traces from the application](#export-traces-from-the-application) + * [Check results in the console](#check-results-in-the-console) +* [Collect and visualize traces using Jaeger](#collect-and-visualize-traces-using-jaeger) +* [Final cleanup](#final-cleanup) +* [Learn more](#learn-more) + +
## Export traces from the application @@ -42,25 +47,29 @@ Run the application again and we should see the trace output from the console: ```text > dotnet run -Activity.TraceId: a80c920e0aabb50b547e2bb7455cfd39 -Activity.SpanId: 4e45a1d51744f329 -Activity.TraceFlags: Recorded -Activity.ParentSpanId: 4f7e9b78c55dcfad -Activity.ActivitySourceName: OpenTelemetry.Instrumentation.Http -Activity.DisplayName: HTTP GET -Activity.Kind: Client -Activity.StartTime: 2022-05-07T02:54:25.7840762Z -Activity.Duration: 00:00:01.9615540 +Activity.TraceId: 693f1d15634bfe6ba3254d6f9d20df27 +Activity.SpanId: 429cc5a90a753fb3 +Activity.TraceFlags: Recorded +Activity.ParentSpanId: 0d64498b736c9a11 +Activity.ActivitySourceName: System.Net.Http +Activity.DisplayName: GET +Activity.Kind: Client +Activity.StartTime: 2024-07-04T13:18:12.2408786Z +Activity.Duration: 00:00:02.1028562 Activity.Tags: - http.method: GET - http.host: httpstat.us - http.url: https://httpstat.us/200?sleep=1000 - http.status_code: 200 + http.request.method: GET + server.address: httpstat.us + server.port: 443 + url.full: https://httpstat.us/200?sleep=Redacted + network.protocol.version: 1.1 + http.response.status_code: 200 Resource associated with Activity: service.name: DemoApp service.version: 1.0.0 - service.instance.id: 1b3b3a6f-be43-46b0-819a-4db1200c633d - + service.instance.id: 03ccafab-e9a7-440a-a9cd-9a0163e0d06c + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: dotnet + telemetry.sdk.version: 1.9.0 ... ``` @@ -178,6 +187,6 @@ BatchExportProcessor --> | Batch | OtlpExporter ## Learn more -- [Jaeger Tracing](https://www.jaegertracing.io/) -- [OTLP Exporter for OpenTelemetry +* [Jaeger Tracing](https://www.jaegertracing.io/) +* [OTLP Exporter for OpenTelemetry .NET](../../../src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md) diff --git a/examples/AspNetCore/Program.cs b/examples/AspNetCore/Program.cs index 89ac46932d..809534f5c7 100644 --- a/examples/AspNetCore/Program.cs +++ b/examples/AspNetCore/Program.cs @@ -24,20 +24,21 @@ // Note: Switch between Explicit/Exponential by setting HistogramAggregation in appsettings.json var histogramAggregation = appBuilder.Configuration.GetValue("HistogramAggregation", defaultValue: "explicit")!.ToLowerInvariant(); -// Build a resource configuration action to set service information. -Action configureResource = r => r.AddService( - serviceName: appBuilder.Configuration.GetValue("ServiceName", defaultValue: "otel-test")!, - serviceVersion: typeof(Program).Assembly.GetName().Version?.ToString() ?? "unknown", - serviceInstanceId: Environment.MachineName); - // Create a service to expose ActivitySource, and Metric Instruments // for manual instrumentation appBuilder.Services.AddSingleton(); -// Configure OpenTelemetry tracing & metrics with auto-start using the +// Clear default logging providers used by WebApplication host. +appBuilder.Logging.ClearProviders(); + +// Configure OpenTelemetry logging, metrics, & tracing with auto-start using the // AddOpenTelemetry extension from OpenTelemetry.Extensions.Hosting. appBuilder.Services.AddOpenTelemetry() - .ConfigureResource(configureResource) + .ConfigureResource(r => r + .AddService( + serviceName: appBuilder.Configuration.GetValue("ServiceName", defaultValue: "otel-test")!, + serviceVersion: typeof(Program).Assembly.GetName().Version?.ToString() ?? "unknown", + serviceInstanceId: Environment.MachineName)) .WithTracing(builder => { // Tracing @@ -121,34 +122,25 @@ builder.AddConsoleExporter(); break; } - }); - -// Clear default logging providers used by WebApplication host. -appBuilder.Logging.ClearProviders(); - -// Configure OpenTelemetry Logging. -appBuilder.Logging.AddOpenTelemetry(options => -{ - // Note: See appsettings.json Logging:OpenTelemetry section for configuration. - - var resourceBuilder = ResourceBuilder.CreateDefault(); - configureResource(resourceBuilder); - options.SetResourceBuilder(resourceBuilder); - - switch (logExporter) + }) + .WithLogging(builder => { - case "otlp": - options.AddOtlpExporter(otlpOptions => - { - // Use IConfiguration directly for Otlp exporter endpoint option. - otlpOptions.Endpoint = new Uri(appBuilder.Configuration.GetValue("Otlp:Endpoint", defaultValue: "http://localhost:4317")!); - }); - break; - default: - options.AddConsoleExporter(); - break; - } -}); + // Note: See appsettings.json Logging:OpenTelemetry section for configuration. + + switch (logExporter) + { + 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")!); + }); + break; + default: + builder.AddConsoleExporter(); + break; + } + }); appBuilder.Services.AddControllers(); diff --git a/docs/metrics/exemplars/docker-compose.yaml b/examples/AspNetCore/docker-compose.yaml similarity index 96% rename from docs/metrics/exemplars/docker-compose.yaml rename to examples/AspNetCore/docker-compose.yaml index c8cc94fa4b..7cdb95106e 100644 --- a/docs/metrics/exemplars/docker-compose.yaml +++ b/examples/AspNetCore/docker-compose.yaml @@ -3,7 +3,7 @@ services: # OTEL Collector to receive logs, metrics and traces from the application otel-collector: - image: otel/opentelemetry-collector:0.70.0 + image: otel/opentelemetry-collector:0.111.0 command: [ "--config=/etc/otel-collector.yaml" ] volumes: - ./otel-collector.yaml:/etc/otel-collector.yaml diff --git a/docs/metrics/exemplars/grafana-datasources.yaml b/examples/AspNetCore/grafana-datasources.yaml similarity index 100% rename from docs/metrics/exemplars/grafana-datasources.yaml rename to examples/AspNetCore/grafana-datasources.yaml diff --git a/docs/metrics/exemplars/otel-collector.yaml b/examples/AspNetCore/otel-collector.yaml similarity index 67% rename from docs/metrics/exemplars/otel-collector.yaml rename to examples/AspNetCore/otel-collector.yaml index bcf0cb5d6d..fa9999d66c 100644 --- a/docs/metrics/exemplars/otel-collector.yaml +++ b/examples/AspNetCore/otel-collector.yaml @@ -2,11 +2,13 @@ receivers: otlp: protocols: grpc: + endpoint: 0.0.0.0:4317 http: + endpoint: 0.0.0.0:4318 exporters: - logging: - loglevel: debug + debug: + verbosity: detailed prometheus: endpoint: ":9201" send_timestamps: true @@ -21,10 +23,10 @@ service: pipelines: traces: receivers: [otlp] - exporters: [logging,otlp] + exporters: [debug, otlp] metrics: receivers: [otlp] - exporters: [logging,prometheus] + exporters: [debug, prometheus] logs: receivers: [otlp] - exporters: [logging] + exporters: [debug] diff --git a/docs/metrics/exemplars/prometheus.yaml b/examples/AspNetCore/prometheus.yaml similarity index 100% rename from docs/metrics/exemplars/prometheus.yaml rename to examples/AspNetCore/prometheus.yaml diff --git a/docs/metrics/exemplars/tempo.yaml b/examples/AspNetCore/tempo.yaml similarity index 100% rename from docs/metrics/exemplars/tempo.yaml rename to examples/AspNetCore/tempo.yaml diff --git a/examples/Console/Examples.Console.csproj b/examples/Console/Examples.Console.csproj index d2b74f2e64..86c6b1df03 100644 --- a/examples/Console/Examples.Console.csproj +++ b/examples/Console/Examples.Console.csproj @@ -4,9 +4,6 @@ Exe $(DefaultTargetFrameworkForExampleApps) $(NoWarn),CS0618 - - - disable
diff --git a/examples/Console/InstrumentationWithActivitySource.cs b/examples/Console/InstrumentationWithActivitySource.cs index 240f58289d..706d68ad99 100644 --- a/examples/Console/InstrumentationWithActivitySource.cs +++ b/examples/Console/InstrumentationWithActivitySource.cs @@ -47,13 +47,13 @@ public void Start(string url) var context = this.listener.GetContext(); using var activity = source.StartActivity( - $"{context.Request.HttpMethod}:{context.Request.Url.AbsolutePath}", + $"{context.Request.HttpMethod}:{context.Request.Url!.AbsolutePath}", ActivityKind.Server); var headerKeys = context.Request.Headers.AllKeys; foreach (var headerKey in headerKeys) { - string headerValue = context.Request.Headers[headerKey]; + string? headerValue = context.Request.Headers[headerKey]; activity?.SetTag($"http.header.{headerKey}", headerValue); } @@ -62,7 +62,7 @@ public void Start(string url) using (var reader = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding)) { requestContent = reader.ReadToEnd(); - childSpan.AddEvent(new ActivityEvent("StreamReader.ReadToEnd")); + childSpan?.AddEvent(new ActivityEvent("StreamReader.ReadToEnd")); } activity?.SetTag("request.content", requestContent); @@ -90,8 +90,8 @@ public void Dispose() private class SampleClient : IDisposable { - private CancellationTokenSource cts; - private Task requestTask; + private CancellationTokenSource? cts; + private Task? requestTask; public void Start(string url) { @@ -154,7 +154,7 @@ public void Dispose() if (this.cts != null) { this.cts.Cancel(); - this.requestTask.Wait(); + this.requestTask!.Wait(); this.requestTask.Dispose(); this.cts.Dispose(); } diff --git a/examples/Console/Program.cs b/examples/Console/Program.cs index 2b93979e07..bca2e4339c 100644 --- a/examples/Console/Program.cs +++ b/examples/Console/Program.cs @@ -33,16 +33,16 @@ public static void Main(string[] args) { Parser.Default.ParseArguments(args) .MapResult( - (ZipkinOptions options) => TestZipkinExporter.Run(options.Uri), - (PrometheusOptions options) => TestPrometheusExporter.Run(options.Port), + (ZipkinOptions options) => TestZipkinExporter.Run(options), + (PrometheusOptions options) => TestPrometheusExporter.Run(options), (MetricsOptions options) => TestMetrics.Run(options), (LogsOptions options) => TestLogs.Run(options), - (GrpcNetClientOptions options) => TestGrpcNetClient.Run(), - (HttpClientOptions options) => TestHttpClient.Run(), + (GrpcNetClientOptions options) => TestGrpcNetClient.Run(options), + (HttpClientOptions options) => TestHttpClient.Run(options), (ConsoleOptions options) => TestConsoleExporter.Run(options), (OpenTelemetryShimOptions options) => TestOTelShimWithConsoleExporter.Run(options), (OpenTracingShimOptions options) => TestOpenTracingShim.Run(options), - (OtlpOptions options) => TestOtlpExporter.Run(options.Endpoint, options.Protocol), + (OtlpOptions options) => TestOtlpExporter.Run(options), (InMemoryOptions options) => TestInMemoryExporter.Run(options), errs => 1); } @@ -54,7 +54,7 @@ public static void Main(string[] args) internal class ZipkinOptions { [Option('u', "uri", HelpText = "Please specify the uri of Zipkin backend", Required = true)] - public string Uri { get; set; } + public required string Uri { get; set; } } [Verb("prometheus", HelpText = "Specify the options required to test Prometheus")] @@ -83,10 +83,10 @@ internal class MetricsOptions public int DefaultCollectionPeriodMilliseconds { get; set; } [Option("useExporter", Default = "console", HelpText = "Options include otlp or console.", Required = false)] - public string UseExporter { get; set; } + public string? UseExporter { get; set; } [Option('e', "endpoint", HelpText = "Target to which the exporter is going to send metrics (default value depends on protocol).", Default = null)] - public string Endpoint { get; set; } + public string? Endpoint { get; set; } [Option('p', "useGrpc", HelpText = "Use gRPC or HTTP when using the OTLP exporter", Required = false, Default = true)] public bool UseGrpc { get; set; } @@ -121,26 +121,26 @@ internal class OpenTracingShimOptions internal 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; } + public string? Endpoint { get; set; } [Option('p', "protocol", HelpText = "Transport protocol used by exporter. Supported values: grpc and http/protobuf.", Default = "grpc")] - public string Protocol { get; set; } + public string? Protocol { get; set; } } [Verb("logs", HelpText = "Specify the options required to test Logs")] internal class LogsOptions { [Option("useExporter", Default = "otlp", HelpText = "Options include otlp or console.", Required = false)] - public string UseExporter { get; set; } + public string? UseExporter { get; set; } [Option('e', "endpoint", HelpText = "Target to which the OTLP exporter is going to send logs (default value depends on protocol).", Default = null)] - public string Endpoint { get; set; } + public string? Endpoint { get; set; } [Option('p', "protocol", HelpText = "Transport protocol used by OTLP exporter. Supported values: grpc and http/protobuf. Only applicable if Exporter is OTLP", Default = "grpc")] - public string Protocol { get; set; } + public string? Protocol { get; set; } [Option("processorType", Default = "batch", HelpText = "export processor type. Supported values: simple and batch", Required = false)] - public string ProcessorType { get; set; } + public string? ProcessorType { get; set; } [Option("scheduledDelay", Default = 5000, HelpText = "The delay interval in milliseconds between two consecutive exports.", Required = false)] public int ScheduledDelayInMilliseconds { get; set; } diff --git a/examples/Console/TestConsoleExporter.cs b/examples/Console/TestConsoleExporter.cs index cf3f803c4f..7a70c0ef1e 100644 --- a/examples/Console/TestConsoleExporter.cs +++ b/examples/Console/TestConsoleExporter.cs @@ -15,21 +15,21 @@ internal class TestConsoleExporter // (eg: C:\repos\opentelemetry-dotnet\examples\Console\) // // dotnet run console - internal static object Run(ConsoleOptions options) + internal static int Run(ConsoleOptions options) { return RunWithActivitySource(); } - private static object RunWithActivitySource() + private static int RunWithActivitySource() { // Enable OpenTelemetry for the sources "Samples.SampleServer" and "Samples.SampleClient" // and use Console exporter. using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddSource("Samples.SampleClient", "Samples.SampleServer") - .ConfigureResource(res => res.AddService("console-test")) - .AddProcessor(new MyProcessor()) // This must be added before ConsoleExporter - .AddConsoleExporter() - .Build(); + .AddSource("Samples.SampleClient", "Samples.SampleServer") + .ConfigureResource(res => res.AddService("console-test")) + .AddProcessor(new MyProcessor()) // This must be added before ConsoleExporter + .AddConsoleExporter() + .Build(); // The above line is required only in applications // which decide to use OpenTelemetry. @@ -43,7 +43,7 @@ private static object RunWithActivitySource() System.Console.ReadLine(); } - return null; + return 0; } /// diff --git a/examples/Console/TestGrpcNetClient.cs b/examples/Console/TestGrpcNetClient.cs index d179d93913..31dce4a167 100644 --- a/examples/Console/TestGrpcNetClient.cs +++ b/examples/Console/TestGrpcNetClient.cs @@ -12,8 +12,10 @@ namespace Examples.Console; internal class TestGrpcNetClient { - internal static object Run() + internal static int Run(GrpcNetClientOptions options) { + Debug.Assert(options != null, "options was null"); + // Prerequisite for running this example. // In a separate console window, start the example // ASP.NET Core gRPC service by running the following command @@ -55,6 +57,6 @@ internal static object Run() System.Console.WriteLine("Press Enter key to exit."); System.Console.ReadLine(); - return null; + return 0; } } diff --git a/examples/Console/TestHttpClient.cs b/examples/Console/TestHttpClient.cs index 27305a5052..7629a622af 100644 --- a/examples/Console/TestHttpClient.cs +++ b/examples/Console/TestHttpClient.cs @@ -15,8 +15,10 @@ internal class TestHttpClient // (eg: C:\repos\opentelemetry-dotnet\examples\Console\) // // dotnet run httpclient - internal static object Run() + internal static int Run(HttpClientOptions options) { + Debug.Assert(options != null, "options was null"); + System.Console.WriteLine("Hello World!"); using var tracerProvider = Sdk.CreateTracerProviderBuilder() @@ -36,6 +38,6 @@ internal static object Run() System.Console.WriteLine("Press Enter key to exit."); System.Console.ReadLine(); - return null; + return 0; } } diff --git a/examples/Console/TestInMemoryExporter.cs b/examples/Console/TestInMemoryExporter.cs index 53d3dd7f49..057bc19cde 100644 --- a/examples/Console/TestInMemoryExporter.cs +++ b/examples/Console/TestInMemoryExporter.cs @@ -15,7 +15,7 @@ internal class TestInMemoryExporter // (eg: C:\repos\opentelemetry-dotnet\examples\Console\) // // dotnet run inmemory - internal static object Run(InMemoryOptions options) + internal static int Run(InMemoryOptions options) { // List that will be populated with the traces by InMemoryExporter var exportedItems = new List(); @@ -28,7 +28,7 @@ internal static object Run(InMemoryOptions options) System.Console.WriteLine($"ActivitySource: {activity.Source.Name} logged the activity {activity.DisplayName}"); } - return null; + return 0; } private static void RunWithActivitySource(ICollection exportedItems) @@ -36,10 +36,10 @@ private static void RunWithActivitySource(ICollection exportedItems) // Enable OpenTelemetry for the sources "Samples.SampleServer" and "Samples.SampleClient" // and use InMemory exporter. using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddSource("Samples.SampleClient", "Samples.SampleServer") - .ConfigureResource(r => r.AddService("inmemory-test")) - .AddInMemoryExporter(exportedItems) - .Build(); + .AddSource("Samples.SampleClient", "Samples.SampleServer") + .ConfigureResource(r => r.AddService("inmemory-test")) + .AddInMemoryExporter(exportedItems) + .Build(); // The above line is required only in applications // which decide to use OpenTelemetry. diff --git a/examples/Console/TestLogs.cs b/examples/Console/TestLogs.cs index 4c88753ffd..427193799a 100644 --- a/examples/Console/TestLogs.cs +++ b/examples/Console/TestLogs.cs @@ -9,7 +9,7 @@ namespace Examples.Console; internal class TestLogs { - internal static object Run(LogsOptions options) + internal static int Run(LogsOptions options) { using var loggerFactory = LoggerFactory.Create(builder => { @@ -17,7 +17,8 @@ internal static object Run(LogsOptions options) { opt.IncludeFormattedMessage = true; opt.IncludeScopes = true; - if (options.UseExporter.Equals("otlp", StringComparison.OrdinalIgnoreCase)) + + if ("otlp".Equals(options.UseExporter, StringComparison.OrdinalIgnoreCase)) { /* * Prerequisite to run this example: @@ -43,31 +44,46 @@ internal static object Run(LogsOptions options) var protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc; - if (options.Protocol.Trim().ToLower().Equals("grpc")) - { - protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc; - } - else if (options.Protocol.Trim().ToLower().Equals("http/protobuf")) + if (!string.IsNullOrEmpty(options.Protocol)) { - protocol = OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf; + switch (options.Protocol.Trim()) + { + case "grpc": + protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc; + break; + case "http/protobuf": + protocol = OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf; + break; + default: + System.Console.WriteLine($"Export protocol {options.Protocol} is not supported. Default protocol 'grpc' will be used."); + break; + } } else { - System.Console.WriteLine($"Export protocol {options.Protocol} is not supported. Default protocol 'grpc' will be used."); + System.Console.WriteLine("Protocol is null or empty. Default protocol 'grpc' will be used."); } var processorType = ExportProcessorType.Batch; - if (options.ProcessorType.Trim().ToLower().Equals("batch")) - { - processorType = ExportProcessorType.Batch; - } - else if (options.ProcessorType.Trim().ToLower().Equals("simple")) + + if (!string.IsNullOrEmpty(options.ProcessorType)) { - processorType = ExportProcessorType.Simple; + switch (options.ProcessorType.Trim()) + { + case "batch": + processorType = ExportProcessorType.Batch; + break; + case "simple": + processorType = ExportProcessorType.Simple; + break; + default: + System.Console.WriteLine($"Export processor type {options.ProcessorType} is not supported. Default processor type 'batch' will be used."); + break; + } } else { - System.Console.WriteLine($"Export processor type {options.ProcessorType} is not supported. Default processor type 'batch' will be used."); + System.Console.WriteLine("Processor type is null or empty. Default processor type 'batch' will be used."); } opt.AddOtlpExporter((exporterOptions, processorOptions) => @@ -103,6 +119,6 @@ internal static object Run(LogsOptions options) logger.LogInformation("Hello from {name} {price}.", "tomato", 2.99); } - return null; + return 0; } } diff --git a/examples/Console/TestMetrics.cs b/examples/Console/TestMetrics.cs index ba943e9177..9582a0f561 100644 --- a/examples/Console/TestMetrics.cs +++ b/examples/Console/TestMetrics.cs @@ -12,10 +12,10 @@ namespace Examples.Console; internal class TestMetrics { - internal static object Run(MetricsOptions options) + internal static int Run(MetricsOptions options) { var meterVersion = "1.0"; - var meterTags = new List> + var meterTags = new List> { new( "MeterTagKey", @@ -27,7 +27,7 @@ internal static object Run(MetricsOptions options) .ConfigureResource(r => r.AddService("myservice")) .AddMeter(meter.Name); // All instruments from this meter are enabled. - if (options.UseExporter.Equals("otlp", StringComparison.OrdinalIgnoreCase)) + if ("otlp".Equals(options.UseExporter, StringComparison.OrdinalIgnoreCase)) { /* * Prerequisite to run this example: @@ -79,13 +79,13 @@ internal static object Run(MetricsOptions options) using var provider = providerBuilder.Build(); - Counter counter = null; + Counter? counter = null; if (options.FlagCounter ?? true) { counter = meter.CreateCounter("counter", "things", "A count of things"); } - Histogram histogram = null; + Histogram? histogram = null; if (options.FlagHistogram ?? false) { histogram = meter.CreateHistogram("histogram"); @@ -99,7 +99,7 @@ internal static object Run(MetricsOptions options) { new Measurement( (int)Process.GetCurrentProcess().PrivateMemorySize64, - new KeyValuePair("tag1", "value1")), + new KeyValuePair("tag1", "value1")), }; }); } @@ -111,45 +111,45 @@ internal static object Run(MetricsOptions options) histogram?.Record( 100, - new KeyValuePair("tag1", "value1")); + new KeyValuePair("tag1", "value1")); histogram?.Record( 200, - new KeyValuePair("tag1", "value2"), - new KeyValuePair("tag2", "value2")); + new KeyValuePair("tag1", "value2"), + new KeyValuePair("tag2", "value2")); histogram?.Record( 100, - new KeyValuePair("tag1", "value1")); + new KeyValuePair("tag1", "value1")); histogram?.Record( 200, - new KeyValuePair("tag2", "value2"), - new KeyValuePair("tag1", "value2")); + new KeyValuePair("tag2", "value2"), + new KeyValuePair("tag1", "value2")); counter?.Add(10); counter?.Add( 100, - new KeyValuePair("tag1", "value1")); + new KeyValuePair("tag1", "value1")); counter?.Add( 200, - new KeyValuePair("tag1", "value2"), - new KeyValuePair("tag2", "value2")); + new KeyValuePair("tag1", "value2"), + new KeyValuePair("tag2", "value2")); counter?.Add( 100, - new KeyValuePair("tag1", "value1")); + new KeyValuePair("tag1", "value1")); counter?.Add( 200, - new KeyValuePair("tag2", "value2"), - new KeyValuePair("tag1", "value2")); + new KeyValuePair("tag2", "value2"), + new KeyValuePair("tag1", "value2")); Task.Delay(500).Wait(); } - return null; + return 0; } } diff --git a/examples/Console/TestOTelShimWithConsoleExporter.cs b/examples/Console/TestOTelShimWithConsoleExporter.cs index 6557357a96..42e6f368f5 100644 --- a/examples/Console/TestOTelShimWithConsoleExporter.cs +++ b/examples/Console/TestOTelShimWithConsoleExporter.cs @@ -9,15 +9,15 @@ namespace Examples.Console; internal class TestOTelShimWithConsoleExporter { - internal static object Run(OpenTelemetryShimOptions options) + internal static int Run(OpenTelemetryShimOptions options) { // Enable OpenTelemetry for the source "MyCompany.MyProduct.MyWebServer" // and use a single pipeline with a custom MyProcessor, and Console exporter. using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddSource("MyCompany.MyProduct.MyWebServer") - .ConfigureResource(r => r.AddService("MyServiceName")) - .AddConsoleExporter() - .Build(); + .AddSource("MyCompany.MyProduct.MyWebServer") + .ConfigureResource(r => r.AddService("MyServiceName")) + .AddConsoleExporter() + .Build(); // The above line is required only in applications // which decide to use OpenTelemetry. @@ -40,6 +40,6 @@ internal static object Run(OpenTelemetryShimOptions options) System.Console.WriteLine("Press Enter key to exit."); System.Console.ReadLine(); - return null; + return 0; } } diff --git a/examples/Console/TestOpenTracingShim.cs b/examples/Console/TestOpenTracingShim.cs index 0de2419b6e..386cfe48ab 100644 --- a/examples/Console/TestOpenTracingShim.cs +++ b/examples/Console/TestOpenTracingShim.cs @@ -12,15 +12,15 @@ namespace Examples.Console; internal class TestOpenTracingShim { - internal static object Run(OpenTracingShimOptions options) + internal static int Run(OpenTracingShimOptions options) { // Enable OpenTelemetry for the source "opentracing-shim" // and use Console exporter. using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddSource("opentracing-shim") - .ConfigureResource(r => r.AddService("MyServiceName")) - .AddConsoleExporter() - .Build(); + .AddSource("opentracing-shim") + .ConfigureResource(r => r.AddService("MyServiceName")) + .AddConsoleExporter() + .Build(); // Instantiate the OpenTracing shim. The underlying OpenTelemetry tracer will create // spans using the "opentracing-shim" source. @@ -53,6 +53,6 @@ internal static object Run(OpenTracingShimOptions options) System.Console.WriteLine("Press Enter key to exit."); System.Console.ReadLine(); - return null; + return 0; } } diff --git a/examples/Console/TestOtlpExporter.cs b/examples/Console/TestOtlpExporter.cs index 94d749eafe..3af0ceea14 100644 --- a/examples/Console/TestOtlpExporter.cs +++ b/examples/Console/TestOtlpExporter.cs @@ -10,7 +10,7 @@ namespace Examples.Console; internal static class TestOtlpExporter { - internal static object Run(string endpoint, string protocol) + internal static int Run(OtlpOptions options) { /* * Prerequisite to run this example: @@ -36,36 +36,36 @@ internal static object Run(string endpoint, string protocol) * For more information about the OpenTelemetry Collector go to https://github.com/open-telemetry/opentelemetry-collector * */ - return RunWithActivitySource(endpoint, protocol); + return RunWithActivitySource(options); } - private static object RunWithActivitySource(string endpoint, string protocol) + private static int RunWithActivitySource(OtlpOptions options) { - var otlpExportProtocol = ToOtlpExportProtocol(protocol); + var otlpExportProtocol = ToOtlpExportProtocol(options.Protocol); if (!otlpExportProtocol.HasValue) { - System.Console.WriteLine($"Export protocol {protocol} is not supported. Default protocol 'grpc' will be used."); + System.Console.WriteLine($"Export protocol {options.Protocol} is not supported. Default protocol 'grpc' will be used."); otlpExportProtocol = OtlpExportProtocol.Grpc; } // Enable OpenTelemetry for the sources "Samples.SampleServer" and "Samples.SampleClient" // and use OTLP exporter. using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddSource("Samples.SampleClient", "Samples.SampleServer") - .ConfigureResource(r => r.AddService("otlp-test")) - .AddOtlpExporter(opt => + .AddSource("Samples.SampleClient", "Samples.SampleServer") + .ConfigureResource(r => r.AddService("otlp-test")) + .AddOtlpExporter(opt => + { + // If endpoint was not specified, the proper one will be selected according to the protocol. + if (!string.IsNullOrEmpty(options.Endpoint)) { - // If endpoint was not specified, the proper one will be selected according to the protocol. - if (!string.IsNullOrEmpty(endpoint)) - { - opt.Endpoint = new Uri(endpoint); - } + opt.Endpoint = new Uri(options.Endpoint); + } - opt.Protocol = otlpExportProtocol.Value; + opt.Protocol = otlpExportProtocol.Value; - System.Console.WriteLine($"OTLP Exporter is using {opt.Protocol} protocol and endpoint {opt.Endpoint}"); - }) - .Build(); + System.Console.WriteLine($"OTLP Exporter is using {opt.Protocol} protocol and endpoint {opt.Endpoint}"); + }) + .Build(); // The above line is required only in Applications // which decide to use OpenTelemetry. @@ -79,11 +79,11 @@ private static object RunWithActivitySource(string endpoint, string protocol) System.Console.ReadLine(); } - return null; + return 0; } - private static OtlpExportProtocol? ToOtlpExportProtocol(string protocol) => - protocol.Trim().ToLower() switch + private static OtlpExportProtocol? ToOtlpExportProtocol(string? protocol) => + protocol?.Trim().ToLower() switch { "grpc" => OtlpExportProtocol.Grpc, "http/protobuf" => OtlpExportProtocol.HttpProtobuf, diff --git a/examples/Console/TestPrometheusExporter.cs b/examples/Console/TestPrometheusExporter.cs index 57f38c466d..a6bc1888f2 100644 --- a/examples/Console/TestPrometheusExporter.cs +++ b/examples/Console/TestPrometheusExporter.cs @@ -17,7 +17,7 @@ internal class TestPrometheusExporter private static readonly Histogram MyHistogram = MyMeter.CreateHistogram("myHistogram"); private static readonly ThreadLocal ThreadLocalRandom = new(() => new Random()); - internal static object Run(int port) + internal static int Run(PrometheusOptions options) { /* prometheus.yml example. Adjust port as per actual. @@ -35,7 +35,7 @@ internal static object Run(int port) .AddMeter(MyMeter.Name) .AddMeter(MyMeter2.Name) .AddPrometheusHttpListener( - options => options.UriPrefixes = new string[] { $"http://localhost:{port}/" }) + o => o.UriPrefixes = new string[] { $"http://localhost:{options.Port}/" }) .Build(); var process = Process.GetCurrentProcess(); @@ -57,12 +57,12 @@ internal static object Run(int port) { 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(ThreadLocalRandom.Value!.Next(1, 1500), new("tag1", "value1"), new("tag2", "value2")); Task.Delay(10).Wait(); } }); - System.Console.WriteLine($"PrometheusExporter exposes metrics via http://localhost:{port}/metrics/"); + System.Console.WriteLine($"PrometheusExporter exposes metrics via http://localhost:{options.Port}/metrics/"); System.Console.WriteLine($"Press Esc key to exit..."); while (true) { @@ -80,7 +80,7 @@ internal static object Run(int port) Task.Delay(200).Wait(); } - return null; + return 0; } private static IEnumerable> GetThreadCpuTime(Process process) diff --git a/examples/Console/TestZipkinExporter.cs b/examples/Console/TestZipkinExporter.cs index 271826a7c2..0bb6432344 100644 --- a/examples/Console/TestZipkinExporter.cs +++ b/examples/Console/TestZipkinExporter.cs @@ -9,7 +9,7 @@ namespace Examples.Console; internal class TestZipkinExporter { - internal static object Run(string zipkinUri) + internal static int Run(ZipkinOptions options) { // Prerequisite for running this example. // Setup zipkin inside local docker using following command: @@ -23,14 +23,15 @@ internal static object Run(string zipkinUri) // Enable OpenTelemetry for the sources "Samples.SampleServer" and "Samples.SampleClient" // and use the Zipkin exporter. + using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddSource("Samples.SampleClient", "Samples.SampleServer") - .ConfigureResource(r => r.AddService("zipkin-test")) - .AddZipkinExporter(o => - { - o.Endpoint = new Uri(zipkinUri); - }) - .Build(); + .AddSource("Samples.SampleClient", "Samples.SampleServer") + .ConfigureResource(r => r.AddService("zipkin-test")) + .AddZipkinExporter(o => + { + o.Endpoint = new Uri(options.Uri); + }) + .Build(); using (var sample = new InstrumentationWithActivitySource()) { @@ -42,6 +43,6 @@ internal static object Run(string zipkinUri) System.Console.ReadLine(); } - return null; + return 0; } } diff --git a/examples/Console/otlp-collector-example/config.yaml b/examples/Console/otlp-collector-example/config.yaml index 932f24a193..57e59398b7 100644 --- a/examples/Console/otlp-collector-example/config.yaml +++ b/examples/Console/otlp-collector-example/config.yaml @@ -8,20 +8,22 @@ receivers: otlp: protocols: grpc: + endpoint: 0.0.0.0:4317 http: + endpoint: 0.0.0.0:4318 exporters: - logging: + debug: verbosity: detailed service: pipelines: traces: receivers: [otlp] - exporters: [logging] + exporters: [debug] metrics: receivers: [otlp] - exporters: [logging] + exporters: [debug] logs: receivers: [otlp] - exporters: [logging] + exporters: [debug] diff --git a/examples/Directory.Build.targets b/examples/Directory.Build.targets new file mode 100644 index 0000000000..a0db146202 --- /dev/null +++ b/examples/Directory.Build.targets @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Directory.Packages.props b/examples/Directory.Packages.props deleted file mode 100644 index 902efc8cc0..0000000000 --- a/examples/Directory.Packages.props +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs b/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs index 476bc26a85..cbc8f0d834 100644 --- a/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs +++ b/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs @@ -54,7 +54,7 @@ public static void StartConsumer(IModel channel, Action p channel.BasicConsume(queue: TestQueueName, autoAck: true, consumer: consumer); } - public static void AddMessagingTags(Activity activity) + public static void AddMessagingTags(Activity? activity) { // These tags are added demonstrating the semantic conventions of the OpenTelemetry messaging specification // See: diff --git a/examples/MicroserviceExample/Utils/Utils.csproj b/examples/MicroserviceExample/Utils/Utils.csproj index cc7382d499..54d0724347 100644 --- a/examples/MicroserviceExample/Utils/Utils.csproj +++ b/examples/MicroserviceExample/Utils/Utils.csproj @@ -1,9 +1,6 @@ netstandard2.0 - - - disable diff --git a/global.json b/global.json index 0aca8b1293..f459bbb82e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { "rollForward": "latestFeature", - "version": "8.0.100" + "version": "9.0.100-rc.1.24452.12" } } diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index ef4301eb07..6cef70da6c 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -1,5 +1,7 @@ + + - - - (this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder) -> OpenTelemetry.Logs.LoggerProviderBuilder! -static OpenTelemetry.Logs.OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, System.Func! instrumentationFactory) -> OpenTelemetry.Logs.LoggerProviderBuilder! -static OpenTelemetry.Logs.OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, System.Func! instrumentationFactory) -> OpenTelemetry.Logs.LoggerProviderBuilder! -static OpenTelemetry.Logs.OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, T! instrumentation) -> OpenTelemetry.Logs.LoggerProviderBuilder! -static OpenTelemetry.Logs.OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.ConfigureServices(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, System.Action! configure) -> OpenTelemetry.Logs.LoggerProviderBuilder! -static OpenTelemetry.Logs.OpenTelemetryDependencyInjectionLoggingServiceCollectionExtensions.ConfigureOpenTelemetryLoggerProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action! configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -static OpenTelemetry.Logs.OpenTelemetryDependencyInjectionLoggingServiceCollectionExtensions.ConfigureOpenTelemetryLoggerProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action! configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! diff --git a/src/OpenTelemetry.Api.ProviderBuilderExtensions/.publicApi/Stable/PublicAPI.Shipped.txt b/src/OpenTelemetry.Api.ProviderBuilderExtensions/.publicApi/Stable/PublicAPI.Shipped.txt index ca48e185f6..8c11dafee8 100644 --- a/src/OpenTelemetry.Api.ProviderBuilderExtensions/.publicApi/Stable/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry.Api.ProviderBuilderExtensions/.publicApi/Stable/PublicAPI.Shipped.txt @@ -1,21 +1,30 @@ #nullable enable OpenTelemetry.IOpenTelemetryBuilder OpenTelemetry.IOpenTelemetryBuilder.Services.get -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +OpenTelemetry.Logs.OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions +OpenTelemetry.Logs.OpenTelemetryDependencyInjectionLoggingServiceCollectionExtensions OpenTelemetry.Metrics.OpenTelemetryDependencyInjectionMeterProviderBuilderExtensions OpenTelemetry.Metrics.OpenTelemetryDependencyInjectionMetricsServiceCollectionExtensions OpenTelemetry.Trace.OpenTelemetryDependencyInjectionTracerProviderBuilderExtensions OpenTelemetry.Trace.OpenTelemetryDependencyInjectionTracingServiceCollectionExtensions -static OpenTelemetry.Metrics.OpenTelemetryDependencyInjectionMeterProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Logs.OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, System.Func! instrumentationFactory) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, System.Func! instrumentationFactory) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, T! instrumentation) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.ConfigureServices(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, System.Action! configure) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.OpenTelemetryDependencyInjectionLoggingServiceCollectionExtensions.ConfigureOpenTelemetryLoggerProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action! configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +static OpenTelemetry.Logs.OpenTelemetryDependencyInjectionLoggingServiceCollectionExtensions.ConfigureOpenTelemetryLoggerProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action! configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static OpenTelemetry.Metrics.OpenTelemetryDependencyInjectionMeterProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, System.Func! instrumentationFactory) -> OpenTelemetry.Metrics.MeterProviderBuilder! static OpenTelemetry.Metrics.OpenTelemetryDependencyInjectionMeterProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, System.Func! instrumentationFactory) -> OpenTelemetry.Metrics.MeterProviderBuilder! static OpenTelemetry.Metrics.OpenTelemetryDependencyInjectionMeterProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, T! instrumentation) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.OpenTelemetryDependencyInjectionMeterProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder) -> OpenTelemetry.Metrics.MeterProviderBuilder! static OpenTelemetry.Metrics.OpenTelemetryDependencyInjectionMeterProviderBuilderExtensions.ConfigureServices(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, System.Action! configure) -> OpenTelemetry.Metrics.MeterProviderBuilder! static OpenTelemetry.Metrics.OpenTelemetryDependencyInjectionMetricsServiceCollectionExtensions.ConfigureOpenTelemetryMeterProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action! configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static OpenTelemetry.Metrics.OpenTelemetryDependencyInjectionMetricsServiceCollectionExtensions.ConfigureOpenTelemetryMeterProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action! configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -static OpenTelemetry.Trace.OpenTelemetryDependencyInjectionTracerProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! tracerProviderBuilder) -> OpenTelemetry.Trace.TracerProviderBuilder! static OpenTelemetry.Trace.OpenTelemetryDependencyInjectionTracerProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! tracerProviderBuilder, System.Func! instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder! static OpenTelemetry.Trace.OpenTelemetryDependencyInjectionTracerProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! tracerProviderBuilder, System.Func! instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder! static OpenTelemetry.Trace.OpenTelemetryDependencyInjectionTracerProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! tracerProviderBuilder, T! instrumentation) -> OpenTelemetry.Trace.TracerProviderBuilder! +static OpenTelemetry.Trace.OpenTelemetryDependencyInjectionTracerProviderBuilderExtensions.AddInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! tracerProviderBuilder) -> OpenTelemetry.Trace.TracerProviderBuilder! static OpenTelemetry.Trace.OpenTelemetryDependencyInjectionTracerProviderBuilderExtensions.ConfigureServices(this OpenTelemetry.Trace.TracerProviderBuilder! tracerProviderBuilder, System.Action! configure) -> OpenTelemetry.Trace.TracerProviderBuilder! static OpenTelemetry.Trace.OpenTelemetryDependencyInjectionTracingServiceCollectionExtensions.ConfigureOpenTelemetryTracerProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action! configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static OpenTelemetry.Trace.OpenTelemetryDependencyInjectionTracingServiceCollectionExtensions.ConfigureOpenTelemetryTracerProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action! configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! diff --git a/src/OpenTelemetry.Api.ProviderBuilderExtensions/CHANGELOG.md b/src/OpenTelemetry.Api.ProviderBuilderExtensions/CHANGELOG.md index 1f2a27b7f3..1b39470bda 100644 --- a/src/OpenTelemetry.Api.ProviderBuilderExtensions/CHANGELOG.md +++ b/src/OpenTelemetry.Api.ProviderBuilderExtensions/CHANGELOG.md @@ -1,7 +1,34 @@ # Changelog +This file contains individual changes for the +OpenTelemetry.Api.ProviderBuilderExtensions package. For highlights and +announcements covering all components see: [Release +Notes](../../RELEASENOTES.md). + ## Unreleased +## 1.10.0-beta.1 + +Released 2024-Sep-30 + +* Updated `Microsoft.Extensions.DependencyInjection.Abstractions` package + version to `9.0.0-rc.1.24431.7`. + ([#5853](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5853)) + +## 1.9.0 + +Released 2024-Jun-14 + +## 1.9.0-rc.1 + +Released 2024-Jun-07 + +* The experimental APIs previously covered by `OTEL1000` + (`LoggerProviderBuilder` `AddInstrumentation` & `ConfigureServices` extensions + and `IServiceCollection.ConfigureOpenTelemetryLoggerProvider` extension) are + now part of the public API and supported in stable builds. + ([#5648](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5648)) + ## 1.9.0-alpha.1 Released 2024-May-20 diff --git a/src/OpenTelemetry.Api.ProviderBuilderExtensions/Logs/OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.cs b/src/OpenTelemetry.Api.ProviderBuilderExtensions/Logs/OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.cs index 257b7332be..4a96f1b081 100644 --- a/src/OpenTelemetry.Api.ProviderBuilderExtensions/Logs/OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Api.ProviderBuilderExtensions/Logs/OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NET6_0_OR_GREATER +#if NET using System.Diagnostics.CodeAnalysis; #endif using Microsoft.Extensions.DependencyInjection; @@ -13,42 +13,20 @@ namespace OpenTelemetry.Logs; /// /// Contains extension methods for the class. /// -#if EXPOSE_EXPERIMENTAL_FEATURES -#if NET8_0_OR_GREATER -[Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif -public -#else -internal -#endif -static class OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions +public static class OpenTelemetryDependencyInjectionLoggerProviderBuilderExtensions { -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Adds instrumentation to the provider. /// /// - /// /// Note: The type specified by will be /// registered as a singleton service into application services. /// /// Instrumentation type. /// . /// The supplied for chaining. -#else - /// - /// Adds instrumentation to the provider. - /// - /// - /// Note: The type specified by will be - /// registered as a singleton service into application services. - /// - /// Instrumentation type. - /// . - /// The supplied for chaining. -#endif public static LoggerProviderBuilder AddInstrumentation< -#if NET6_0_OR_GREATER +#if NET [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T>(this LoggerProviderBuilder loggerProviderBuilder) @@ -64,24 +42,13 @@ public static LoggerProviderBuilder AddInstrumentation< return loggerProviderBuilder; } -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Adds instrumentation to the provider. /// /// Instrumentation type. - /// WARNING: This is an experimental API which might change or be removed in the future. Use at your own risk. /// . /// Instrumentation instance. /// The supplied for chaining. -#else - /// - /// Adds instrumentation to the provider. - /// - /// Instrumentation type. - /// . - /// Instrumentation instance. - /// The supplied for chaining. -#endif public static LoggerProviderBuilder AddInstrumentation(this LoggerProviderBuilder loggerProviderBuilder, T instrumentation) where T : class { @@ -95,24 +62,13 @@ public static LoggerProviderBuilder AddInstrumentation(this LoggerProviderBui return loggerProviderBuilder; } -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Adds instrumentation to the provider. /// /// Instrumentation type. - /// /// . /// Instrumentation factory. /// The supplied for chaining. -#else - /// - /// Adds instrumentation to the provider. - /// - /// Instrumentation type. - /// . - /// Instrumentation factory. - /// The supplied for chaining. -#endif public static LoggerProviderBuilder AddInstrumentation( this LoggerProviderBuilder loggerProviderBuilder, Func instrumentationFactory) @@ -128,16 +84,6 @@ public static LoggerProviderBuilder AddInstrumentation( return loggerProviderBuilder; } -#if EXPOSE_EXPERIMENTAL_FEATURES - /// - /// Adds instrumentation to the provider. - /// - /// Instrumentation type. - /// - /// . - /// Instrumentation factory. - /// The supplied for chaining. -#else /// /// Adds instrumentation to the provider. /// @@ -145,7 +91,6 @@ public static LoggerProviderBuilder AddInstrumentation( /// . /// Instrumentation factory. /// The supplied for chaining. -#endif public static LoggerProviderBuilder AddInstrumentation( this LoggerProviderBuilder loggerProviderBuilder, Func instrumentationFactory) @@ -165,32 +110,17 @@ public static LoggerProviderBuilder AddInstrumentation( return loggerProviderBuilder; } -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Register a callback action to configure the where logging services are configured. /// /// - /// /// Note: Logging services are only available during the application /// configuration phase. /// /// . /// Configuration callback. /// The supplied for chaining. -#else - /// - /// Register a callback action to configure the where logging services are configured. - /// - /// - /// Note: Logging services are only available during the application - /// configuration phase. - /// - /// . - /// Configuration callback. - /// The supplied for chaining. -#endif public static LoggerProviderBuilder ConfigureServices( this LoggerProviderBuilder loggerProviderBuilder, Action configure) diff --git a/src/OpenTelemetry.Api.ProviderBuilderExtensions/Logs/OpenTelemetryDependencyInjectionLoggingServiceCollectionExtensions.cs b/src/OpenTelemetry.Api.ProviderBuilderExtensions/Logs/OpenTelemetryDependencyInjectionLoggingServiceCollectionExtensions.cs index 8e6f899b24..8d33a420a6 100644 --- a/src/OpenTelemetry.Api.ProviderBuilderExtensions/Logs/OpenTelemetryDependencyInjectionLoggingServiceCollectionExtensions.cs +++ b/src/OpenTelemetry.Api.ProviderBuilderExtensions/Logs/OpenTelemetryDependencyInjectionLoggingServiceCollectionExtensions.cs @@ -1,9 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if EXPOSE_EXPERIMENTAL_FEATURES && NET8_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -#endif using Microsoft.Extensions.DependencyInjection; using OpenTelemetry.Internal; @@ -12,23 +9,13 @@ namespace OpenTelemetry.Logs; /// /// Extension methods for setting up OpenTelemetry logging services in an . /// -#if EXPOSE_EXPERIMENTAL_FEATURES -#if NET8_0_OR_GREATER -[Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif -public -#else -internal -#endif -static class OpenTelemetryDependencyInjectionLoggingServiceCollectionExtensions +public static class OpenTelemetryDependencyInjectionLoggingServiceCollectionExtensions { -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Registers an action used to configure the OpenTelemetry . /// /// - /// /// Notes: /// /// This is safe to be called multiple times and by library authors. @@ -45,29 +32,6 @@ static class OpenTelemetryDependencyInjectionLoggingServiceCollectionExtensions /// cref="LoggerProviderBuilder"/>. /// The so that additional calls /// can be chained. -#else - /// - /// Registers an action used to configure the OpenTelemetry . - /// - /// - /// Notes: - /// - /// This is safe to be called multiple times and by library authors. - /// Each registered configuration action will be applied - /// sequentially. - /// A will NOT be created automatically - /// using this method. To begin collecting logs use the - /// IServiceCollection.AddOpenTelemetry extension in the - /// OpenTelemetry.Extensions.Hosting package. - /// - /// - /// . - /// Callback action to configure the . - /// The so that additional calls - /// can be chained. -#endif public static IServiceCollection ConfigureOpenTelemetryLoggerProvider( this IServiceCollection services, Action configure) @@ -80,40 +44,6 @@ public static IServiceCollection ConfigureOpenTelemetryLoggerProvider( return services; } -#if EXPOSE_EXPERIMENTAL_FEATURES - /// - /// Registers an action used to configure the OpenTelemetry once the - /// is available. - /// - /// - /// - /// Notes: - /// - /// This is safe to be called multiple times and by library authors. - /// Each registered configuration action will be applied - /// sequentially. - /// A will NOT be created automatically - /// using this method. To begin collecting logs use the - /// IServiceCollection.AddOpenTelemetry extension in the - /// OpenTelemetry.Extensions.Hosting package. - /// The supplied configuration delegate is called once the is available. Services may NOT be added to a - /// once the has been created. Many helper extensions - /// register services and may throw if invoked inside the configuration - /// delegate. If you don't need access to the - /// call instead which is safe to be used with - /// helper extensions. - /// - /// - /// . - /// Callback action to configure the . - /// The so that additional calls - /// can be chained. -#else /// /// Registers an action used to configure the OpenTelemetry once the @@ -145,7 +75,6 @@ public static IServiceCollection ConfigureOpenTelemetryLoggerProvider( /// cref="LoggerProviderBuilder"/>. /// The so that additional calls /// can be chained. -#endif public static IServiceCollection ConfigureOpenTelemetryLoggerProvider( this IServiceCollection services, Action configure) diff --git a/src/OpenTelemetry.Api.ProviderBuilderExtensions/Metrics/OpenTelemetryDependencyInjectionMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Api.ProviderBuilderExtensions/Metrics/OpenTelemetryDependencyInjectionMeterProviderBuilderExtensions.cs index 457ddc8766..fea2275e9a 100644 --- a/src/OpenTelemetry.Api.ProviderBuilderExtensions/Metrics/OpenTelemetryDependencyInjectionMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Api.ProviderBuilderExtensions/Metrics/OpenTelemetryDependencyInjectionMeterProviderBuilderExtensions.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NET6_0_OR_GREATER +#if NET using System.Diagnostics.CodeAnalysis; #endif using Microsoft.Extensions.DependencyInjection; @@ -26,7 +26,7 @@ public static class OpenTelemetryDependencyInjectionMeterProviderBuilderExtensio /// . /// The supplied for chaining. public static MeterProviderBuilder AddInstrumentation< -#if NET6_0_OR_GREATER +#if NET [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T>(this MeterProviderBuilder meterProviderBuilder) diff --git a/src/OpenTelemetry.Api.ProviderBuilderExtensions/Trace/OpenTelemetryDependencyInjectionTracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Api.ProviderBuilderExtensions/Trace/OpenTelemetryDependencyInjectionTracerProviderBuilderExtensions.cs index 35088b2a5e..2e6ab37628 100644 --- a/src/OpenTelemetry.Api.ProviderBuilderExtensions/Trace/OpenTelemetryDependencyInjectionTracerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Api.ProviderBuilderExtensions/Trace/OpenTelemetryDependencyInjectionTracerProviderBuilderExtensions.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NET6_0_OR_GREATER +#if NET using System.Diagnostics.CodeAnalysis; #endif using Microsoft.Extensions.DependencyInjection; @@ -26,7 +26,7 @@ public static class OpenTelemetryDependencyInjectionTracerProviderBuilderExtensi /// . /// The supplied for chaining. public static TracerProviderBuilder AddInstrumentation< -#if NET6_0_OR_GREATER +#if NET [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T>(this TracerProviderBuilder tracerProviderBuilder) diff --git a/src/OpenTelemetry.Api/.publicApi/Experimental/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Api/.publicApi/Experimental/PublicAPI.Unshipped.txt index 2c2dcbb59e..4cb12fd296 100644 --- a/src/OpenTelemetry.Api/.publicApi/Experimental/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Api/.publicApi/Experimental/PublicAPI.Unshipped.txt @@ -1,19 +1,12 @@ abstract OpenTelemetry.Logs.Logger.EmitLog(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributeList attributes) -> void -abstract OpenTelemetry.Logs.LoggerProviderBuilder.AddInstrumentation(System.Func! instrumentationFactory) -> OpenTelemetry.Logs.LoggerProviderBuilder! -OpenTelemetry.Logs.IDeferredLoggerProviderBuilder -OpenTelemetry.Logs.IDeferredLoggerProviderBuilder.Configure(System.Action! configure) -> OpenTelemetry.Logs.LoggerProviderBuilder! 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 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.LoggerProvider.LoggerProvider() -> void -OpenTelemetry.Logs.LoggerProviderBuilder -OpenTelemetry.Logs.LoggerProviderBuilder.LoggerProviderBuilder() -> void OpenTelemetry.Logs.LogRecordAttributeList OpenTelemetry.Logs.LogRecordAttributeList.Add(string! key, object? value) -> void OpenTelemetry.Logs.LogRecordAttributeList.Add(System.Collections.Generic.KeyValuePair attribute) -> void diff --git a/src/OpenTelemetry.Api/.publicApi/Stable/PublicAPI.Shipped.txt b/src/OpenTelemetry.Api/.publicApi/Stable/PublicAPI.Shipped.txt index a87bb70121..821cf08067 100644 --- a/src/OpenTelemetry.Api/.publicApi/Stable/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry.Api/.publicApi/Stable/PublicAPI.Shipped.txt @@ -1,57 +1,18 @@ #nullable enable -~abstract OpenTelemetry.Context.Propagation.TextMapPropagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func> getter) -> OpenTelemetry.Context.Propagation.PropagationContext -~abstract OpenTelemetry.Context.Propagation.TextMapPropagator.Fields.get -> System.Collections.Generic.ISet -~abstract OpenTelemetry.Context.Propagation.TextMapPropagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action setter) -> void -~OpenTelemetry.Baggage.GetBaggage() -> System.Collections.Generic.IReadOnlyDictionary -~OpenTelemetry.Baggage.GetBaggage(string name) -> string -~OpenTelemetry.Baggage.GetEnumerator() -> System.Collections.Generic.Dictionary.Enumerator -~OpenTelemetry.Baggage.RemoveBaggage(string name) -> OpenTelemetry.Baggage -~OpenTelemetry.Baggage.SetBaggage(params System.Collections.Generic.KeyValuePair[] baggageItems) -> OpenTelemetry.Baggage -~OpenTelemetry.Baggage.SetBaggage(string name, string value) -> OpenTelemetry.Baggage -~OpenTelemetry.Baggage.SetBaggage(System.Collections.Generic.IEnumerable> baggageItems) -> OpenTelemetry.Baggage -~OpenTelemetry.Context.AsyncLocalRuntimeContextSlot.AsyncLocalRuntimeContextSlot(string name) -> void -~OpenTelemetry.Context.AsyncLocalRuntimeContextSlot.Value.get -> object -~OpenTelemetry.Context.AsyncLocalRuntimeContextSlot.Value.set -> void -~OpenTelemetry.Context.IRuntimeContextSlotValueAccessor.Value.get -> object -~OpenTelemetry.Context.IRuntimeContextSlotValueAccessor.Value.set -> void -~OpenTelemetry.Context.Propagation.CompositeTextMapPropagator.CompositeTextMapPropagator(System.Collections.Generic.IEnumerable propagators) -> void -~OpenTelemetry.Context.RuntimeContextSlot.Name.get -> string -~OpenTelemetry.Context.RuntimeContextSlot.RuntimeContextSlot(string name) -> void -~OpenTelemetry.Context.ThreadLocalRuntimeContextSlot.ThreadLocalRuntimeContextSlot(string name) -> void -~OpenTelemetry.Context.ThreadLocalRuntimeContextSlot.Value.get -> object -~OpenTelemetry.Context.ThreadLocalRuntimeContextSlot.Value.set -> void -~override OpenTelemetry.Baggage.Equals(object obj) -> bool -~override OpenTelemetry.Context.Propagation.B3Propagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func> getter) -> OpenTelemetry.Context.Propagation.PropagationContext -~override OpenTelemetry.Context.Propagation.B3Propagator.Fields.get -> System.Collections.Generic.ISet -~override OpenTelemetry.Context.Propagation.B3Propagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action setter) -> void -~override OpenTelemetry.Context.Propagation.BaggagePropagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func> getter) -> OpenTelemetry.Context.Propagation.PropagationContext -~override OpenTelemetry.Context.Propagation.BaggagePropagator.Fields.get -> System.Collections.Generic.ISet -~override OpenTelemetry.Context.Propagation.BaggagePropagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action setter) -> void -~override OpenTelemetry.Context.Propagation.CompositeTextMapPropagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func> getter) -> OpenTelemetry.Context.Propagation.PropagationContext -~override OpenTelemetry.Context.Propagation.CompositeTextMapPropagator.Fields.get -> System.Collections.Generic.ISet -~override OpenTelemetry.Context.Propagation.CompositeTextMapPropagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action setter) -> void -~override OpenTelemetry.Context.Propagation.PropagationContext.Equals(object obj) -> bool -~override OpenTelemetry.Context.Propagation.TraceContextPropagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func> getter) -> OpenTelemetry.Context.Propagation.PropagationContext -~override OpenTelemetry.Context.Propagation.TraceContextPropagator.Fields.get -> System.Collections.Generic.ISet -~override OpenTelemetry.Context.Propagation.TraceContextPropagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action setter) -> void -~static OpenTelemetry.Baggage.Create(System.Collections.Generic.Dictionary baggageItems = null) -> OpenTelemetry.Baggage -~static OpenTelemetry.Baggage.GetBaggage(OpenTelemetry.Baggage baggage = default(OpenTelemetry.Baggage)) -> System.Collections.Generic.IReadOnlyDictionary -~static OpenTelemetry.Baggage.GetBaggage(string name, OpenTelemetry.Baggage baggage = default(OpenTelemetry.Baggage)) -> string -~static OpenTelemetry.Baggage.GetEnumerator(OpenTelemetry.Baggage baggage = default(OpenTelemetry.Baggage)) -> System.Collections.Generic.Dictionary.Enumerator -~static OpenTelemetry.Baggage.RemoveBaggage(string name, OpenTelemetry.Baggage baggage = default(OpenTelemetry.Baggage)) -> OpenTelemetry.Baggage -~static OpenTelemetry.Baggage.SetBaggage(string name, string value, OpenTelemetry.Baggage baggage = default(OpenTelemetry.Baggage)) -> OpenTelemetry.Baggage -~static OpenTelemetry.Baggage.SetBaggage(System.Collections.Generic.IEnumerable> baggageItems, OpenTelemetry.Baggage baggage = default(OpenTelemetry.Baggage)) -> OpenTelemetry.Baggage -~static OpenTelemetry.Context.Propagation.Propagators.DefaultTextMapPropagator.get -> OpenTelemetry.Context.Propagation.TextMapPropagator -~static OpenTelemetry.Context.RuntimeContext.ContextSlotType.get -> System.Type -~static OpenTelemetry.Context.RuntimeContext.ContextSlotType.set -> void -~static OpenTelemetry.Context.RuntimeContext.GetSlot(string slotName) -> OpenTelemetry.Context.RuntimeContextSlot -~static OpenTelemetry.Context.RuntimeContext.GetValue(string slotName) -> object -~static OpenTelemetry.Context.RuntimeContext.GetValue(string slotName) -> T -~static OpenTelemetry.Context.RuntimeContext.RegisterSlot(string slotName) -> OpenTelemetry.Context.RuntimeContextSlot -~static OpenTelemetry.Context.RuntimeContext.SetValue(string slotName, object value) -> void -~static OpenTelemetry.Context.RuntimeContext.SetValue(string slotName, T value) -> void -abstract OpenTelemetry.Context.RuntimeContextSlot.Get() -> T +static OpenTelemetry.Context.RuntimeContext.ContextSlotType.get -> System.Type! +static OpenTelemetry.Context.RuntimeContext.ContextSlotType.set -> void +static OpenTelemetry.Context.RuntimeContext.GetSlot(string! slotName) -> OpenTelemetry.Context.RuntimeContextSlot! +static OpenTelemetry.Context.RuntimeContext.GetValue(string! slotName) -> object? +static OpenTelemetry.Context.RuntimeContext.GetValue(string! slotName) -> T? +static OpenTelemetry.Context.RuntimeContext.RegisterSlot(string! slotName) -> OpenTelemetry.Context.RuntimeContextSlot! +static OpenTelemetry.Context.RuntimeContext.SetValue(string! slotName, object? value) -> void +static OpenTelemetry.Context.RuntimeContext.SetValue(string! slotName, T value) -> void +abstract OpenTelemetry.Context.Propagation.TextMapPropagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func?>! getter) -> OpenTelemetry.Context.Propagation.PropagationContext +abstract OpenTelemetry.Context.Propagation.TextMapPropagator.Fields.get -> System.Collections.Generic.ISet? +abstract OpenTelemetry.Context.Propagation.TextMapPropagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action! setter) -> void +abstract OpenTelemetry.Context.RuntimeContextSlot.Get() -> T? abstract OpenTelemetry.Context.RuntimeContextSlot.Set(T value) -> void +abstract OpenTelemetry.Logs.LoggerProviderBuilder.AddInstrumentation(System.Func! instrumentationFactory) -> OpenTelemetry.Logs.LoggerProviderBuilder! abstract OpenTelemetry.Metrics.MeterProviderBuilder.AddInstrumentation(System.Func! instrumentationFactory) -> OpenTelemetry.Metrics.MeterProviderBuilder! abstract OpenTelemetry.Metrics.MeterProviderBuilder.AddMeter(params string![]! names) -> OpenTelemetry.Metrics.MeterProviderBuilder! abstract OpenTelemetry.Trace.TracerProviderBuilder.AddInstrumentation(System.Func! instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder! @@ -63,15 +24,28 @@ OpenTelemetry.Baggage.Baggage() -> void OpenTelemetry.Baggage.ClearBaggage() -> OpenTelemetry.Baggage OpenTelemetry.Baggage.Count.get -> int OpenTelemetry.Baggage.Equals(OpenTelemetry.Baggage other) -> bool +OpenTelemetry.Baggage.GetBaggage() -> System.Collections.Generic.IReadOnlyDictionary! +OpenTelemetry.Baggage.GetBaggage(string! name) -> string? +OpenTelemetry.Baggage.GetEnumerator() -> System.Collections.Generic.Dictionary.Enumerator +OpenTelemetry.Baggage.RemoveBaggage(string! name) -> OpenTelemetry.Baggage +OpenTelemetry.Baggage.SetBaggage(params System.Collections.Generic.KeyValuePair[]! baggageItems) -> OpenTelemetry.Baggage +OpenTelemetry.Baggage.SetBaggage(string! name, string? value) -> OpenTelemetry.Baggage +OpenTelemetry.Baggage.SetBaggage(System.Collections.Generic.IEnumerable>! baggageItems) -> OpenTelemetry.Baggage OpenTelemetry.BaseProvider OpenTelemetry.BaseProvider.~BaseProvider() -> void OpenTelemetry.BaseProvider.BaseProvider() -> void OpenTelemetry.BaseProvider.Dispose() -> void OpenTelemetry.Context.AsyncLocalRuntimeContextSlot +OpenTelemetry.Context.AsyncLocalRuntimeContextSlot.AsyncLocalRuntimeContextSlot(string! name) -> void +OpenTelemetry.Context.AsyncLocalRuntimeContextSlot.Value.get -> object? +OpenTelemetry.Context.AsyncLocalRuntimeContextSlot.Value.set -> void OpenTelemetry.Context.IRuntimeContextSlotValueAccessor +OpenTelemetry.Context.IRuntimeContextSlotValueAccessor.Value.get -> object? +OpenTelemetry.Context.IRuntimeContextSlotValueAccessor.Value.set -> void OpenTelemetry.Context.Propagation.B3Propagator OpenTelemetry.Context.Propagation.B3Propagator.B3Propagator() -> void OpenTelemetry.Context.Propagation.B3Propagator.B3Propagator(bool singleHeader) -> void +OpenTelemetry.Context.Propagation.CompositeTextMapPropagator.CompositeTextMapPropagator(System.Collections.Generic.IEnumerable! propagators) -> void OpenTelemetry.Context.Propagation.BaggagePropagator OpenTelemetry.Context.Propagation.BaggagePropagator.BaggagePropagator() -> void OpenTelemetry.Context.Propagation.CompositeTextMapPropagator @@ -89,7 +63,18 @@ OpenTelemetry.Context.Propagation.TraceContextPropagator.TraceContextPropagator( OpenTelemetry.Context.RuntimeContext OpenTelemetry.Context.RuntimeContextSlot OpenTelemetry.Context.RuntimeContextSlot.Dispose() -> void +OpenTelemetry.Context.RuntimeContextSlot.Name.get -> string! +OpenTelemetry.Context.RuntimeContextSlot.RuntimeContextSlot(string! name) -> void OpenTelemetry.Context.ThreadLocalRuntimeContextSlot +OpenTelemetry.Context.ThreadLocalRuntimeContextSlot.ThreadLocalRuntimeContextSlot(string! name) -> void +OpenTelemetry.Context.ThreadLocalRuntimeContextSlot.Value.get -> object? +OpenTelemetry.Context.ThreadLocalRuntimeContextSlot.Value.set -> void +OpenTelemetry.Logs.IDeferredLoggerProviderBuilder +OpenTelemetry.Logs.IDeferredLoggerProviderBuilder.Configure(System.Action! configure) -> OpenTelemetry.Logs.LoggerProviderBuilder! +OpenTelemetry.Logs.LoggerProvider +OpenTelemetry.Logs.LoggerProvider.LoggerProvider() -> void +OpenTelemetry.Logs.LoggerProviderBuilder +OpenTelemetry.Logs.LoggerProviderBuilder.LoggerProviderBuilder() -> void OpenTelemetry.Metrics.IDeferredMeterProviderBuilder OpenTelemetry.Metrics.IDeferredMeterProviderBuilder.Configure(System.Action! configure) -> OpenTelemetry.Metrics.MeterProviderBuilder! OpenTelemetry.Metrics.MeterProvider @@ -104,8 +89,8 @@ OpenTelemetry.Trace.Link.Attributes.get -> System.Collections.Generic.IEnumerabl OpenTelemetry.Trace.Link.Context.get -> OpenTelemetry.Trace.SpanContext OpenTelemetry.Trace.Link.Equals(OpenTelemetry.Trace.Link other) -> bool OpenTelemetry.Trace.Link.Link() -> void -OpenTelemetry.Trace.Link.Link(in OpenTelemetry.Trace.SpanContext spanContext) -> void OpenTelemetry.Trace.Link.Link(in OpenTelemetry.Trace.SpanContext spanContext, OpenTelemetry.Trace.SpanAttributes? attributes) -> void +OpenTelemetry.Trace.Link.Link(in OpenTelemetry.Trace.SpanContext spanContext) -> void OpenTelemetry.Trace.SpanAttributes OpenTelemetry.Trace.SpanAttributes.Add(string! key, bool value) -> void OpenTelemetry.Trace.SpanAttributes.Add(string! key, bool[]? values) -> void @@ -145,10 +130,10 @@ OpenTelemetry.Trace.StatusCode.Error = 2 -> OpenTelemetry.Trace.StatusCode OpenTelemetry.Trace.StatusCode.Ok = 1 -> OpenTelemetry.Trace.StatusCode OpenTelemetry.Trace.StatusCode.Unset = 0 -> OpenTelemetry.Trace.StatusCode OpenTelemetry.Trace.TelemetrySpan -OpenTelemetry.Trace.TelemetrySpan.AddEvent(string! name) -> OpenTelemetry.Trace.TelemetrySpan! OpenTelemetry.Trace.TelemetrySpan.AddEvent(string! name, 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, 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.Context.get -> OpenTelemetry.Trace.SpanContext OpenTelemetry.Trace.TelemetrySpan.Dispose() -> void OpenTelemetry.Trace.TelemetrySpan.End() -> void @@ -178,12 +163,26 @@ OpenTelemetry.Trace.TracerProvider.GetTracer(string! name, string? version = nul OpenTelemetry.Trace.TracerProvider.TracerProvider() -> void OpenTelemetry.Trace.TracerProviderBuilder OpenTelemetry.Trace.TracerProviderBuilder.TracerProviderBuilder() -> void +override OpenTelemetry.Baggage.Equals(object? obj) -> bool override OpenTelemetry.Baggage.GetHashCode() -> int -override OpenTelemetry.Context.AsyncLocalRuntimeContextSlot.Get() -> T +override OpenTelemetry.Context.AsyncLocalRuntimeContextSlot.Get() -> T? override OpenTelemetry.Context.AsyncLocalRuntimeContextSlot.Set(T value) -> void +override OpenTelemetry.Context.Propagation.B3Propagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func?>! getter) -> OpenTelemetry.Context.Propagation.PropagationContext +override OpenTelemetry.Context.Propagation.B3Propagator.Fields.get -> System.Collections.Generic.ISet! +override OpenTelemetry.Context.Propagation.B3Propagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action! setter) -> void +override OpenTelemetry.Context.Propagation.BaggagePropagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func?>! getter) -> OpenTelemetry.Context.Propagation.PropagationContext +override OpenTelemetry.Context.Propagation.BaggagePropagator.Fields.get -> System.Collections.Generic.ISet! +override OpenTelemetry.Context.Propagation.BaggagePropagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action! setter) -> void +override OpenTelemetry.Context.Propagation.CompositeTextMapPropagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func?>! getter) -> OpenTelemetry.Context.Propagation.PropagationContext +override OpenTelemetry.Context.Propagation.CompositeTextMapPropagator.Fields.get -> System.Collections.Generic.ISet! +override OpenTelemetry.Context.Propagation.CompositeTextMapPropagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action! setter) -> void override OpenTelemetry.Context.Propagation.PropagationContext.GetHashCode() -> int +override OpenTelemetry.Context.Propagation.PropagationContext.Equals(object? obj) -> bool +override OpenTelemetry.Context.Propagation.TraceContextPropagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func?>! getter) -> OpenTelemetry.Context.Propagation.PropagationContext +override OpenTelemetry.Context.Propagation.TraceContextPropagator.Fields.get -> System.Collections.Generic.ISet! +override OpenTelemetry.Context.Propagation.TraceContextPropagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action! setter) -> void override OpenTelemetry.Context.ThreadLocalRuntimeContextSlot.Dispose(bool disposing) -> void -override OpenTelemetry.Context.ThreadLocalRuntimeContextSlot.Get() -> T +override OpenTelemetry.Context.ThreadLocalRuntimeContextSlot.Get() -> T? override OpenTelemetry.Context.ThreadLocalRuntimeContextSlot.Set(T value) -> void override OpenTelemetry.Trace.Link.Equals(object? obj) -> bool override OpenTelemetry.Trace.Link.GetHashCode() -> int @@ -195,16 +194,24 @@ override OpenTelemetry.Trace.Status.ToString() -> string! override OpenTelemetry.Trace.TracerProvider.Dispose(bool disposing) -> void static OpenTelemetry.ActivityContextExtensions.IsValid(this System.Diagnostics.ActivityContext ctx) -> bool static OpenTelemetry.Baggage.ClearBaggage(OpenTelemetry.Baggage baggage = default(OpenTelemetry.Baggage)) -> OpenTelemetry.Baggage +static OpenTelemetry.Baggage.Create(System.Collections.Generic.Dictionary? baggageItems = null) -> OpenTelemetry.Baggage static OpenTelemetry.Baggage.Current.get -> OpenTelemetry.Baggage static OpenTelemetry.Baggage.Current.set -> void +static OpenTelemetry.Baggage.GetBaggage(OpenTelemetry.Baggage baggage = default(OpenTelemetry.Baggage)) -> System.Collections.Generic.IReadOnlyDictionary! +static OpenTelemetry.Baggage.GetBaggage(string! name, OpenTelemetry.Baggage baggage = default(OpenTelemetry.Baggage)) -> string? +static OpenTelemetry.Baggage.GetEnumerator(OpenTelemetry.Baggage baggage = default(OpenTelemetry.Baggage)) -> System.Collections.Generic.Dictionary.Enumerator static OpenTelemetry.Baggage.operator !=(OpenTelemetry.Baggage left, OpenTelemetry.Baggage right) -> bool static OpenTelemetry.Baggage.operator ==(OpenTelemetry.Baggage left, OpenTelemetry.Baggage right) -> bool +static OpenTelemetry.Baggage.RemoveBaggage(string! name, OpenTelemetry.Baggage baggage = default(OpenTelemetry.Baggage)) -> OpenTelemetry.Baggage +static OpenTelemetry.Baggage.SetBaggage(string! name, string? value, OpenTelemetry.Baggage baggage = default(OpenTelemetry.Baggage)) -> OpenTelemetry.Baggage +static OpenTelemetry.Baggage.SetBaggage(System.Collections.Generic.IEnumerable>! baggageItems, OpenTelemetry.Baggage baggage = default(OpenTelemetry.Baggage)) -> OpenTelemetry.Baggage static OpenTelemetry.Context.Propagation.PropagationContext.operator !=(OpenTelemetry.Context.Propagation.PropagationContext left, OpenTelemetry.Context.Propagation.PropagationContext right) -> bool static OpenTelemetry.Context.Propagation.PropagationContext.operator ==(OpenTelemetry.Context.Propagation.PropagationContext left, OpenTelemetry.Context.Propagation.PropagationContext right) -> bool -static OpenTelemetry.Trace.ActivityExtensions.GetStatus(this System.Diagnostics.Activity! activity) -> OpenTelemetry.Trace.Status -static OpenTelemetry.Trace.ActivityExtensions.RecordException(this System.Diagnostics.Activity! activity, System.Exception? ex) -> void -static OpenTelemetry.Trace.ActivityExtensions.RecordException(this System.Diagnostics.Activity! activity, System.Exception? ex, in System.Diagnostics.TagList tags) -> void -static OpenTelemetry.Trace.ActivityExtensions.SetStatus(this System.Diagnostics.Activity! activity, OpenTelemetry.Trace.Status status) -> void +static OpenTelemetry.Context.Propagation.Propagators.DefaultTextMapPropagator.get -> OpenTelemetry.Context.Propagation.TextMapPropagator! +static OpenTelemetry.Trace.ActivityExtensions.GetStatus(this System.Diagnostics.Activity? activity) -> OpenTelemetry.Trace.Status +static OpenTelemetry.Trace.ActivityExtensions.RecordException(this System.Diagnostics.Activity? activity, System.Exception? ex, in System.Diagnostics.TagList tags) -> void +static OpenTelemetry.Trace.ActivityExtensions.RecordException(this System.Diagnostics.Activity? activity, System.Exception? ex) -> void +static OpenTelemetry.Trace.ActivityExtensions.SetStatus(this System.Diagnostics.Activity? activity, OpenTelemetry.Trace.Status status) -> void static OpenTelemetry.Trace.Link.operator !=(OpenTelemetry.Trace.Link link1, OpenTelemetry.Trace.Link link2) -> bool static OpenTelemetry.Trace.Link.operator ==(OpenTelemetry.Trace.Link link1, OpenTelemetry.Trace.Link link2) -> bool static OpenTelemetry.Trace.SpanContext.implicit operator System.Diagnostics.ActivityContext(OpenTelemetry.Trace.SpanContext spanContext) -> System.Diagnostics.ActivityContext diff --git a/src/OpenTelemetry.Api/.publicApi/Stable/net462/PublicAPI.Shipped.txt b/src/OpenTelemetry.Api/.publicApi/Stable/net462/PublicAPI.Shipped.txt index 58383768cd..378097ec65 100644 --- a/src/OpenTelemetry.Api/.publicApi/Stable/net462/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry.Api/.publicApi/Stable/net462/PublicAPI.Shipped.txt @@ -1,6 +1,6 @@ -~OpenTelemetry.Context.RemotingRuntimeContextSlot.RemotingRuntimeContextSlot(string name) -> void -~OpenTelemetry.Context.RemotingRuntimeContextSlot.Value.get -> object -~OpenTelemetry.Context.RemotingRuntimeContextSlot.Value.set -> void OpenTelemetry.Context.RemotingRuntimeContextSlot -override OpenTelemetry.Context.RemotingRuntimeContextSlot.Get() -> T +OpenTelemetry.Context.RemotingRuntimeContextSlot.RemotingRuntimeContextSlot(string! name) -> void +OpenTelemetry.Context.RemotingRuntimeContextSlot.Value.get -> object? +OpenTelemetry.Context.RemotingRuntimeContextSlot.Value.set -> void +override OpenTelemetry.Context.RemotingRuntimeContextSlot.Get() -> T? override OpenTelemetry.Context.RemotingRuntimeContextSlot.Set(T value) -> void diff --git a/src/OpenTelemetry.Api/ActivityContextExtensions.cs b/src/OpenTelemetry.Api/ActivityContextExtensions.cs index f50a9af8fe..fb630ae661 100644 --- a/src/OpenTelemetry.Api/ActivityContextExtensions.cs +++ b/src/OpenTelemetry.Api/ActivityContextExtensions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; // The ActivityContext class is in the System.Diagnostics namespace. diff --git a/src/OpenTelemetry.Api/Baggage.cs b/src/OpenTelemetry.Api/Baggage.cs index 46cdedbd7d..93b972f4f1 100644 --- a/src/OpenTelemetry.Api/Baggage.cs +++ b/src/OpenTelemetry.Api/Baggage.cs @@ -87,7 +87,7 @@ public static Baggage Current /// /// Baggage key/value pairs. /// . - public static Baggage Create(Dictionary baggageItems = null) + public static Baggage Create(Dictionary? baggageItems = null) { if (baggageItems == null) { @@ -133,7 +133,7 @@ public static Dictionary.Enumerator GetEnumerator(Baggage baggag /// Optional . is used if not specified. /// Baggage item or if nothing was found. [SuppressMessage("roslyn", "RS0026", Justification = "TODO: fix APIs that violate the backcompt requirement - multiple overloads with optional parameters: https://github.com/dotnet/roslyn/blob/main/docs/Adding%20Optional%20Parameters%20in%20Public%20API.md.")] - public static string GetBaggage(string name, Baggage baggage = default) + public static string? GetBaggage(string name, Baggage baggage = default) => baggage == default ? Current.GetBaggage(name) : baggage.GetBaggage(name); /// @@ -145,7 +145,7 @@ public static string GetBaggage(string name, Baggage baggage = default) /// New containing the key/value pair. /// Note: The returned will be set as the new instance. [SuppressMessage("roslyn", "RS0026", Justification = "TODO: fix APIs that violate the backcompt requirement - multiple overloads with optional parameters: https://github.com/dotnet/roslyn/blob/main/docs/Adding%20Optional%20Parameters%20in%20Public%20API.md.")] - public static Baggage SetBaggage(string name, string value, Baggage baggage = default) + public static Baggage SetBaggage(string name, string? value, Baggage baggage = default) { var baggageHolder = EnsureBaggageHolder(); lock (baggageHolder) @@ -164,7 +164,7 @@ public static Baggage SetBaggage(string name, string value, Baggage baggage = de /// New containing the new key/value pairs. /// Note: The returned will be set as the new instance. [SuppressMessage("roslyn", "RS0026", Justification = "TODO: fix APIs that violate the backcompt requirement - multiple overloads with optional parameters: https://github.com/dotnet/roslyn/blob/main/docs/Adding%20Optional%20Parameters%20in%20Public%20API.md.")] - public static Baggage SetBaggage(IEnumerable> baggageItems, Baggage baggage = default) + public static Baggage SetBaggage(IEnumerable> baggageItems, Baggage baggage = default) { var baggageHolder = EnsureBaggageHolder(); lock (baggageHolder) @@ -222,11 +222,11 @@ public IReadOnlyDictionary GetBaggage() /// /// Baggage item name. /// Baggage item or if nothing was found. - public string GetBaggage(string name) + public string? GetBaggage(string name) { Guard.ThrowIfNullOrEmpty(name); - return this.baggage != null && this.baggage.TryGetValue(name, out string value) + return this.baggage != null && this.baggage.TryGetValue(name, out string? value) ? value : null; } @@ -237,7 +237,7 @@ public string GetBaggage(string name) /// Baggage item name. /// Baggage item value. /// New containing the key/value pair. - public Baggage SetBaggage(string name, string value) + public Baggage SetBaggage(string name, string? value) { if (string.IsNullOrEmpty(value)) { @@ -247,7 +247,7 @@ public Baggage SetBaggage(string name, string value) return new Baggage( new Dictionary(this.baggage ?? EmptyBaggage, StringComparer.OrdinalIgnoreCase) { - [name] = value, + [name] = value!, }); } @@ -256,15 +256,15 @@ public Baggage SetBaggage(string name, string value) /// /// Baggage key/value pairs. /// New containing the key/value pairs. - public Baggage SetBaggage(params KeyValuePair[] baggageItems) - => this.SetBaggage((IEnumerable>)baggageItems); + public Baggage SetBaggage(params KeyValuePair[] baggageItems) + => this.SetBaggage((IEnumerable>)baggageItems); /// /// Returns a new which contains the new key/value pairs. /// /// Baggage key/value pairs. /// New containing the key/value pairs. - public Baggage SetBaggage(IEnumerable> baggageItems) + public Baggage SetBaggage(IEnumerable> baggageItems) { if (baggageItems?.Any() != true) { @@ -281,7 +281,7 @@ public Baggage SetBaggage(IEnumerable> baggageItems } else { - newBaggage[item.Key] = item.Value; + newBaggage[item.Key] = item.Value!; } } @@ -325,11 +325,11 @@ public bool Equals(Baggage other) return false; } - return baggageIsNullOrEmpty || this.baggage.SequenceEqual(other.baggage); + return baggageIsNullOrEmpty || this.baggage!.SequenceEqual(other.baggage!); } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) => (obj is Baggage baggage) && this.Equals(baggage); /// diff --git a/src/OpenTelemetry.Api/BaseProvider.cs b/src/OpenTelemetry.Api/BaseProvider.cs index ce8e30a46b..117f18c3c6 100644 --- a/src/OpenTelemetry.Api/BaseProvider.cs +++ b/src/OpenTelemetry.Api/BaseProvider.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry; /// diff --git a/src/OpenTelemetry.Api/CHANGELOG.md b/src/OpenTelemetry.Api/CHANGELOG.md index 9bdc9bf5d1..a1a16a7f8c 100644 --- a/src/OpenTelemetry.Api/CHANGELOG.md +++ b/src/OpenTelemetry.Api/CHANGELOG.md @@ -1,7 +1,61 @@ # Changelog +This file contains individual changes for the OpenTelemetry.Api package. For +highlights and announcements covering all components see: [Release +Notes](../../RELEASENOTES.md). + ## Unreleased +## 1.10.0-beta.1 + +Released 2024-Sep-30 + +* **Breaking change:** CompositeTextMapPropagator.Fields now returns a + unioned set of fields from all combined propagators. Previously this always + returned an empty set. + ([#5745](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5745)) + +* Optimize performance of `TraceContextPropagator.Extract`. + ([#5749](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5749)) + +* Obsoleted the `ActivityExtensions.GetStatus` and + `ActivityExtensions.SetStatus` extension methods. Users should migrate to the + `System.Diagnostics.DiagnosticSource` + [Activity.SetStatus](https://learn.microsoft.com/dotnet/api/system.diagnostics.activity.setstatus) + API for setting the status and + [Activity.Status](https://learn.microsoft.com/dotnet/api/system.diagnostics.activity.status) + & + [Activity.StatusDescription](https://learn.microsoft.com/dotnet/api/system.diagnostics.activity.statusdescription) + APIs for reading the status of an `Activity` instance. + ([#5781](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5781)) + +* Updated `System.Diagnostics.DiagnosticSource` package version to + `9.0.0-rc.1.24431.7`. + ([#5853](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5853)) + +* Obsoleted the `ActivityExtensions.RecordException` extension method. Users + should migrate to the `System.Diagnostics.DiagnosticSource` + [Activity.AddException](https://learn.microsoft.com/dotnet/api/system.diagnostics.activity.addexception) + API for adding exceptions on an `Activity` instance. + ([#5841](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5841)) + +## 1.9.0 + +Released 2024-Jun-14 + +* **Breaking change:** Revert space character encoding change from `+` to `%20` + for baggage item values from [#5303](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5303) + ([#5687](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5687)) + +## 1.9.0-rc.1 + +Released 2024-Jun-07 + +* The experimental APIs previously covered by `OTEL1000` (`LoggerProvider`, + `LoggerProviderBuilder`, & `IDeferredLoggerProviderBuilder`) are now part of + the public API and supported in stable builds. + ([#5648](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5648)) + ## 1.9.0-alpha.1 Released 2024-May-20 diff --git a/src/OpenTelemetry.Api/Context/AsyncLocalRuntimeContextSlot.cs b/src/OpenTelemetry.Api/Context/AsyncLocalRuntimeContextSlot.cs index 15eb31d08c..7bafc0e951 100644 --- a/src/OpenTelemetry.Api/Context/AsyncLocalRuntimeContextSlot.cs +++ b/src/OpenTelemetry.Api/Context/AsyncLocalRuntimeContextSlot.cs @@ -24,15 +24,25 @@ public AsyncLocalRuntimeContextSlot(string name) } /// - public object Value + public object? Value { get => this.slot.Value; - set => this.slot.Value = (T)value; + set + { + if (typeof(T).IsValueType && value is null) + { + this.slot.Value = default!; + } + else + { + this.slot.Value = (T)value!; + } + } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override T Get() + public override T? Get() { return this.slot.Value; } diff --git a/src/OpenTelemetry.Api/Context/IRuntimeContextSlotValueAccessor.cs b/src/OpenTelemetry.Api/Context/IRuntimeContextSlotValueAccessor.cs index 1e16ea4260..19c02050cd 100644 --- a/src/OpenTelemetry.Api/Context/IRuntimeContextSlotValueAccessor.cs +++ b/src/OpenTelemetry.Api/Context/IRuntimeContextSlotValueAccessor.cs @@ -11,5 +11,5 @@ public interface IRuntimeContextSlotValueAccessor /// /// Gets or sets the value of the slot as an . /// - object Value { get; set; } + object? Value { get; set; } } diff --git a/src/OpenTelemetry.Api/Context/Propagation/B3Propagator.cs b/src/OpenTelemetry.Api/Context/Propagation/B3Propagator.cs index 10b4aee1d8..1827617210 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/B3Propagator.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/B3Propagator.cs @@ -66,7 +66,7 @@ public B3Propagator(bool singleHeader) /// [Obsolete("Use B3Propagator class from OpenTelemetry.Extensions.Propagators namespace, shipped as part of OpenTelemetry.Extensions.Propagators package.")] #pragma warning disable CS0809 // Obsolete member overrides non-obsolete member - public override PropagationContext Extract(PropagationContext context, T carrier, Func> getter) + public override PropagationContext Extract(PropagationContext context, T carrier, Func?> getter) #pragma warning restore CS0809 // Obsolete member overrides non-obsolete member { if (context.ActivityContext.IsValid()) @@ -146,7 +146,7 @@ public override void Inject(PropagationContext context, T carrier, Action(PropagationContext context, T carrier, Func> getter) + private static PropagationContext ExtractFromMultipleHeaders(PropagationContext context, T carrier, Func?> getter) { try { @@ -179,7 +179,8 @@ private static PropagationContext ExtractFromMultipleHeaders(PropagationConte } var traceOptions = ActivityTraceFlags.None; - if (SampledValues.Contains(getter(carrier, XB3Sampled)?.FirstOrDefault()) + var xb3Sampled = getter(carrier, XB3Sampled)?.FirstOrDefault(); + if ((xb3Sampled != null && SampledValues.Contains(xb3Sampled)) || FlagsValue.Equals(getter(carrier, XB3Flags)?.FirstOrDefault(), StringComparison.Ordinal)) { traceOptions |= ActivityTraceFlags.Recorded; @@ -196,7 +197,7 @@ private static PropagationContext ExtractFromMultipleHeaders(PropagationConte } } - private static PropagationContext ExtractFromSingleHeader(PropagationContext context, T carrier, Func> getter) + private static PropagationContext ExtractFromSingleHeader(PropagationContext context, T carrier, Func?> getter) { try { @@ -206,7 +207,7 @@ private static PropagationContext ExtractFromSingleHeader(PropagationContext 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.Api/Context/Propagation/BaggagePropagator.cs b/src/OpenTelemetry.Api/Context/Propagation/BaggagePropagator.cs index e9dc29d950..938980931e 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/BaggagePropagator.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/BaggagePropagator.cs @@ -1,6 +1,10 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +#if NET +using System.Diagnostics.CodeAnalysis; +#endif +using System.Net; using System.Text; using OpenTelemetry.Internal; @@ -23,7 +27,7 @@ public class BaggagePropagator : TextMapPropagator public override ISet Fields => new HashSet { BaggageHeaderName }; /// - public override PropagationContext Extract(PropagationContext context, T carrier, Func> getter) + public override PropagationContext Extract(PropagationContext context, T carrier, Func?> getter) { if (context.Baggage != default) { @@ -45,16 +49,16 @@ public override PropagationContext Extract(PropagationContext context, T carr try { - Dictionary baggage = null; var baggageCollection = getter(carrier, BaggageHeaderName); if (baggageCollection?.Any() ?? false) { - TryExtractBaggage(baggageCollection.ToArray(), out baggage); + if (TryExtractBaggage(baggageCollection.ToArray(), out var baggage)) + { + return new PropagationContext(context.ActivityContext, new Baggage(baggage!)); + } } - return new PropagationContext( - context.ActivityContext, - baggage == null ? context.Baggage : new Baggage(baggage)); + return new PropagationContext(context.ActivityContext, context.Baggage); } catch (Exception ex) { @@ -93,7 +97,7 @@ public override void Inject(PropagationContext context, T carrier, Action(PropagationContext context, T carrier, Action baggage) + internal static bool TryExtractBaggage( + string[] baggageCollection, +#if NET + [NotNullWhen(true)] +#endif + out Dictionary? baggage) { int baggageLength = -1; bool done = false; - Dictionary baggageDictionary = null; + Dictionary? baggageDictionary = null; foreach (var item in baggageCollection) { @@ -140,8 +149,8 @@ internal static bool TryExtractBaggage(string[] baggageCollection, out Dictionar continue; } - var key = Uri.UnescapeDataString(parts[0]); - var value = Uri.UnescapeDataString(parts[1]); + var key = WebUtility.UrlDecode(parts[0]); + var value = WebUtility.UrlDecode(parts[1]); if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) { diff --git a/src/OpenTelemetry.Api/Context/Propagation/CompositeTextMapPropagator.cs b/src/OpenTelemetry.Api/Context/Propagation/CompositeTextMapPropagator.cs index a6b12c3cd1..e160bc8ac7 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/CompositeTextMapPropagator.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/CompositeTextMapPropagator.cs @@ -11,8 +11,8 @@ namespace OpenTelemetry.Context.Propagation; /// public class CompositeTextMapPropagator : TextMapPropagator { - private static readonly ISet EmptyFields = new HashSet(); - private readonly List propagators; + private readonly IReadOnlyList propagators; + private readonly ISet allFields; /// /// Initializes a new instance of the class. @@ -22,18 +22,55 @@ public CompositeTextMapPropagator(IEnumerable propagators) { Guard.ThrowIfNull(propagators); - this.propagators = new List(propagators); + var propagatorsList = new List(); + + foreach (var propagator in propagators) + { + if (propagator is not null) + { + propagatorsList.Add(propagator); + } + } + + this.propagators = propagatorsList; + + // For efficiency, we resolve the fields from all propagators only once, as they are + // not expected to change (although the implementation doesn't strictly prevent that). + if (this.propagators.Count == 0) + { + // Use a new empty HashSet for each instance to avoid any potential mutation issues. + this.allFields = new HashSet(); + } + else + { + ISet? fields = this.propagators[0].Fields; + + var output = fields is not null + ? new HashSet(fields) + : []; + + for (int i = 1; i < this.propagators.Count; i++) + { + fields = this.propagators[i].Fields; + if (fields is not null) + { + output.UnionWith(fields); + } + } + + this.allFields = output; + } } /// - public override ISet Fields => EmptyFields; + public override ISet Fields => this.allFields; /// - public override PropagationContext Extract(PropagationContext context, T carrier, Func> getter) + public override PropagationContext Extract(PropagationContext context, T carrier, Func?> getter) { - foreach (var propagator in this.propagators) + for (int i = 0; i < this.propagators.Count; i++) { - context = propagator.Extract(context, carrier, getter); + context = this.propagators[i].Extract(context, carrier, getter); } return context; @@ -42,9 +79,9 @@ public override PropagationContext Extract(PropagationContext context, T carr /// public override void Inject(PropagationContext context, T carrier, Action setter) { - foreach (var propagator in this.propagators) + for (int i = 0; i < this.propagators.Count; i++) { - propagator.Inject(context, carrier, setter); + this.propagators[i].Inject(context, carrier, setter); } } } diff --git a/src/OpenTelemetry.Api/Context/Propagation/NoopTextMapPropagator.cs b/src/OpenTelemetry.Api/Context/Propagation/NoopTextMapPropagator.cs index 03dd45785d..a722a4f087 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/NoopTextMapPropagator.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/NoopTextMapPropagator.cs @@ -7,9 +7,9 @@ internal sealed class NoopTextMapPropagator : TextMapPropagator { private static readonly PropagationContext DefaultPropagationContext = default; - public override ISet Fields => null; + public override ISet? Fields => null; - public override PropagationContext Extract(PropagationContext context, T carrier, Func> getter) + public override PropagationContext Extract(PropagationContext context, T carrier, Func?> getter) { return DefaultPropagationContext; } diff --git a/src/OpenTelemetry.Api/Context/Propagation/PropagationContext.cs b/src/OpenTelemetry.Api/Context/Propagation/PropagationContext.cs index 77b6c71e77..0923ed45d6 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/PropagationContext.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/PropagationContext.cs @@ -53,7 +53,7 @@ public bool Equals(PropagationContext value) } /// - public override bool Equals(object obj) => (obj is PropagationContext context) && this.Equals(context); + public override bool Equals(object? obj) => (obj is PropagationContext context) && this.Equals(context); /// public override int GetHashCode() diff --git a/src/OpenTelemetry.Api/Context/Propagation/TextMapPropagator.cs b/src/OpenTelemetry.Api/Context/Propagation/TextMapPropagator.cs index 1000b317bf..bb0432378b 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/TextMapPropagator.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/TextMapPropagator.cs @@ -15,12 +15,12 @@ public abstract class TextMapPropagator /// * allow pre-allocation of fields, especially in systems like gRPC Metadata /// * allow a single-pass over an iterator (ex OpenTracing has no getter in TextMap). /// - public abstract ISet Fields { get; } + public abstract ISet? Fields { get; } /// /// Injects the context into a carrier. /// - /// Type of an object to set context on. Typically HttpRequest or similar. + /// Type of object to set context on. Typically,HttpRequest or similar. /// The default context to transmit over the wire. /// Object to set context on. Instance of this object will be passed to setter. /// Action that will set name and value pair on the object. @@ -29,10 +29,10 @@ public abstract class TextMapPropagator /// /// Extracts the context from a carrier. /// - /// Type of object to extract context from. Typically HttpRequest or similar. + /// Type of object to extract context from. Typically, HttpRequest or similar. /// The default context to be used if Extract fails. /// Object to extract context from. Instance of this object will be passed to the getter. /// Function that will return string value of a key with the specified name. - /// Context from it's text representation. - public abstract PropagationContext Extract(PropagationContext context, T carrier, Func> getter); + /// Context from its text representation. + public abstract PropagationContext Extract(PropagationContext context, T carrier, Func?> getter); } diff --git a/src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs b/src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs index de71129542..ee67aaaaaf 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; @@ -34,7 +34,7 @@ public class TraceContextPropagator : TextMapPropagator public override ISet Fields => new HashSet { TraceState, TraceParent }; /// - public override PropagationContext Extract(PropagationContext context, T carrier, Func> getter) + public override PropagationContext Extract(PropagationContext context, T carrier, Func?> getter) { if (context.ActivityContext.IsValid()) { @@ -72,11 +72,11 @@ public override PropagationContext Extract(PropagationContext context, T carr return context; } - string tracestate = null; + string? tracestate = null; var tracestateCollection = getter(carrier, TraceState); if (tracestateCollection?.Any() ?? false) { - TryExtractTracestate(tracestateCollection.ToArray(), out tracestate); + TryExtractTracestate(tracestateCollection, out tracestate); } return new PropagationContext( @@ -113,7 +113,7 @@ public override void Inject(PropagationContext context, T carrier, Action(PropagationContext context, T carrier, Action 0) { setter(carrier, TraceState, tracestateStr); @@ -220,31 +220,37 @@ internal static bool TryExtractTraceparent(string traceparent, out ActivityTrace return true; } - internal static bool TryExtractTracestate(string[] tracestateCollection, out string tracestateResult) + internal static bool TryExtractTracestate(IEnumerable tracestateCollection, out string tracestateResult) { tracestateResult = string.Empty; - if (tracestateCollection != null) + char[]? rentedArray = null; + Span traceStateBuffer = stackalloc char[128]; // 256B + Span keyLookupBuffer = stackalloc char[96]; // 192B (3x32 keys) + int keys = 0; + int charsWritten = 0; + + try { - var keySet = new HashSet(); - var result = new StringBuilder(); - for (int i = 0; i < tracestateCollection.Length; ++i) + foreach (var tracestateItem in tracestateCollection) { - var tracestate = tracestateCollection[i].AsSpan(); - int begin = 0; - while (begin < tracestate.Length) + var tracestate = tracestateItem.AsSpan(); + int position = 0; + + while (position < tracestate.Length) { - int length = tracestate.Slice(begin).IndexOf(','); + int length = tracestate.Slice(position).IndexOf(','); ReadOnlySpan listMember; + if (length != -1) { - listMember = tracestate.Slice(begin, length).Trim(); - begin += length + 1; + listMember = tracestate.Slice(position, length).Trim(); + position += length + 1; } else { - listMember = tracestate.Slice(begin).Trim(); - begin = tracestate.Length; + listMember = tracestate.Slice(position).Trim(); + position = tracestate.Length; } // https://github.com/w3c/trace-context/blob/master/spec/20-http_request_header_format.md#tracestate-header-field-values @@ -255,7 +261,7 @@ internal static bool TryExtractTracestate(string[] tracestateCollection, out str continue; } - if (keySet.Count >= 32) + if (keys >= 32) { // https://github.com/w3c/trace-context/blob/master/spec/20-http_request_header_format.md#list // test_tracestate_member_count_limit @@ -286,25 +292,107 @@ internal static bool TryExtractTracestate(string[] tracestateCollection, out str } // ValidateKey() call above has ensured the key does not contain upper case letters. - if (!keySet.Add(key.ToString())) + + var duplicationCheckLength = Math.Min(key.Length, 3); + + if (keys > 0) { - // test_tracestate_duplicated_keys - return false; + // 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 + } + } } - if (result.Length > 0) + // 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) { - result.Append(','); + GrowBuffer(ref rentedArray, ref traceStateBuffer); } - result.Append(listMember.ToString()); + if (charsWritten > 0) + { + traceStateBuffer[charsWritten++] = ','; + } + + listMember.CopyTo(traceStateBuffer.Slice(charsWritten)); + charsWritten += listMember.Length; + + keys++; } } - tracestateResult = result.ToString(); + tracestateResult = traceStateBuffer.Slice(0, charsWritten).ToString(); + + return true; + } + finally + { + if (rentedArray is not null) + { + ArrayPool.Shared.Return(rentedArray); + rentedArray = null; + } } - return true; + [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(); + } } private static byte HexCharToByte(char c) @@ -430,7 +518,7 @@ private static bool IsLowerAlphaDigit(char c) return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z'); } -#if NET6_0_OR_GREATER +#if NET private static void WriteTraceParentIntoSpan(Span destination, ActivityContext context) { "00-".CopyTo(destination); diff --git a/src/OpenTelemetry.Api/Context/Propagation/TraceStateUtilsNew.cs b/src/OpenTelemetry.Api/Context/Propagation/TraceStateUtilsNew.cs index 717e1a9458..eef28f8ad9 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/TraceStateUtilsNew.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/TraceStateUtilsNew.cs @@ -56,7 +56,7 @@ internal static bool AppendTraceState(string traceStateString, List(keyStr, value.ToString())); + tracestate!.Add(new KeyValuePair(keyStr, value.ToString())); } else { @@ -82,7 +82,7 @@ internal static bool AppendTraceState(string traceStateString, List> traceState) + internal static string GetString(IEnumerable>? traceState) { if (traceState == null || !traceState.Any()) { diff --git a/src/OpenTelemetry.Api/Context/RemotingRuntimeContextSlot.cs b/src/OpenTelemetry.Api/Context/RemotingRuntimeContextSlot.cs index 61d2722653..93f7bd4754 100644 --- a/src/OpenTelemetry.Api/Context/RemotingRuntimeContextSlot.cs +++ b/src/OpenTelemetry.Api/Context/RemotingRuntimeContextSlot.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #if NETFRAMEWORK + using System.Collections; using System.Reflection; using System.Runtime.CompilerServices; @@ -38,25 +39,39 @@ public RemotingRuntimeContextSlot(string name) } /// - public object Value + public object? Value { get => this.Get(); - set => this.Set((T)value); + set + { + if (typeof(T).IsValueType && value is null) + { + this.Set(default!); + } + else + { + this.Set((T)value!); + } + } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override T Get() + public override T? Get() { - if (!(CallContext.LogicalGetData(this.Name) is BitArray wrapper)) + if (CallContext.LogicalGetData(this.Name) is not BitArray wrapper) { return default; } var value = WrapperField.GetValue(wrapper); - return value is T t - ? t - : default; + + if (typeof(T).IsValueType && value is null) + { + return default; + } + + return (T)value; } /// diff --git a/src/OpenTelemetry.Api/Context/RuntimeContext.cs b/src/OpenTelemetry.Api/Context/RuntimeContext.cs index 93c96299d2..c984147bf4 100644 --- a/src/OpenTelemetry.Api/Context/RuntimeContext.cs +++ b/src/OpenTelemetry.Api/Context/RuntimeContext.cs @@ -56,7 +56,8 @@ public static Type ContextSlotType public static RuntimeContextSlot RegisterSlot(string slotName) { Guard.ThrowIfNullOrEmpty(slotName); - RuntimeContextSlot slot = null; + + RuntimeContextSlot? slot = null; lock (Slots) { @@ -80,6 +81,10 @@ public static RuntimeContextSlot RegisterSlot(string slotName) slot = new RemotingRuntimeContextSlot(slotName); } #endif + else + { + throw new NotSupportedException($"ContextSlotType '{ContextSlotType}' is not supported"); + } Slots[slotName] = slot; return slot; @@ -95,9 +100,10 @@ public static RuntimeContextSlot RegisterSlot(string slotName) public static RuntimeContextSlot GetSlot(string slotName) { Guard.ThrowIfNullOrEmpty(slotName); + var slot = GuardNotFound(slotName); - var contextSlot = Guard.ThrowIfNotOfType>(slot); - return contextSlot; + + return Guard.ThrowIfNotOfType>(slot); } /* @@ -143,7 +149,7 @@ public static void SetValue(string slotName, T value) /// The type of the value. /// The value retrieved from the context slot. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T GetValue(string slotName) + public static T? GetValue(string slotName) { return GetSlot(slotName).Get(); } @@ -153,12 +159,13 @@ public static T GetValue(string slotName) /// /// The name of the context slot. /// The value to be set. - public static void SetValue(string slotName, object value) + public static void SetValue(string slotName, object? value) { Guard.ThrowIfNullOrEmpty(slotName); + var slot = GuardNotFound(slotName); - var runtimeContextSlotValueAccessor = Guard.ThrowIfNotOfType(slot); - runtimeContextSlotValueAccessor.Value = value; + + Guard.ThrowIfNotOfType(slot).Value = value; } /// @@ -166,12 +173,13 @@ public static void SetValue(string slotName, object value) /// /// The name of the context slot. /// The value retrieved from the context slot. - public static object GetValue(string slotName) + public static object? GetValue(string slotName) { Guard.ThrowIfNullOrEmpty(slotName); + var slot = GuardNotFound(slotName); - var runtimeContextSlotValueAccessor = Guard.ThrowIfNotOfType(slot); - return runtimeContextSlotValueAccessor.Value; + + return Guard.ThrowIfNotOfType(slot).Value; } // For testing purpose diff --git a/src/OpenTelemetry.Api/Context/RuntimeContextSlot.cs b/src/OpenTelemetry.Api/Context/RuntimeContextSlot.cs index 918e4b7942..a89afd394a 100644 --- a/src/OpenTelemetry.Api/Context/RuntimeContextSlot.cs +++ b/src/OpenTelemetry.Api/Context/RuntimeContextSlot.cs @@ -1,6 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using OpenTelemetry.Internal; + namespace OpenTelemetry.Context; /// @@ -15,6 +17,8 @@ public abstract class RuntimeContextSlot : IDisposable /// The name of the context slot. protected RuntimeContextSlot(string name) { + Guard.ThrowIfNullOrEmpty(name); + this.Name = name; } @@ -27,7 +31,7 @@ protected RuntimeContextSlot(string name) /// Get the value from the context slot. /// /// The value retrieved from the context slot. - public abstract T Get(); + public abstract T? Get(); /// /// Set the value to the context slot. diff --git a/src/OpenTelemetry.Api/Context/ThreadLocalRuntimeContextSlot.cs b/src/OpenTelemetry.Api/Context/ThreadLocalRuntimeContextSlot.cs index c7724a842b..360f9df564 100644 --- a/src/OpenTelemetry.Api/Context/ThreadLocalRuntimeContextSlot.cs +++ b/src/OpenTelemetry.Api/Context/ThreadLocalRuntimeContextSlot.cs @@ -25,15 +25,25 @@ public ThreadLocalRuntimeContextSlot(string name) } /// - public object Value + public object? Value { get => this.slot.Value; - set => this.slot.Value = (T)value; + set + { + if (typeof(T).IsValueType && value is null) + { + this.slot.Value = default!; + } + else + { + this.slot.Value = (T)value!; + } + } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override T Get() + public override T? Get() { return this.slot.Value; } diff --git a/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs b/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs index ea802a6ed1..5ac56d362f 100644 --- a/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs +++ b/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics.Tracing; namespace OpenTelemetry.Internal; diff --git a/src/OpenTelemetry.Api/Logs/IDeferredLoggerProviderBuilder.cs b/src/OpenTelemetry.Api/Logs/IDeferredLoggerProviderBuilder.cs index 70528fe6cb..11397157ce 100644 --- a/src/OpenTelemetry.Api/Logs/IDeferredLoggerProviderBuilder.cs +++ b/src/OpenTelemetry.Api/Logs/IDeferredLoggerProviderBuilder.cs @@ -1,35 +1,14 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - -#if NET8_0_OR_GREATER && EXPOSE_EXPERIMENTAL_FEATURES -using System.Diagnostics.CodeAnalysis; -using OpenTelemetry.Internal; -#endif - namespace OpenTelemetry.Logs; -#if EXPOSE_EXPERIMENTAL_FEATURES -/// -/// Describes a logger provider builder that supports deferred -/// initialization using an to perform -/// dependency injection. -/// -/// -#if NET8_0_OR_GREATER -[Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif -public -#else /// /// Describes a logger provider builder that supports deferred /// initialization using an to perform /// dependency injection. /// -internal -#endif -interface IDeferredLoggerProviderBuilder +public interface IDeferredLoggerProviderBuilder { /// /// Register a callback action to configure the /// -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public diff --git a/src/OpenTelemetry.Api/Logs/LogRecordData.cs b/src/OpenTelemetry.Api/Logs/LogRecordData.cs index cb3c49292a..20d8d5f264 100644 --- a/src/OpenTelemetry.Api/Logs/LogRecordData.cs +++ b/src/OpenTelemetry.Api/Logs/LogRecordData.cs @@ -1,10 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; -#if NET8_0_OR_GREATER && EXPOSE_EXPERIMENTAL_FEATURES +#if NET && EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Internal; #endif @@ -16,7 +14,7 @@ namespace OpenTelemetry.Logs; /// Stores details about a log message. /// /// -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public diff --git a/src/OpenTelemetry.Api/Logs/LogRecordSeverity.cs b/src/OpenTelemetry.Api/Logs/LogRecordSeverity.cs index 9f48e71e85..b6a5b3e000 100644 --- a/src/OpenTelemetry.Api/Logs/LogRecordSeverity.cs +++ b/src/OpenTelemetry.Api/Logs/LogRecordSeverity.cs @@ -1,9 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - -#if NET8_0_OR_GREATER && EXPOSE_EXPERIMENTAL_FEATURES +#if NET && EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Internal; #endif @@ -15,7 +13,7 @@ namespace OpenTelemetry.Logs; /// Describes the severity level of a log record. /// /// -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public diff --git a/src/OpenTelemetry.Api/Logs/LogRecordSeverityExtensions.cs b/src/OpenTelemetry.Api/Logs/LogRecordSeverityExtensions.cs index f171edbc9c..81453d0302 100644 --- a/src/OpenTelemetry.Api/Logs/LogRecordSeverityExtensions.cs +++ b/src/OpenTelemetry.Api/Logs/LogRecordSeverityExtensions.cs @@ -1,9 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - -#if NET8_0_OR_GREATER && EXPOSE_EXPERIMENTAL_FEATURES +#if NET && EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Internal; #endif @@ -15,7 +13,7 @@ namespace OpenTelemetry.Logs; /// Contains extension methods for the enum. /// /// -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public diff --git a/src/OpenTelemetry.Api/Logs/Logger.cs b/src/OpenTelemetry.Api/Logs/Logger.cs index 71baf7d611..f8a50f2a6d 100644 --- a/src/OpenTelemetry.Api/Logs/Logger.cs +++ b/src/OpenTelemetry.Api/Logs/Logger.cs @@ -1,9 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - -#if NET8_0_OR_GREATER && EXPOSE_EXPERIMENTAL_FEATURES +#if NET && EXPOSE_EXPERIMENTAL_FEATURES using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Internal; #endif @@ -15,7 +13,7 @@ namespace OpenTelemetry.Logs; /// Logger is the class responsible for creating log records. /// /// WARNING: This is an experimental API which might change or be removed in the future. Use at your own risk. -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public diff --git a/src/OpenTelemetry.Api/Logs/LoggerProvider.cs b/src/OpenTelemetry.Api/Logs/LoggerProvider.cs index c7993dc664..71cc40e232 100644 --- a/src/OpenTelemetry.Api/Logs/LoggerProvider.cs +++ b/src/OpenTelemetry.Api/Logs/LoggerProvider.cs @@ -1,33 +1,19 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - -#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET using System.Diagnostics.CodeAnalysis; #endif -#if NET8_0_OR_GREATER +#if EXPOSE_EXPERIMENTAL_FEATURES && NET using OpenTelemetry.Internal; #endif namespace OpenTelemetry.Logs; -#if EXPOSE_EXPERIMENTAL_FEATURES -/// -/// LoggerProvider is the entry point of the OpenTelemetry API. It provides access to . -/// -/// -#if NET8_0_OR_GREATER -[Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif -public -#else /// /// LoggerProvider is the entry point of the OpenTelemetry API. It provides access to . /// -internal -#endif - class LoggerProvider : BaseProvider +public class LoggerProvider : BaseProvider { private static readonly NoopLogger NoopLogger = new(); @@ -38,37 +24,55 @@ protected LoggerProvider() { } +#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Gets a logger. /// + /// /// instance. -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif - public Logger GetLogger() + public +#else + internal +#endif + Logger GetLogger() => this.GetLogger(name: null, version: null); +#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Gets a logger with the given name. /// + /// /// Optional name identifying the instrumentation library. /// instance. -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif - public Logger GetLogger(string? name) + public +#else + internal +#endif + Logger GetLogger(string? name) => this.GetLogger(name, version: null); +#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Gets a logger with the given name and version. /// + /// /// Optional name identifying the instrumentation library. /// Optional version of the instrumentation library. /// instance. -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif - public Logger GetLogger(string? name, string? version) + public +#else + internal +#endif + Logger GetLogger(string? name, string? version) { if (!this.TryCreateLogger(name, out var logger)) { @@ -80,18 +84,24 @@ public Logger GetLogger(string? name, string? version) return logger; } +#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Try to create a logger with the given name. /// + /// /// Optional name identifying the instrumentation library. /// . /// if the logger was created. -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif - protected virtual bool TryCreateLogger( + protected +#else + internal +#endif + virtual bool TryCreateLogger( string? name, -#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET [NotNullWhen(true)] #endif out Logger? logger) diff --git a/src/OpenTelemetry.Api/Logs/LoggerProviderBuilder.cs b/src/OpenTelemetry.Api/Logs/LoggerProviderBuilder.cs index 3fa9c6fc74..2bc69d1d0c 100644 --- a/src/OpenTelemetry.Api/Logs/LoggerProviderBuilder.cs +++ b/src/OpenTelemetry.Api/Logs/LoggerProviderBuilder.cs @@ -1,31 +1,12 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - -#if NET8_0_OR_GREATER && EXPOSE_EXPERIMENTAL_FEATURES -using System.Diagnostics.CodeAnalysis; -using OpenTelemetry.Internal; -#endif - namespace OpenTelemetry.Logs; -#if EXPOSE_EXPERIMENTAL_FEATURES -/// -/// LoggerProviderBuilder base class. -/// -/// -#if NET8_0_OR_GREATER -[Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif -public -#else /// /// LoggerProviderBuilder base class. /// -internal -#endif - abstract class LoggerProviderBuilder +public abstract class LoggerProviderBuilder { /// /// Initializes a new instance of the class. diff --git a/src/OpenTelemetry.Api/Logs/NoopLogger.cs b/src/OpenTelemetry.Api/Logs/NoopLogger.cs index f33ec668ac..2c2b61c418 100644 --- a/src/OpenTelemetry.Api/Logs/NoopLogger.cs +++ b/src/OpenTelemetry.Api/Logs/NoopLogger.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Logs; internal sealed class NoopLogger : Logger diff --git a/src/OpenTelemetry.Api/Metrics/IDeferredMeterProviderBuilder.cs b/src/OpenTelemetry.Api/Metrics/IDeferredMeterProviderBuilder.cs index c283219c43..5f0dc38d34 100644 --- a/src/OpenTelemetry.Api/Metrics/IDeferredMeterProviderBuilder.cs +++ b/src/OpenTelemetry.Api/Metrics/IDeferredMeterProviderBuilder.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Metrics; /// diff --git a/src/OpenTelemetry.Api/Metrics/MeterProvider.cs b/src/OpenTelemetry.Api/Metrics/MeterProvider.cs index a16fd88df9..64fcb5e8e5 100644 --- a/src/OpenTelemetry.Api/Metrics/MeterProvider.cs +++ b/src/OpenTelemetry.Api/Metrics/MeterProvider.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Metrics; /// diff --git a/src/OpenTelemetry.Api/Metrics/MeterProviderBuilder.cs b/src/OpenTelemetry.Api/Metrics/MeterProviderBuilder.cs index 95075dbe70..4fb6bb007d 100644 --- a/src/OpenTelemetry.Api/Metrics/MeterProviderBuilder.cs +++ b/src/OpenTelemetry.Api/Metrics/MeterProviderBuilder.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Metrics; /// diff --git a/src/OpenTelemetry.Api/OpenTelemetry.Api.csproj b/src/OpenTelemetry.Api/OpenTelemetry.Api.csproj index 0a781a9417..0e6d270b40 100644 --- a/src/OpenTelemetry.Api/OpenTelemetry.Api.csproj +++ b/src/OpenTelemetry.Api/OpenTelemetry.Api.csproj @@ -4,9 +4,6 @@ OpenTelemetry .NET API OpenTelemetry core- - - - disable @@ -19,6 +16,7 @@ + diff --git a/src/OpenTelemetry.Api/README.md b/src/OpenTelemetry.Api/README.md index 5fd769f446..db0471e177 100644 --- a/src/OpenTelemetry.Api/README.md +++ b/src/OpenTelemetry.Api/README.md @@ -539,4 +539,8 @@ seeing these internal logs. ## References -* [OpenTelemetry Project](https://opentelemetry.io/) +* [OpenTelemetry Baggage API specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/baggage/api.md) +* [OpenTelemetry Logs Bridge API specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/bridge-api.md) +* [OpenTelemetry Metrics API specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md) +* [OpenTelemetry Propagators API specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md) +* [OpenTelemetry Tracing API specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md) diff --git a/src/OpenTelemetry.Api/Trace/ActivityExtensions.cs b/src/OpenTelemetry.Api/Trace/ActivityExtensions.cs index d8ac769b9c..b22339267b 100644 --- a/src/OpenTelemetry.Api/Trace/ActivityExtensions.cs +++ b/src/OpenTelemetry.Api/Trace/ActivityExtensions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using System.Runtime.CompilerServices; using OpenTelemetry.Internal; @@ -21,17 +19,34 @@ public static class ActivityExtensions { /// /// Sets the status of activity execution. - /// Activity class in .NET does not support 'Status'. - /// This extension provides a workaround to store Status as special tags with key name of otel.status_code and otel.status_description. - /// Read more about SetStatus here https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status. /// + /// + /// Note: This method is obsolete. Call the + /// method instead. For more details see: . + /// /// Activity instance. /// Activity execution status. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetStatus(this Activity activity, Status status) + [Obsolete("Call Activity.SetStatus instead this method will be removed in a future version.")] + public static void SetStatus(this Activity? activity, Status status) { if (activity != null) { + switch (status.StatusCode) + { + case StatusCode.Ok: + activity.SetStatus(ActivityStatusCode.Ok); + break; + case StatusCode.Unset: + activity.SetStatus(ActivityStatusCode.Unset); + break; + case StatusCode.Error: + activity.SetStatus(ActivityStatusCode.Error, status.Description); + break; + } + activity.SetTag(SpanAttributeConstants.StatusCodeKey, StatusHelper.GetTagValueForStatusCode(status.StatusCode)); activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, status.Description); } @@ -39,21 +54,37 @@ public static void SetStatus(this Activity activity, Status status) /// /// Gets the status of activity execution. - /// Activity class in .NET does not support 'Status'. - /// This extension provides a workaround to retrieve Status from special tags with key name otel.status_code and otel.status_description. /// + /// + /// Note: This method is obsolete. Use the and + /// properties instead. For more + /// details see: . + /// /// Activity instance. /// Activity execution status. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Status GetStatus(this Activity activity) + [Obsolete("Use Activity.Status and Activity.StatusDescription instead this method will be removed in a future version.")] + public static Status GetStatus(this Activity? activity) { - if (activity == null - || !activity.TryGetStatus(out var statusCode, out var statusDescription)) + if (activity != null) { - return Status.Unset; + switch (activity.Status) + { + case ActivityStatusCode.Ok: + return Status.Ok; + case ActivityStatusCode.Error: + return new Status(StatusCode.Error, activity.StatusDescription); + } + + if (activity.TryGetStatus(out var statusCode, out var statusDescription)) + { + return new Status(statusCode, statusDescription); + } } - return new Status(statusCode, statusDescription); + return Status.Unset; } /// @@ -61,11 +92,14 @@ public static Status GetStatus(this Activity activity) /// /// Activity instance. /// Exception to be recorded. - /// The exception is recorded as per specification. + /// + /// Note: This method is obsolete. Please use instead. + /// The exception is recorded as per specification. /// "exception.stacktrace" is represented using the value of Exception.ToString. /// + [Obsolete("Call Activity.AddException instead this method will be removed in a future version.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void RecordException(this Activity activity, Exception? ex) + public static void RecordException(this Activity? activity, Exception? ex) => RecordException(activity, ex, default); /// @@ -74,33 +108,20 @@ public static void RecordException(this Activity activity, Exception? ex) /// Activity instance. /// Exception to be recorded. /// Additional tags to record on the event. - /// The exception is recorded as per specification. + /// + /// Note: This method is obsolete. Please use instead. + /// The exception is recorded as per specification. /// "exception.stacktrace" is represented using the value of Exception.ToString. /// + [Obsolete("Call Activity.AddException instead this method will be removed in a future version.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void RecordException(this Activity activity, Exception? ex, in TagList tags) + public static void RecordException(this Activity? activity, Exception? ex, in TagList tags) { if (ex == null || activity == null) { return; } - var tagsCollection = new ActivityTagsCollection - { - { SemanticConventions.AttributeExceptionType, ex.GetType().FullName }, - { SemanticConventions.AttributeExceptionStacktrace, ex.ToInvariantString() }, - }; - - if (!string.IsNullOrWhiteSpace(ex.Message)) - { - tagsCollection.Add(SemanticConventions.AttributeExceptionMessage, ex.Message); - } - - foreach (var tag in tags) - { - tagsCollection[tag.Key] = tag.Value; - } - - activity.AddEvent(new ActivityEvent(SemanticConventions.AttributeExceptionEventName, default, tagsCollection)); + activity.AddException(ex, in tags); } } diff --git a/src/OpenTelemetry.Api/Trace/IDeferredTracerProviderBuilder.cs b/src/OpenTelemetry.Api/Trace/IDeferredTracerProviderBuilder.cs index 8be46ae443..7b0d3f7123 100644 --- a/src/OpenTelemetry.Api/Trace/IDeferredTracerProviderBuilder.cs +++ b/src/OpenTelemetry.Api/Trace/IDeferredTracerProviderBuilder.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Trace; /// diff --git a/src/OpenTelemetry.Api/Trace/Link.cs b/src/OpenTelemetry.Api/Trace/Link.cs index 45af879162..1972a624c2 100644 --- a/src/OpenTelemetry.Api/Trace/Link.cs +++ b/src/OpenTelemetry.Api/Trace/Link.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; namespace OpenTelemetry.Trace; diff --git a/src/OpenTelemetry.Api/Trace/SpanAttributes.cs b/src/OpenTelemetry.Api/Trace/SpanAttributes.cs index 84850ae048..5975830c98 100644 --- a/src/OpenTelemetry.Api/Trace/SpanAttributes.cs +++ b/src/OpenTelemetry.Api/Trace/SpanAttributes.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using OpenTelemetry.Internal; diff --git a/src/OpenTelemetry.Api/Trace/SpanContext.cs b/src/OpenTelemetry.Api/Trace/SpanContext.cs index b94f50cc5f..825ad62bb4 100644 --- a/src/OpenTelemetry.Api/Trace/SpanContext.cs +++ b/src/OpenTelemetry.Api/Trace/SpanContext.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using OpenTelemetry.Context.Propagation; @@ -85,13 +83,14 @@ public IEnumerable> TraceState { get { - if (string.IsNullOrEmpty(this.ActivityContext.TraceState)) + var traceState = this.ActivityContext.TraceState; + if (string.IsNullOrEmpty(traceState)) { return Enumerable.Empty>(); } var traceStateResult = new List>(); - TraceStateUtilsNew.AppendTraceState(this.ActivityContext.TraceState, traceStateResult); + TraceStateUtilsNew.AppendTraceState(traceState!, traceStateResult); return traceStateResult; } } diff --git a/src/OpenTelemetry.Api/Trace/SpanKind.cs b/src/OpenTelemetry.Api/Trace/SpanKind.cs index f3237a6bd4..07358f7b0e 100644 --- a/src/OpenTelemetry.Api/Trace/SpanKind.cs +++ b/src/OpenTelemetry.Api/Trace/SpanKind.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Trace; /// diff --git a/src/OpenTelemetry.Api/Trace/Status.cs b/src/OpenTelemetry.Api/Trace/Status.cs index b7d0eb35c3..a39ee8dd75 100644 --- a/src/OpenTelemetry.Api/Trace/Status.cs +++ b/src/OpenTelemetry.Api/Trace/Status.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Trace; /// diff --git a/src/OpenTelemetry.Api/Trace/StatusCode.cs b/src/OpenTelemetry.Api/Trace/StatusCode.cs index 9332d708d0..28d691c10e 100644 --- a/src/OpenTelemetry.Api/Trace/StatusCode.cs +++ b/src/OpenTelemetry.Api/Trace/StatusCode.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Trace; /// diff --git a/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs b/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs index f70598d078..47e67c6095 100644 --- a/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs +++ b/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using System.Runtime.CompilerServices; using OpenTelemetry.Internal; @@ -47,7 +45,9 @@ public ActivitySpanId ParentSpanId /// Status to be set. public void SetStatus(Status value) { - this.Activity?.SetStatus(value); +#pragma warning disable + this.Activity.SetStatus(value); +#pragma warning restore } /// diff --git a/src/OpenTelemetry.Api/Trace/Tracer.cs b/src/OpenTelemetry.Api/Trace/Tracer.cs index 44bf17e1f5..d0fb906ec1 100644 --- a/src/OpenTelemetry.Api/Trace/Tracer.cs +++ b/src/OpenTelemetry.Api/Trace/Tracer.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -54,7 +52,7 @@ public static TelemetrySpan CurrentSpan /// /// The span to be made current. /// The supplied span for call chaining. -#if NET6_0_OR_GREATER +#if NET [return: NotNullIfNotNull(nameof(span))] #endif [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/OpenTelemetry.Api/Trace/TracerProvider.cs b/src/OpenTelemetry.Api/Trace/TracerProvider.cs index 1dc0a5f293..21197d0abd 100644 --- a/src/OpenTelemetry.Api/Trace/TracerProvider.cs +++ b/src/OpenTelemetry.Api/Trace/TracerProvider.cs @@ -1,10 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Collections.Concurrent; -#if NET6_0_OR_GREATER +#if NET using System.Diagnostics.CodeAnalysis; #endif @@ -36,7 +34,7 @@ protected TracerProvider() /// Version of the instrumentation library. /// Tracer instance. public Tracer GetTracer( -#if NET6_0_OR_GREATER +#if NET [AllowNull] #endif string name, diff --git a/src/OpenTelemetry.Api/Trace/TracerProviderBuilder.cs b/src/OpenTelemetry.Api/Trace/TracerProviderBuilder.cs index 0a8c3dae3d..ac9b7a2dbe 100644 --- a/src/OpenTelemetry.Api/Trace/TracerProviderBuilder.cs +++ b/src/OpenTelemetry.Api/Trace/TracerProviderBuilder.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; namespace OpenTelemetry.Trace; diff --git a/src/OpenTelemetry.Exporter.Console/.publicApi/Experimental/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Console/.publicApi/Experimental/PublicAPI.Unshipped.txt index be114835cc..e69de29bb2 100644 --- a/src/OpenTelemetry.Exporter.Console/.publicApi/Experimental/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Console/.publicApi/Experimental/PublicAPI.Unshipped.txt @@ -1,3 +0,0 @@ -static OpenTelemetry.Logs.ConsoleExporterLoggingExtensions.AddConsoleExporter(this OpenTelemetry.Logs.LoggerProviderBuilder loggerProviderBuilder) -> OpenTelemetry.Logs.LoggerProviderBuilder -static OpenTelemetry.Logs.ConsoleExporterLoggingExtensions.AddConsoleExporter(this OpenTelemetry.Logs.LoggerProviderBuilder loggerProviderBuilder, string name, System.Action configure) -> OpenTelemetry.Logs.LoggerProviderBuilder -static OpenTelemetry.Logs.ConsoleExporterLoggingExtensions.AddConsoleExporter(this OpenTelemetry.Logs.LoggerProviderBuilder loggerProviderBuilder, System.Action configure) -> OpenTelemetry.Logs.LoggerProviderBuilder diff --git a/src/OpenTelemetry.Exporter.Console/.publicApi/Stable/PublicAPI.Shipped.txt b/src/OpenTelemetry.Exporter.Console/.publicApi/Stable/PublicAPI.Shipped.txt index 6f900a1f6f..9927c4d147 100644 --- a/src/OpenTelemetry.Exporter.Console/.publicApi/Stable/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry.Exporter.Console/.publicApi/Stable/PublicAPI.Shipped.txt @@ -1,8 +1,9 @@ +#nullable enable OpenTelemetry.Exporter.ConsoleActivityExporter -OpenTelemetry.Exporter.ConsoleActivityExporter.ConsoleActivityExporter(OpenTelemetry.Exporter.ConsoleExporterOptions options) -> void +OpenTelemetry.Exporter.ConsoleActivityExporter.ConsoleActivityExporter(OpenTelemetry.Exporter.ConsoleExporterOptions! options) -> void OpenTelemetry.Exporter.ConsoleExporter -OpenTelemetry.Exporter.ConsoleExporter.ConsoleExporter(OpenTelemetry.Exporter.ConsoleExporterOptions options) -> void -OpenTelemetry.Exporter.ConsoleExporter.WriteLine(string message) -> void +OpenTelemetry.Exporter.ConsoleExporter.ConsoleExporter(OpenTelemetry.Exporter.ConsoleExporterOptions! options) -> void +OpenTelemetry.Exporter.ConsoleExporter.WriteLine(string! message) -> void OpenTelemetry.Exporter.ConsoleExporterOptions OpenTelemetry.Exporter.ConsoleExporterOptions.ConsoleExporterOptions() -> void OpenTelemetry.Exporter.ConsoleExporterOptions.Targets.get -> OpenTelemetry.Exporter.ConsoleExporterOutputTargets @@ -11,23 +12,26 @@ OpenTelemetry.Exporter.ConsoleExporterOutputTargets OpenTelemetry.Exporter.ConsoleExporterOutputTargets.Console = 1 -> OpenTelemetry.Exporter.ConsoleExporterOutputTargets OpenTelemetry.Exporter.ConsoleExporterOutputTargets.Debug = 2 -> OpenTelemetry.Exporter.ConsoleExporterOutputTargets OpenTelemetry.Exporter.ConsoleLogRecordExporter -OpenTelemetry.Exporter.ConsoleLogRecordExporter.ConsoleLogRecordExporter(OpenTelemetry.Exporter.ConsoleExporterOptions options) -> void +OpenTelemetry.Exporter.ConsoleLogRecordExporter.ConsoleLogRecordExporter(OpenTelemetry.Exporter.ConsoleExporterOptions! options) -> void OpenTelemetry.Exporter.ConsoleMetricExporter -OpenTelemetry.Exporter.ConsoleMetricExporter.ConsoleMetricExporter(OpenTelemetry.Exporter.ConsoleExporterOptions options) -> void +OpenTelemetry.Exporter.ConsoleMetricExporter.ConsoleMetricExporter(OpenTelemetry.Exporter.ConsoleExporterOptions! options) -> void OpenTelemetry.Logs.ConsoleExporterLoggingExtensions OpenTelemetry.Metrics.ConsoleExporterMetricsExtensions OpenTelemetry.Trace.ConsoleExporterHelperExtensions -override OpenTelemetry.Exporter.ConsoleActivityExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult +override OpenTelemetry.Exporter.ConsoleActivityExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult override OpenTelemetry.Exporter.ConsoleLogRecordExporter.Dispose(bool disposing) -> void -override OpenTelemetry.Exporter.ConsoleLogRecordExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult -override OpenTelemetry.Exporter.ConsoleMetricExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult -static OpenTelemetry.Logs.ConsoleExporterLoggingExtensions.AddConsoleExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions -static OpenTelemetry.Logs.ConsoleExporterLoggingExtensions.AddConsoleExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions, System.Action configure) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions -static OpenTelemetry.Metrics.ConsoleExporterMetricsExtensions.AddConsoleExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.ConsoleExporterMetricsExtensions.AddConsoleExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, string name, System.Action configureExporterAndMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.ConsoleExporterMetricsExtensions.AddConsoleExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, string name, System.Action configureExporter) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.ConsoleExporterMetricsExtensions.AddConsoleExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configureExporterAndMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.ConsoleExporterMetricsExtensions.AddConsoleExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configureExporter) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Trace.ConsoleExporterHelperExtensions.AddConsoleExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder) -> OpenTelemetry.Trace.TracerProviderBuilder -static OpenTelemetry.Trace.ConsoleExporterHelperExtensions.AddConsoleExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder, string name, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder -static OpenTelemetry.Trace.ConsoleExporterHelperExtensions.AddConsoleExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder +override OpenTelemetry.Exporter.ConsoleLogRecordExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult +override OpenTelemetry.Exporter.ConsoleMetricExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult +static OpenTelemetry.Logs.ConsoleExporterLoggingExtensions.AddConsoleExporter(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.ConsoleExporterLoggingExtensions.AddConsoleExporter(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, string? name, System.Action? configure) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.ConsoleExporterLoggingExtensions.AddConsoleExporter(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, System.Action! configure) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.ConsoleExporterLoggingExtensions.AddConsoleExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +static OpenTelemetry.Logs.ConsoleExporterLoggingExtensions.AddConsoleExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions, System.Action? configure) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +static OpenTelemetry.Metrics.ConsoleExporterMetricsExtensions.AddConsoleExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.ConsoleExporterMetricsExtensions.AddConsoleExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, string? name, System.Action? configureExporterAndMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.ConsoleExporterMetricsExtensions.AddConsoleExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, string? name, System.Action? configureExporter) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.ConsoleExporterMetricsExtensions.AddConsoleExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Action? configureExporterAndMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.ConsoleExporterMetricsExtensions.AddConsoleExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Action! configureExporter) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Trace.ConsoleExporterHelperExtensions.AddConsoleExporter(this OpenTelemetry.Trace.TracerProviderBuilder! builder) -> OpenTelemetry.Trace.TracerProviderBuilder! +static OpenTelemetry.Trace.ConsoleExporterHelperExtensions.AddConsoleExporter(this OpenTelemetry.Trace.TracerProviderBuilder! builder, string? name, System.Action? configure) -> OpenTelemetry.Trace.TracerProviderBuilder! +static OpenTelemetry.Trace.ConsoleExporterHelperExtensions.AddConsoleExporter(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action! configure) -> OpenTelemetry.Trace.TracerProviderBuilder! diff --git a/src/OpenTelemetry.Exporter.Console/CHANGELOG.md b/src/OpenTelemetry.Exporter.Console/CHANGELOG.md index 6605e9d684..2702c92541 100644 --- a/src/OpenTelemetry.Exporter.Console/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Console/CHANGELOG.md @@ -1,7 +1,40 @@ # Changelog +This file contains individual changes for the OpenTelemetry.Exporter.Console +package. For highlights and announcements covering all components see: [Release +Notes](../../RELEASENOTES.md). + ## Unreleased +* Added direct reference to `System.Text.Json` for the `net8.0` target with + minimum version of `8.0.5` in response to + [CVE-2024-30105](https://github.com/advisories/GHSA-hh2w-p6rv-4g7w) & + [CVE-2024-43485](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-43485). + ([#5874](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5874), + [#5891](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5891)) + +* Added support for Instrumentation Scope Attributes (i.e + [ActivitySource.Tags](https://learn.microsoft.com/dotnet/api/system.diagnostics.activitysource.tags)) + when writing traces to the console. + ([#5935](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5935)) + +## 1.10.0-beta.1 + +Released 2024-Sep-30 + +## 1.9.0 + +Released 2024-Jun-14 + +## 1.9.0-rc.1 + +Released 2024-Jun-07 + +* The experimental APIs previously covered by `OTEL1000` + (`LoggerProviderBuilder.AddConsoleExporter` extension) are now part of the + public API and supported in stable builds. + ([#5648](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5648)) + ## 1.9.0-alpha.1 Released 2024-May-20 @@ -33,9 +66,9 @@ Released 2023-Dec-08 Released 2023-Nov-29 -* Add support for Instrumentation Scope Attributes (i.e [Meter - Tags](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.meter.tags)), - fixing issue +* Added support for Instrumentation Scope Attributes (i.e + [Meter.Tags](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.meter.tags)), + when writing metrics to the console, fixing issue [#4563](https://github.com/open-telemetry/opentelemetry-dotnet/issues/4563). ([#5089](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5089)) @@ -93,7 +126,8 @@ Released 2023-May-25 ([#4507](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4507)) * Added direct reference to `System.Text.Encodings.Web` with minimum version of -`4.7.2` in response to [CVE-2021-26701](https://github.com/dotnet/runtime/issues/49377). + `4.7.2` in response to + [CVE-2021-26701](https://github.com/dotnet/runtime/issues/49377). ([#4390](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4390)) * Updated `LogRecord` console output: `Body` is now shown (if set), diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleActivityExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleActivityExporter.cs index 9197cd1287..c1b707b3e4 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleActivityExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleActivityExporter.cs @@ -31,12 +31,6 @@ public override ExportResult Export(in Batch batch) this.WriteLine($"Activity.ParentSpanId: {activity.ParentSpanId}"); } - this.WriteLine($"Activity.ActivitySourceName: {activity.Source.Name}"); - if (!string.IsNullOrEmpty(activity.Source.Version)) - { - this.WriteLine($"Activity.ActivitySourceVersion: {activity.Source.Version}"); - } - this.WriteLine($"Activity.DisplayName: {activity.DisplayName}"); this.WriteLine($"Activity.Kind: {activity.Kind}"); this.WriteLine($"Activity.StartTime: {activity.StartTimeUtc:yyyy-MM-ddTHH:mm:ss.fffffffZ}"); @@ -117,13 +111,32 @@ public override ExportResult Export(in Batch batch) } } + this.WriteLine("Instrumentation scope (ActivitySource):"); + this.WriteLine($" Name: {activity.Source.Name}"); + if (!string.IsNullOrEmpty(activity.Source.Version)) + { + this.WriteLine($" Version: {activity.Source.Version}"); + } + + if (activity.Source.Tags?.Any() == true) + { + this.WriteLine(" Tags:"); + foreach (var activitySourceTag in activity.Source.Tags) + { + if (this.TagWriter.TryTransformTag(activitySourceTag, out var result)) + { + this.WriteLine($" {result.Key}: {result.Value}"); + } + } + } + var resource = this.ParentProvider.GetResource(); if (resource != Resource.Empty) { this.WriteLine("Resource associated with Activity:"); foreach (var resourceAttribute in resource.Attributes) { - if (this.TagWriter.TryTransformTag(resourceAttribute, out var result)) + if (this.TagWriter.TryTransformTag(resourceAttribute.Key, resourceAttribute.Value, out var result)) { this.WriteLine($" {result.Key}: {result.Value}"); } diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleExporterHelperExtensions.cs b/src/OpenTelemetry.Exporter.Console/ConsoleExporterHelperExtensions.cs index b79aa0d9ed..68a811669e 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleExporterHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleExporterHelperExtensions.cs @@ -31,13 +31,13 @@ public static TracerProviderBuilder AddConsoleExporter(this TracerProviderBuilde /// Adds Console exporter to the TracerProvider. /// /// builder to use. - /// Name which is used when retrieving options. - /// Callback action for configuring . + /// Optional name which is used when retrieving options. + /// Optional callback action for configuring . /// The instance of to chain the calls. public static TracerProviderBuilder AddConsoleExporter( this TracerProviderBuilder builder, - string name, - Action configure) + string? name, + Action? configure) { Guard.ThrowIfNull(builder); diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleExporterLoggingExtensions.cs b/src/OpenTelemetry.Exporter.Console/ConsoleExporterLoggingExtensions.cs index 6b024498cb..498aa3a926 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleExporterLoggingExtensions.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleExporterLoggingExtensions.cs @@ -1,9 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if EXPOSE_EXPERIMENTAL_FEATURES && NET8_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -#endif using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using OpenTelemetry.Exporter; @@ -18,7 +15,7 @@ public static class ConsoleExporterLoggingExtensions /// /// options to use. /// The instance of to chain the calls. - /// todo: [Obsolete("Call LoggerProviderBuilder.AddConsoleExporter instead this method will be removed in a future version.")] + // TODO: [Obsolete("Call LoggerProviderBuilder.AddConsoleExporter instead this method will be removed in a future version.")] public static OpenTelemetryLoggerOptions AddConsoleExporter(this OpenTelemetryLoggerOptions loggerOptions) => AddConsoleExporter(loggerOptions, configure: null); @@ -26,10 +23,10 @@ public static OpenTelemetryLoggerOptions AddConsoleExporter(this OpenTelemetryLo /// Adds Console exporter with OpenTelemetryLoggerOptions. /// /// options to use. - /// Callback action for configuring . + /// Optional callback action for configuring . /// The instance of to chain the calls. - /// todo: [Obsolete("Call LoggerProviderBuilder.AddConsoleExporter instead this method will be removed in a future version.")] - public static OpenTelemetryLoggerOptions AddConsoleExporter(this OpenTelemetryLoggerOptions loggerOptions, Action configure) + // TODO: [Obsolete("Call LoggerProviderBuilder.AddConsoleExporter instead this method will be removed in a future version.")] + public static OpenTelemetryLoggerOptions AddConsoleExporter(this OpenTelemetryLoggerOptions loggerOptions, Action? configure) { Guard.ThrowIfNull(loggerOptions); @@ -38,82 +35,37 @@ public static OpenTelemetryLoggerOptions AddConsoleExporter(this OpenTelemetryLo return loggerOptions.AddProcessor(new SimpleLogRecordExportProcessor(new ConsoleLogRecordExporter(options))); } -#if EXPOSE_EXPERIMENTAL_FEATURES - /// - /// Adds Console exporter with LoggerProviderBuilder. - /// - /// WARNING: This is an experimental API which might change or be removed in the future. Use at your own risk. - /// . - /// The supplied instance of to chain the calls. -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else /// /// Adds Console exporter with LoggerProviderBuilder. /// /// . /// The supplied instance of to chain the calls. - internal -#endif - static LoggerProviderBuilder AddConsoleExporter( + public static LoggerProviderBuilder AddConsoleExporter( this LoggerProviderBuilder loggerProviderBuilder) => AddConsoleExporter(loggerProviderBuilder, name: null, configure: null); -#if EXPOSE_EXPERIMENTAL_FEATURES - /// - /// Adds Console exporter with LoggerProviderBuilder. - /// - /// - /// . - /// Callback action for configuring . - /// The supplied instance of to chain the calls. -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else /// /// Adds Console exporter with LoggerProviderBuilder. /// /// . /// Callback action for configuring . /// The supplied instance of to chain the calls. - internal -#endif - static LoggerProviderBuilder AddConsoleExporter( + public static LoggerProviderBuilder AddConsoleExporter( this LoggerProviderBuilder loggerProviderBuilder, Action configure) => AddConsoleExporter(loggerProviderBuilder, name: null, configure); -#if EXPOSE_EXPERIMENTAL_FEATURES - /// - /// Adds Console exporter with LoggerProviderBuilder. - /// - /// - /// . - /// Name which is used when retrieving options. - /// Callback action for configuring . - /// The supplied instance of to chain the calls. -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else /// /// Adds Console exporter with LoggerProviderBuilder. /// /// . - /// Name which is used when retrieving options. - /// Callback action for configuring . + /// Optional name which is used when retrieving options. + /// Optional callback action for configuring . /// The supplied instance of to chain the calls. - internal -#endif - static LoggerProviderBuilder AddConsoleExporter( + public static LoggerProviderBuilder AddConsoleExporter( this LoggerProviderBuilder loggerProviderBuilder, - string name, - Action configure) + string? name, + Action? configure) { Guard.ThrowIfNull(loggerProviderBuilder); diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricsExtensions.cs b/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricsExtensions.cs index 103593efdd..78ee90829d 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricsExtensions.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricsExtensions.cs @@ -37,13 +37,13 @@ public static MeterProviderBuilder AddConsoleExporter(this MeterProviderBuilder /// Adds to the . /// /// builder to use. - /// Name which is used when retrieving options. - /// Callback action for configuring . + /// Optional name which is used when retrieving options. + /// Optional callback action for configuring . /// The instance of to chain the calls. public static MeterProviderBuilder AddConsoleExporter( this MeterProviderBuilder builder, - string name, - Action configureExporter) + string? name, + Action? configureExporter) { Guard.ThrowIfNull(builder); @@ -72,7 +72,7 @@ public static MeterProviderBuilder AddConsoleExporter( /// The instance of to chain the calls. public static MeterProviderBuilder AddConsoleExporter( this MeterProviderBuilder builder, - Action configureExporterAndMetricReader) + Action? configureExporterAndMetricReader) => AddConsoleExporter(builder, name: null, configureExporterAndMetricReader); /// @@ -86,8 +86,8 @@ public static MeterProviderBuilder AddConsoleExporter( /// The instance of to chain the calls. public static MeterProviderBuilder AddConsoleExporter( this MeterProviderBuilder builder, - string name, - Action configureExporterAndMetricReader) + string? name, + Action? configureExporterAndMetricReader) { Guard.ThrowIfNull(builder); diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs index 3ad9091095..0dfe396b30 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs @@ -10,9 +10,9 @@ namespace OpenTelemetry.Exporter; public class ConsoleLogRecordExporter : ConsoleExporter { private const int RightPaddingLength = 35; - private readonly object syncObject = new(); + private readonly Lock syncObject = new(); private bool disposed; - private string disposedStackTrace; + private string? disposedStackTrace; private bool isDisposeMessageSent; public ConsoleLogRecordExporter(ConsoleExporterOptions options) @@ -39,7 +39,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(this.disposedStackTrace); + this.WriteLine(this.disposedStackTrace!); } return ExportResult.Failure; @@ -90,7 +90,7 @@ public override ExportResult Export(in Batch batch) // See https://github.com/open-telemetry/opentelemetry-dotnet/pull/3182 // for explanation. var valueToTransform = logRecord.Attributes[i].Key.Equals("{OriginalFormat}") - ? new KeyValuePair("OriginalFormat (a.k.a Body)", logRecord.Attributes[i].Value) + ? new KeyValuePair("OriginalFormat (a.k.a Body)", logRecord.Attributes[i].Value) : logRecord.Attributes[i]; if (this.TagWriter.TryTransformTag(valueToTransform, out var result)) @@ -125,7 +125,7 @@ void ProcessScope(LogRecordScope scope, ConsoleLogRecordExporter exporter) exporter.WriteLine("LogRecord.ScopeValues (Key:Value):"); } - foreach (KeyValuePair scopeItem in scope) + foreach (KeyValuePair scopeItem in scope) { if (this.TagWriter.TryTransformTag(scopeItem, out var result)) { @@ -140,7 +140,7 @@ void ProcessScope(LogRecordScope scope, ConsoleLogRecordExporter exporter) this.WriteLine("\nResource associated with LogRecord:"); foreach (var resourceAttribute in resource.Attributes) { - if (this.TagWriter.TryTransformTag(resourceAttribute, out var result)) + if (this.TagWriter.TryTransformTag(resourceAttribute.Key, resourceAttribute.Value, out var result)) { this.WriteLine($"{result.Key}: {result.Value}"); } diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs index 4bd9772b21..97ba3bbe25 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs @@ -10,8 +10,6 @@ namespace OpenTelemetry.Exporter; public class ConsoleMetricExporter : ConsoleExporter { - private Resource resource; - public ConsoleMetricExporter(ConsoleExporterOptions options) : base(options) { @@ -19,30 +17,13 @@ public ConsoleMetricExporter(ConsoleExporterOptions options) public override ExportResult Export(in Batch batch) { - if (this.resource == null) - { - this.resource = this.ParentProvider.GetResource(); - if (this.resource != Resource.Empty) - { - this.WriteLine("Resource associated with Metric:"); - foreach (var resourceAttribute in this.resource.Attributes) - { - if (this.TagWriter.TryTransformTag(resourceAttribute, out var result)) - { - this.WriteLine($" {result.Key}: {result.Value}"); - } - } - } - } - foreach (var metric in batch) { var msg = new StringBuilder($"\n"); msg.Append($"Metric Name: {metric.Name}"); if (metric.Description != string.Empty) { - msg.Append(", "); - msg.Append(metric.Description); + msg.Append($", Description: {metric.Description}"); } if (metric.Unit != string.Empty) @@ -50,30 +31,8 @@ public override ExportResult Export(in Batch batch) msg.Append($", Unit: {metric.Unit}"); } - if (!string.IsNullOrEmpty(metric.MeterName)) - { - msg.Append($", Meter: {metric.MeterName}"); - - if (!string.IsNullOrEmpty(metric.MeterVersion)) - { - msg.Append($"/{metric.MeterVersion}"); - } - } - this.WriteLine(msg.ToString()); - if (metric.MeterTags != null) - { - foreach (var meterTag in metric.MeterTags) - { - this.WriteLine("\tMeter Tags:"); - 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; @@ -220,7 +179,7 @@ public override ExportResult Export(in Batch batch) { if (!appendedTagString) { - exemplarString.Append(" Filtered Tags : "); + exemplarString.Append(" Filtered Tags: "); appendedTagString = true; } @@ -257,6 +216,38 @@ public override ExportResult Export(in Batch batch) } 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 2f36df0091..fcaf9cdb43 100644 --- a/src/OpenTelemetry.Exporter.Console/Implementation/ConsoleTagWriter.cs +++ b/src/OpenTelemetry.Exporter.Console/Implementation/ConsoleTagWriter.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using System.Text; using OpenTelemetry.Internal; @@ -21,9 +19,14 @@ public ConsoleTagWriter(Action onUnsupportedTagDropped) } public bool TryTransformTag(KeyValuePair tag, out KeyValuePair result) + { + return this.TryTransformTag(tag.Key, tag.Value, out result); + } + + public bool TryTransformTag(string key, object? value, out KeyValuePair result) { ConsoleTag consoleTag = default; - if (this.TryWriteTag(ref consoleTag, tag)) + if (this.TryWriteTag(ref consoleTag, key, value)) { result = new KeyValuePair(consoleTag.Key!, consoleTag.Value!); return true; diff --git a/src/OpenTelemetry.Exporter.Console/OpenTelemetry.Exporter.Console.csproj b/src/OpenTelemetry.Exporter.Console/OpenTelemetry.Exporter.Console.csproj index 1f1aa356b5..75bcbe0c3c 100644 --- a/src/OpenTelemetry.Exporter.Console/OpenTelemetry.Exporter.Console.csproj +++ b/src/OpenTelemetry.Exporter.Console/OpenTelemetry.Exporter.Console.csproj @@ -5,20 +5,13 @@ Console exporter for OpenTelemetry .NET $(PackageTags);Console;distributed-tracing core- - - - disable + true $(NoWarn),1591 - - - - - diff --git a/src/OpenTelemetry.Exporter.InMemory/.publicApi/Experimental/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.InMemory/.publicApi/Experimental/PublicAPI.Unshipped.txt index f7378ee36c..e69de29bb2 100644 --- a/src/OpenTelemetry.Exporter.InMemory/.publicApi/Experimental/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.InMemory/.publicApi/Experimental/PublicAPI.Unshipped.txt @@ -1 +0,0 @@ -static OpenTelemetry.Logs.InMemoryExporterLoggingExtensions.AddInMemoryExporter(this OpenTelemetry.Logs.LoggerProviderBuilder loggerProviderBuilder, System.Collections.Generic.ICollection exportedItems) -> OpenTelemetry.Logs.LoggerProviderBuilder diff --git a/src/OpenTelemetry.Exporter.InMemory/.publicApi/Stable/PublicAPI.Shipped.txt b/src/OpenTelemetry.Exporter.InMemory/.publicApi/Stable/PublicAPI.Shipped.txt index 499065e2d0..3a85e139a8 100644 --- a/src/OpenTelemetry.Exporter.InMemory/.publicApi/Stable/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry.Exporter.InMemory/.publicApi/Stable/PublicAPI.Shipped.txt @@ -1,24 +1,26 @@ +#nullable enable OpenTelemetry.Exporter.InMemoryExporter -OpenTelemetry.Exporter.InMemoryExporter.InMemoryExporter(System.Collections.Generic.ICollection exportedItems) -> void +OpenTelemetry.Exporter.InMemoryExporter.InMemoryExporter(System.Collections.Generic.ICollection! exportedItems) -> void OpenTelemetry.Logs.InMemoryExporterLoggingExtensions OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions OpenTelemetry.Metrics.MetricSnapshot -OpenTelemetry.Metrics.MetricSnapshot.Description.get -> string -OpenTelemetry.Metrics.MetricSnapshot.MeterName.get -> string -OpenTelemetry.Metrics.MetricSnapshot.MeterVersion.get -> string -OpenTelemetry.Metrics.MetricSnapshot.MetricPoints.get -> System.Collections.Generic.IReadOnlyList -OpenTelemetry.Metrics.MetricSnapshot.MetricSnapshot(OpenTelemetry.Metrics.Metric metric) -> void +OpenTelemetry.Metrics.MetricSnapshot.Description.get -> string! +OpenTelemetry.Metrics.MetricSnapshot.MeterName.get -> string! +OpenTelemetry.Metrics.MetricSnapshot.MeterVersion.get -> string! +OpenTelemetry.Metrics.MetricSnapshot.MetricPoints.get -> System.Collections.Generic.IReadOnlyList! +OpenTelemetry.Metrics.MetricSnapshot.MetricSnapshot(OpenTelemetry.Metrics.Metric! metric) -> void OpenTelemetry.Metrics.MetricSnapshot.MetricType.get -> OpenTelemetry.Metrics.MetricType -OpenTelemetry.Metrics.MetricSnapshot.Name.get -> string -OpenTelemetry.Metrics.MetricSnapshot.Unit.get -> string +OpenTelemetry.Metrics.MetricSnapshot.Name.get -> string! +OpenTelemetry.Metrics.MetricSnapshot.Unit.get -> string! OpenTelemetry.Trace.InMemoryExporterHelperExtensions override OpenTelemetry.Exporter.InMemoryExporter.Dispose(bool disposing) -> void -override OpenTelemetry.Exporter.InMemoryExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult -static OpenTelemetry.Logs.InMemoryExporterLoggingExtensions.AddInMemoryExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions, System.Collections.Generic.ICollection exportedItems) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions -static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, string name, System.Collections.Generic.ICollection exportedItems, System.Action configureMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, string name, System.Collections.Generic.ICollection exportedItems, System.Action configureMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Collections.Generic.ICollection exportedItems) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Collections.Generic.ICollection exportedItems, System.Action configureMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Collections.Generic.ICollection exportedItems) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Collections.Generic.ICollection exportedItems, System.Action configureMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Trace.InMemoryExporterHelperExtensions.AddInMemoryExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Collections.Generic.ICollection exportedItems) -> OpenTelemetry.Trace.TracerProviderBuilder +override OpenTelemetry.Exporter.InMemoryExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult +static OpenTelemetry.Logs.InMemoryExporterLoggingExtensions.AddInMemoryExporter(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, System.Collections.Generic.ICollection! exportedItems) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.InMemoryExporterLoggingExtensions.AddInMemoryExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions, System.Collections.Generic.ICollection! exportedItems) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, string? name, System.Collections.Generic.ICollection! exportedItems, System.Action? configureMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, string? name, System.Collections.Generic.ICollection! exportedItems, System.Action? configureMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Collections.Generic.ICollection! exportedItems) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Collections.Generic.ICollection! exportedItems, System.Action! configureMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Collections.Generic.ICollection! exportedItems) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Collections.Generic.ICollection! exportedItems, System.Action! configureMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Trace.InMemoryExporterHelperExtensions.AddInMemoryExporter(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Collections.Generic.ICollection! exportedItems) -> OpenTelemetry.Trace.TracerProviderBuilder! diff --git a/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md b/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md index d4aa853c67..37dcd673c8 100644 --- a/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md @@ -1,7 +1,28 @@ # Changelog +This file contains individual changes for the OpenTelemetry.Exporter.InMemory +package. For highlights and announcements covering all components see: [Release +Notes](../../RELEASENOTES.md). + ## Unreleased +## 1.10.0-beta.1 + +Released 2024-Sep-30 + +## 1.9.0 + +Released 2024-Jun-14 + +## 1.9.0-rc.1 + +Released 2024-Jun-07 + +* The experimental APIs previously covered by `OTEL1000` + (`LoggerProviderBuilder.AddInMemoryExporter` extension) are now part of the + public API and supported in stable builds. + ([#5648](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5648)) + ## 1.9.0-alpha.1 Released 2024-May-20 diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs index dedcf65b37..8533108ce8 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs @@ -6,10 +6,10 @@ namespace OpenTelemetry.Exporter; public class InMemoryExporter : BaseExporter where T : class { - private readonly ICollection exportedItems; + private readonly ICollection? exportedItems; private readonly ExportFunc onExport; private bool disposed; - private string disposedStackTrace; + private string? disposedStackTrace; public InMemoryExporter(ICollection exportedItems) { diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs index 982595f882..19b2079921 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs @@ -1,9 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if EXPOSE_EXPERIMENTAL_FEATURES && NET8_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -#endif using OpenTelemetry.Exporter; using OpenTelemetry.Internal; @@ -17,7 +14,7 @@ public static class InMemoryExporterLoggingExtensions /// options to use. /// Collection which will be populated with the exported . /// The supplied instance of to chain the calls. - /// todo: [Obsolete("Call LoggerProviderBuilder.AddInMemoryExporter instead this method will be removed in a future version.")] + // TODO: [Obsolete("Call LoggerProviderBuilder.AddInMemoryExporter instead this method will be removed in a future version.")] public static OpenTelemetryLoggerOptions AddInMemoryExporter( this OpenTelemetryLoggerOptions loggerOptions, ICollection exportedItems) @@ -31,28 +28,13 @@ public static OpenTelemetryLoggerOptions AddInMemoryExporter( new SimpleLogRecordExportProcessor(logExporter)); } -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Adds InMemory exporter to the LoggerProviderBuilder. /// - /// WARNING: This is an experimental API which might change or be removed in the future. Use at your own risk. /// . /// Collection which will be populated with the exported . /// The supplied instance of to chain the calls. -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else - /// - /// Adds InMemory exporter to the LoggerProviderBuilder. - /// - /// . - /// Collection which will be populated with the exported . - /// The supplied instance of to chain the calls. - internal -#endif - static LoggerProviderBuilder AddInMemoryExporter( + public static LoggerProviderBuilder AddInMemoryExporter( this LoggerProviderBuilder loggerProviderBuilder, ICollection exportedItems) { diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs index e645b67b45..da5ecda24d 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs @@ -51,15 +51,15 @@ public static MeterProviderBuilder AddInMemoryExporter( /// Be aware that may continue to be updated after export. /// /// builder to use. - /// Name which is used when retrieving options. + /// Optional name which is used when retrieving options. /// Collection which will be populated with the exported . - /// Callback action for configuring . + /// Optional callback action for configuring . /// The instance of to chain the calls. public static MeterProviderBuilder AddInMemoryExporter( this MeterProviderBuilder builder, - string name, + string? name, ICollection exportedItems, - Action configureMetricReader) + Action? configureMetricReader) { Guard.ThrowIfNull(builder); Guard.ThrowIfNull(exportedItems); @@ -119,15 +119,15 @@ public static MeterProviderBuilder AddInMemoryExporter( /// Use this if you need a copy of that will not be updated after export. /// /// builder to use. - /// Name which is used when retrieving options. + /// Optional name which is used when retrieving options. /// Collection which will be populated with the exported represented as . - /// Callback action for configuring . + /// Optional callback action for configuring . /// The instance of to chain the calls. public static MeterProviderBuilder AddInMemoryExporter( this MeterProviderBuilder builder, - string name, + string? name, ICollection exportedItems, - Action configureMetricReader) + Action? configureMetricReader) { Guard.ThrowIfNull(builder); Guard.ThrowIfNull(exportedItems); diff --git a/src/OpenTelemetry.Exporter.InMemory/OpenTelemetry.Exporter.InMemory.csproj b/src/OpenTelemetry.Exporter.InMemory/OpenTelemetry.Exporter.InMemory.csproj index f27ac13801..8ca97988f6 100644 --- a/src/OpenTelemetry.Exporter.InMemory/OpenTelemetry.Exporter.InMemory.csproj +++ b/src/OpenTelemetry.Exporter.InMemory/OpenTelemetry.Exporter.InMemory.csproj @@ -5,9 +5,6 @@ In-memory exporter for OpenTelemetry .NET $(PackageTags) core- - - - disable diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Experimental/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Experimental/PublicAPI.Unshipped.txt index e6bd747c9d..e69de29bb2 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Experimental/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Experimental/PublicAPI.Unshipped.txt @@ -1,5 +0,0 @@ -static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.LoggerProviderBuilder! builder) -> OpenTelemetry.Logs.LoggerProviderBuilder! -static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.LoggerProviderBuilder! builder, string? name, System.Action? configureExporterAndProcessor) -> OpenTelemetry.Logs.LoggerProviderBuilder! -static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.LoggerProviderBuilder! builder, string? name, System.Action? configureExporter) -> OpenTelemetry.Logs.LoggerProviderBuilder! -static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.LoggerProviderBuilder! builder, System.Action! configureExporterAndProcessor) -> OpenTelemetry.Logs.LoggerProviderBuilder! -static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.LoggerProviderBuilder! builder, System.Action! configureExporter) -> OpenTelemetry.Logs.LoggerProviderBuilder! diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/PublicAPI.Shipped.txt b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/PublicAPI.Shipped.txt index 2fd5a42942..f46941162a 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/PublicAPI.Shipped.txt @@ -1,8 +1,4 @@ #nullable enable -~OpenTelemetry.Exporter.OtlpMetricExporter.OtlpMetricExporter(OpenTelemetry.Exporter.OtlpExporterOptions options) -> void -~OpenTelemetry.Exporter.OtlpTraceExporter.OtlpTraceExporter(OpenTelemetry.Exporter.OtlpExporterOptions options) -> void -~override OpenTelemetry.Exporter.OtlpMetricExporter.Export(in OpenTelemetry.Batch metrics) -> OpenTelemetry.ExportResult -~override OpenTelemetry.Exporter.OtlpTraceExporter.Export(in OpenTelemetry.Batch activityBatch) -> OpenTelemetry.ExportResult OpenTelemetry.Exporter.OtlpExporterOptions OpenTelemetry.Exporter.OtlpExporterOptions.BatchExportProcessorOptions.get -> OpenTelemetry.BatchExportProcessorOptions! OpenTelemetry.Exporter.OtlpExporterOptions.BatchExportProcessorOptions.set -> void @@ -25,14 +21,23 @@ OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf = 1 -> OpenTelemetry.Expo OpenTelemetry.Exporter.OtlpLogExporter OpenTelemetry.Exporter.OtlpLogExporter.OtlpLogExporter(OpenTelemetry.Exporter.OtlpExporterOptions! options) -> void OpenTelemetry.Exporter.OtlpMetricExporter +OpenTelemetry.Exporter.OtlpMetricExporter.OtlpMetricExporter(OpenTelemetry.Exporter.OtlpExporterOptions! options) -> void OpenTelemetry.Exporter.OtlpTraceExporter +OpenTelemetry.Exporter.OtlpTraceExporter.OtlpTraceExporter(OpenTelemetry.Exporter.OtlpExporterOptions! options) -> void OpenTelemetry.Logs.OtlpLogExporterHelperExtensions OpenTelemetry.Metrics.OtlpMetricExporterExtensions OpenTelemetry.OpenTelemetryBuilderOtlpExporterExtensions OpenTelemetry.Trace.OtlpTraceExporterHelperExtensions override OpenTelemetry.Exporter.OtlpLogExporter.Export(in OpenTelemetry.Batch logRecordBatch) -> OpenTelemetry.ExportResult +override OpenTelemetry.Exporter.OtlpMetricExporter.Export(in OpenTelemetry.Batch metrics) -> OpenTelemetry.ExportResult override OpenTelemetry.Exporter.OtlpMetricExporter.OnShutdown(int timeoutMilliseconds) -> bool +override OpenTelemetry.Exporter.OtlpTraceExporter.Export(in OpenTelemetry.Batch activityBatch) -> OpenTelemetry.ExportResult override OpenTelemetry.Exporter.OtlpTraceExporter.OnShutdown(int timeoutMilliseconds) -> bool +static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.LoggerProviderBuilder! builder) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.LoggerProviderBuilder! builder, string? name, System.Action? configureExporterAndProcessor) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.LoggerProviderBuilder! builder, string? name, System.Action? configureExporter) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.LoggerProviderBuilder! builder, System.Action! configureExporterAndProcessor) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.LoggerProviderBuilder! builder, System.Action! configureExporter) -> OpenTelemetry.Logs.LoggerProviderBuilder! static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions, string? name, System.Action? configureExporterAndProcessor) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions, string? name, System.Action? configure) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OpenTelemetryBuilderOtlpExporterExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OpenTelemetryBuilderOtlpExporterExtensions.cs index 80164f0fa5..6b1d2afeba 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OpenTelemetryBuilderOtlpExporterExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OpenTelemetryBuilderOtlpExporterExtensions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using Microsoft.Extensions.Configuration; using OpenTelemetry.Exporter; using OpenTelemetry.Internal; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OtlpExporterBuilder.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OtlpExporterBuilder.cs index 4c3d37cf28..d5a04aa60f 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OtlpExporterBuilder.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OtlpExporterBuilder.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -40,7 +38,7 @@ internal OtlpExporterBuilder( name ??= Options.DefaultName; - RegisterOtlpExporterServices(services!, name!); + RegisterOtlpExporterServices(services!, name); this.name = name; this.Services = services!; @@ -176,9 +174,9 @@ private static void RegisterOtlpExporterServices(IServiceCollection services, st services!.AddOtlpExporterTracingServices(); // Note: UseOtlpExporterRegistration is added to the service collection - // to detect repeated calls to "UseOtlpExporter" and to throw if - // "AddOtlpExporter" extensions are called - services!.AddSingleton(); + // for each invocation to detect repeated calls to "UseOtlpExporter" and + // to throw if "AddOtlpExporter" extensions are called + services!.AddSingleton(UseOtlpExporterRegistration.Instance); services!.RegisterOptionsFactory((sp, configuration, name) => new OtlpExporterBuilderOptions( configuration, diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OtlpExporterBuilderOptions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OtlpExporterBuilderOptions.cs index e3ba3541ff..7d9786bb04 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OtlpExporterBuilderOptions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/OtlpExporterBuilderOptions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using Microsoft.Extensions.Configuration; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/UseOtlpExporterRegistration.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/UseOtlpExporterRegistration.cs index ad0ad9fbc9..e2de10663f 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/UseOtlpExporterRegistration.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Builder/UseOtlpExporterRegistration.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Exporter; // Note: This class is added to the IServiceCollection when UseOtlpExporter is @@ -10,4 +8,15 @@ namespace OpenTelemetry.Exporter; // calls to signal-specific AddOtlpExporter can throw. internal sealed class UseOtlpExporterRegistration { + public static readonly UseOtlpExporterRegistration Instance = new(); + + private UseOtlpExporterRegistration() + { + // Note: Some dependency injection containers (ex: Unity, Grace) will + // automatically create services if they have a public constructor even + // if the service was never registered into the IServiceCollection. The + // behavior of UseOtlpExporterRegistration requires that it should only + // exist if registered. This private constructor is intended to prevent + // automatic instantiation. + } } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index 0831012508..93577e9df6 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -1,7 +1,52 @@ # Changelog +This file contains individual changes for the +OpenTelemetry.Exporter.OpenTelemetryProtocol package. For highlights and +announcements covering all components see: [Release +Notes](../../RELEASENOTES.md). + ## Unreleased +* Added support for exporting instrumentation scope attributes from + `ActivitySource.Tags`. + ([#5897](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5897)) + +## 1.10.0-beta.1 + +Released 2024-Sep-30 + +* **Breaking change**: Non-primitive attribute (logs) and tag (traces) values + converted using `Convert.ToString` will now format using + `CultureInfo.InvariantCulture`. + ([#5700](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5700)) + +* Fixed an issue causing `NotSupportedException`s to be thrown on startup when + `AddOtlpExporter` registration extensions are called while using custom + dependency injection containers which automatically create services (Unity, + Grace, etc.). + ([#5808](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5808)) + + * Fixed `PlatformNotSupportedException`s being thrown during export when running + on mobile platforms which caused telemetry to be dropped silently. + ([#5821](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/5821)) + +* Updated `Microsoft.Extensions.Hosting.Abstractions` package + version to `9.0.0-rc.1.24431.7`. + ([#5853](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5853)) + +## 1.9.0 + +Released 2024-Jun-14 + +## 1.9.0-rc.1 + +Released 2024-Jun-07 + +* The experimental APIs previously covered by `OTEL1000` + (`LoggerProviderBuilder.AddOtlpExporter` extension) are now part of the public + API and supported in stable builds. + ([#5648](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5648)) + ## 1.9.0-alpha.1 Released 2024-May-20 diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/IOtlpExporterOptions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/IOtlpExporterOptions.cs index 4c394759a1..d6402bc85a 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/IOtlpExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/IOtlpExporterOptions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - #if NETFRAMEWORK using System.Net.Http; #endif @@ -16,7 +14,7 @@ namespace OpenTelemetry.Exporter; internal interface IOtlpExporterOptions { /// - /// Gets or sets the the OTLP transport protocol. + /// Gets or sets the OTLP transport protocol. /// OtlpExportProtocol Protocol { get; set; } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs index d3dacb3a1f..313a8e9f59 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs @@ -32,9 +32,11 @@ internal static void AddBatch( }; request.ResourceSpans.Add(resourceSpans); + var maxTags = sdkLimitOptions.AttributeCountLimit ?? int.MaxValue; + foreach (var activity in activityBatch) { - Span span = activity.ToOtlpSpan(sdkLimitOptions); + Span? span = activity.ToOtlpSpan(sdkLimitOptions); if (span == null) { OpenTelemetryProtocolExporterEventSource.Log.CouldNotTranslateActivity( @@ -44,15 +46,15 @@ internal static void AddBatch( } var activitySourceName = activity.Source.Name; - if (!spansByLibrary.TryGetValue(activitySourceName, out var spans)) + if (!spansByLibrary.TryGetValue(activitySourceName, out var scopeSpans)) { - spans = GetSpanListFromPool(activitySourceName, activity.Source.Version); + scopeSpans = GetSpanListFromPool(activity.Source, maxTags, sdkLimitOptions.AttributeValueLengthLimit); - spansByLibrary.Add(activitySourceName, spans); - resourceSpans.ScopeSpans.Add(spans); + spansByLibrary.Add(activitySourceName, scopeSpans); + resourceSpans.ScopeSpans.Add(scopeSpans); } - spans.Spans.Add(span); + scopeSpans.Spans.Add(span); } } @@ -65,38 +67,73 @@ internal static void Return(this ExportTraceServiceRequest request) return; } - foreach (var scope in resourceSpans.ScopeSpans) + foreach (var scopeSpan in resourceSpans.ScopeSpans) { - scope.Spans.Clear(); - SpanListPool.Add(scope); + scopeSpan.Spans.Clear(); + scopeSpan.Scope.Attributes.Clear(); + SpanListPool.Add(scopeSpan); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ScopeSpans GetSpanListFromPool(string name, string version) + internal static ScopeSpans GetSpanListFromPool(ActivitySource activitySource, int maxTags, int? attributeValueLengthLimit) { - if (!SpanListPool.TryTake(out var spans)) + if (!SpanListPool.TryTake(out var scopeSpans)) { - spans = new ScopeSpans + scopeSpans = new ScopeSpans { Scope = new InstrumentationScope { - Name = name, // Name is enforced to not be null, but it can be empty. - Version = version ?? string.Empty, // NRE throw by proto + Name = activitySource.Name, // Name is enforced to not be null, but it can be empty. + Version = activitySource.Version ?? string.Empty, // NRE throw by proto }, }; } else { - spans.Scope.Name = name; - spans.Scope.Version = version ?? string.Empty; + scopeSpans.Scope.Name = activitySource.Name; // Name is enforced to not be null, but it can be empty. + scopeSpans.Scope.Version = activitySource.Version ?? string.Empty; // NRE throw by proto + } + + if (activitySource.Tags != null) + { + var scopeAttributes = scopeSpans.Scope.Attributes; + + if (activitySource.Tags is IReadOnlyList> activitySourceTagsList) + { + for (int i = 0; i < activitySourceTagsList.Count; i++) + { + if (scopeAttributes.Count < maxTags) + { + OtlpTagWriter.Instance.TryWriteTag(ref scopeAttributes, activitySourceTagsList[i], attributeValueLengthLimit); + } + else + { + scopeSpans.Scope.DroppedAttributesCount++; + } + } + } + else + { + foreach (var tag in activitySource.Tags) + { + if (scopeAttributes.Count < maxTags) + { + OtlpTagWriter.Instance.TryWriteTag(ref scopeAttributes, tag, attributeValueLengthLimit); + } + else + { + scopeSpans.Scope.DroppedAttributesCount++; + } + } + } } - return spans; + return scopeSpans; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static Span ToOtlpSpan(this Activity activity, SdkLimitOptions sdkLimitOptions) + internal static Span? ToOtlpSpan(this Activity activity, SdkLimitOptions sdkLimitOptions) { if (activity.IdFormat != ActivityIdFormat.W3C) { @@ -145,7 +182,7 @@ internal static Span ToOtlpSpan(this Activity activity, SdkLimitOptions sdkLimit if (activity.Kind == ActivityKind.Client || activity.Kind == ActivityKind.Producer) { - PeerServiceResolver.Resolve(ref otlpTags, out string peerServiceName, out bool addAsTag); + PeerServiceResolver.Resolve(ref otlpTags, out string? peerServiceName, out bool addAsTag); if (peerServiceName != null && addAsTag) { @@ -180,7 +217,7 @@ internal static Span ToOtlpSpan(this Activity activity, SdkLimitOptions sdkLimit } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static OtlpTrace.Status ToOtlpStatus(this Activity activity, ref TagEnumerationState otlpTags) + private static OtlpTrace.Status? ToOtlpStatus(this Activity activity, ref TagEnumerationState otlpTags) { var statusCodeForTagValue = StatusHelper.GetStatusCodeForTagValue(otlpTags.StatusCode); if (activity.Status == ActivityStatusCode.Unset && statusCodeForTagValue == null) @@ -189,7 +226,7 @@ private static OtlpTrace.Status ToOtlpStatus(this Activity activity, ref TagEnum } OtlpTrace.Status.Types.StatusCode otlpActivityStatusCode = OtlpTrace.Status.Types.StatusCode.Unset; - string otlpStatusDescription = null; + string? otlpStatusDescription = null; if (activity.Status != ActivityStatusCode.Unset) { // The numerical values of the two enumerations match, a simple cast is enough. @@ -204,7 +241,7 @@ private static OtlpTrace.Status ToOtlpStatus(this Activity activity, ref TagEnum if (statusCodeForTagValue != StatusCode.Unset) { // The numerical values of the two enumerations match, a simple cast is enough. - otlpActivityStatusCode = (OtlpTrace.Status.Types.StatusCode)(int)statusCodeForTagValue; + otlpActivityStatusCode = (OtlpTrace.Status.Types.StatusCode)(int)statusCodeForTagValue!; if (statusCodeForTagValue == StatusCode.Error && !string.IsNullOrEmpty(otlpTags.StatusDescription)) { otlpStatusDescription = otlpTags.StatusDescription; @@ -304,17 +341,17 @@ private struct TagEnumerationState : PeerServiceResolver.IPeerServiceState public Span Span; - public string StatusCode; + public string? StatusCode; - public string StatusDescription; + public string? StatusDescription; - public string PeerService { get; set; } + public string? PeerService { get; set; } public int? PeerServicePriority { get; set; } - public string HostName { get; set; } + public string? HostName { get; set; } - public string IpAddress { get; set; } + public string? IpAddress { get; set; } public long Port { get; set; } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExperimentalOptions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExperimentalOptions.cs index 65d0bf57ee..25b345ac96 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExperimentalOptions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExperimentalOptions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using Microsoft.Extensions.Configuration; namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/BaseOtlpGrpcExportClient.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/BaseOtlpGrpcExportClient.cs index 493d267bc7..b585f2fc08 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/BaseOtlpGrpcExportClient.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/BaseOtlpGrpcExportClient.cs @@ -3,7 +3,7 @@ using Grpc.Core; using OpenTelemetry.Internal; -#if NETSTANDARD2_1 || NET6_0_OR_GREATER +#if NETSTANDARD2_1 || NET using Grpc.Net.Client; #endif @@ -25,10 +25,10 @@ protected BaseOtlpGrpcExportClient(OtlpExporterOptions options) this.TimeoutMilliseconds = options.TimeoutMilliseconds; } -#if NETSTANDARD2_1 || NET6_0_OR_GREATER - internal GrpcChannel Channel { get; set; } +#if NETSTANDARD2_1 || NET + internal GrpcChannel? Channel { get; set; } #else - internal Channel Channel { get; set; } + internal Channel? Channel { get; set; } #endif internal Uri Endpoint { get; } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/BaseOtlpHttpExportClient.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/BaseOtlpHttpExportClient.cs index 4fedc6b617..7e975fa133 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/BaseOtlpHttpExportClient.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/BaseOtlpHttpExportClient.cs @@ -13,6 +13,9 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClie internal abstract class BaseOtlpHttpExportClient : IExportClient { private static readonly ExportClientHttpResponse SuccessExportResponse = new ExportClientHttpResponse(success: true, deadlineUtc: default, response: null, exception: null); +#if NET + private readonly bool synchronousSendSupportedByCurrentPlatform; +#endif protected BaseOtlpHttpExportClient(OtlpExporterOptions options, HttpClient httpClient, string signalPath) { @@ -27,6 +30,14 @@ protected BaseOtlpHttpExportClient(OtlpExporterOptions options, HttpClient httpC this.Endpoint = new UriBuilder(exporterEndpoint).Uri; this.Headers = options.GetHeaders>((d, k, v) => d.Add(k, v)); this.HttpClient = httpClient; + +#if NET + // See: https://github.com/dotnet/runtime/blob/280f2a0c60ce0378b8db49adc0eecc463d00fe5d/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.AnyMobile.cs#L767 + this.synchronousSendSupportedByCurrentPlatform = !OperatingSystem.IsAndroid() + && !OperatingSystem.IsIOS() + && !OperatingSystem.IsTvOS() + && !OperatingSystem.IsBrowser(); +#endif } internal HttpClient HttpClient { get; } @@ -88,8 +99,10 @@ protected HttpRequestMessage CreateHttpRequest(TRequest exportRequest) protected HttpResponseMessage SendHttpRequest(HttpRequestMessage request, CancellationToken cancellationToken) { -#if NET6_0_OR_GREATER - return this.HttpClient.Send(request, cancellationToken); +#if NET + return this.synchronousSendSupportedByCurrentPlatform + ? this.HttpClient.Send(request, cancellationToken) + : this.HttpClient.SendAsync(request, cancellationToken).GetAwaiter().GetResult(); #else return this.HttpClient.SendAsync(request, cancellationToken).GetAwaiter().GetResult(); #endif diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExportClientGrpcResponse.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExportClientGrpcResponse.cs index f7c95107a7..4a96a7ad7c 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExportClientGrpcResponse.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExportClientGrpcResponse.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; internal sealed class ExportClientGrpcResponse : ExportClientResponse diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExportClientHttpResponse.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExportClientHttpResponse.cs index 9d274b0ffe..7e94996e7b 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExportClientHttpResponse.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExportClientHttpResponse.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Net; #if NETFRAMEWORK using System.Net.Http; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExportClientResponse.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExportClientResponse.cs index 49f8c0eb20..3a14b53725 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExportClientResponse.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExportClientResponse.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable +using System.Diagnostics.CodeAnalysis; namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; @@ -14,6 +14,7 @@ protected ExportClientResponse(bool success, DateTime deadlineUtc, Exception? ex this.DeadlineUtc = deadlineUtc; } + [MemberNotNullWhen(false, nameof(Exception))] public bool Success { get; } public Exception? Exception { get; } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/IExportClient.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/IExportClient.cs index 0c44da6ef3..26c7c5e33c 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/IExportClient.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/IExportClient.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; /// Export client interface. diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpGrpcLogExportClient.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpGrpcLogExportClient.cs index b45837cd82..e90f05ff5d 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpGrpcLogExportClient.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/OtlpGrpcLogExportClient.cs @@ -11,7 +11,7 @@ internal sealed class OtlpGrpcLogExportClient : BaseOtlpGrpcExportClient> meterTags) + internal static ScopeMetrics GetMetricListFromPool(string name, string version, IEnumerable>? meterTags) { if (!MetricListPool.TryTake(out var scopeMetrics)) { @@ -432,7 +432,7 @@ private static void AddAttributes(ReadOnlyTagCollection tags, RepeatedField> meterTags, RepeatedField attributes) + private static void AddScopeAttributes(IEnumerable> meterTags, RepeatedField attributes) { foreach (var tag in meterTags) { diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpExporterOptionsConfigurationType.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpExporterOptionsConfigurationType.cs index d3cedd6915..0fb01edc25 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpExporterOptionsConfigurationType.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpExporterOptionsConfigurationType.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Exporter; [Flags] diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpLogRecordTransformer.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpLogRecordTransformer.cs index e3d70c4018..e6942286e2 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpLogRecordTransformer.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpLogRecordTransformer.cs @@ -102,9 +102,9 @@ internal OtlpLogs.ScopeLogs GetLogListFromPool(string name) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal OtlpLogs.LogRecord ToOtlpLog(LogRecord logRecord) + internal OtlpLogs.LogRecord? ToOtlpLog(LogRecord logRecord) { - OtlpLogs.LogRecord otlpLogRecord = null; + OtlpLogs.LogRecord? otlpLogRecord = null; try { @@ -238,7 +238,7 @@ void ProcessScope(LogRecordScope scope, OtlpLogs.LogRecord otlpLog) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void AddAttribute(OtlpLogs.LogRecord logRecord, KeyValuePair attribute, int maxAttributeCount, int? maxValueLength) + private static void AddAttribute(OtlpLogs.LogRecord logRecord, KeyValuePair attribute, int maxAttributeCount, int? maxValueLength) { var logRecordAttributes = logRecord.Attributes; @@ -253,9 +253,9 @@ private static void AddAttribute(OtlpLogs.LogRecord logRecord, KeyValuePair(key, value); + var attributeItem = new KeyValuePair(key, value); AddAttribute(logRecord, attributeItem, maxAttributeCount, maxValueLength); } @@ -263,7 +263,7 @@ private static void AddStringAttribute(OtlpLogs.LogRecord logRecord, string key, [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void AddIntAttribute(OtlpLogs.LogRecord logRecord, string key, int value, int maxAttributeCount) { - var attributeItem = new KeyValuePair(key, value); + var attributeItem = new KeyValuePair(key, value); AddAttribute(logRecord, attributeItem, maxAttributeCount, maxValueLength: null); } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpServiceCollectionExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpServiceCollectionExtensions.cs index 282c84ed27..79c7ddb476 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpServiceCollectionExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpServiceCollectionExtensions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpTagWriter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpTagWriter.cs index 64315fd8e0..c713d4b646 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpTagWriter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpTagWriter.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using Google.Protobuf.Collections; using OpenTelemetry.Internal; using OtlpCommon = OpenTelemetry.Proto.Common.V1; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ResourceExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ResourceExtensions.cs index 656fa4eea6..f114f07297 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ResourceExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ResourceExtensions.cs @@ -17,7 +17,7 @@ public static OtlpResource.Resource ToOtlpResource(this Resource resource) foreach (KeyValuePair attribute in resource.Attributes) { - OtlpTagWriter.Instance.TryWriteTag(ref processResourceAttributes, attribute); + OtlpTagWriter.Instance.TryWriteTag(ref processResourceAttributes, attribute.Key, attribute.Value); } if (!processResource.Attributes.Any(kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName)) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpLogFieldNumberConstants.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpLogFieldNumberConstants.cs new file mode 100644 index 0000000000..3361a8551c --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpLogFieldNumberConstants.cs @@ -0,0 +1,88 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; + +internal static class ProtobufOtlpLogFieldNumberConstants +{ + // Resource Logs +#pragma warning disable SA1310 // Field names should not contain underscore + internal const int ResourceLogs_Resource = 1; + internal const int ResourceLogs_Scope_Logs = 2; + internal const int ResourceLogs_Schema_Url = 3; + + // Resource + internal const int Resource_Attributes = 1; + + // ScopeLogs + internal const int ScopeLogs_Scope = 1; + internal const int ScopeLogs_Log_Records = 2; + internal const int ScopeLogs_Schema_Url = 3; + + // LogRecord + internal const int LogRecord_Time_Unix_Nano = 1; + internal const int LogRecord_Observed_Time_Unix_Nano = 11; + internal const int LogRecord_Severity_Number = 2; + internal const int LogRecord_Severity_Text = 3; + internal const int LogRecord_Body = 5; + internal const int LogRecord_Attributes = 6; + internal const int LogRecord_Dropped_Attributes_Count = 7; + internal const int LogRecord_Flags = 8; + internal const int LogRecord_Trace_Id = 9; + internal const int LogRecord_Span_Id = 10; + + // SeverityNumber + internal const int Severity_Number_Unspecified = 0; + internal const int Severity_Number_Trace = 1; + internal const int Severity_Number_Trace2 = 2; + internal const int Severity_Number_Trace3 = 3; + internal const int Severity_Number_Trace4 = 4; + internal const int Severity_Number_Debug = 5; + internal const int Severity_Number_Debug2 = 6; + internal const int Severity_Number_Debug3 = 7; + internal const int Severity_Number_Debug4 = 8; + internal const int Severity_Number_Info = 9; + internal const int Severity_Number_Info2 = 10; + internal const int Severity_Number_Info3 = 11; + internal const int Severity_Number_Info4 = 12; + internal const int Severity_Number_Warn = 13; + internal const int Severity_Number_Warn2 = 14; + internal const int Severity_Number_Warn3 = 15; + internal const int Severity_Number_Warn4 = 16; + internal const int Severity_Number_Error = 17; + internal const int Severity_Number_Error2 = 18; + internal const int Severity_Number_Error3 = 19; + internal const int Severity_Number_Error4 = 20; + internal const int Severity_Number_Fatal = 21; + internal const int Severity_Number_Fatal2 = 22; + internal const int Severity_Number_Fatal3 = 23; + internal const int Severity_Number_Fatal4 = 24; + + // LogRecordFlags + + internal const int LogRecord_Flags_Do_Not_Use = 0; + internal const int LogRecord_Flags_Trace_Flags_Mask = 0x000000FF; + + // InstrumentationScope + internal const int InstrumentationScope_Name = 1; + internal const int InstrumentationScope_Version = 2; + internal const int InstrumentationScope_Attributes = 3; + internal const int InstrumentationScope_Dropped_Attributes_Count = 4; + + // KeyValue + internal const int KeyValue_Key = 1; + internal const int KeyValue_Value = 2; + + // AnyValue + internal const int AnyValue_String_Value = 1; + internal const int AnyValue_Bool_Value = 2; + internal const int AnyValue_Int_Value = 3; + internal const int AnyValue_Double_Value = 4; + internal const int AnyValue_Array_Value = 5; + internal const int AnyValue_Kvlist_Value = 6; + internal const int AnyValue_Bytes_Value = 7; + + internal const int ArrayValue_Value = 1; +#pragma warning restore SA1310 // Field names should not contain underscore +} + diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpLogSerializer.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpLogSerializer.cs new file mode 100644 index 0000000000..4c2f9c95ac --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpLogSerializer.cs @@ -0,0 +1,290 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.Internal; +using OpenTelemetry.Logs; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; + +internal static class ProtobufOtlpLogSerializer +{ + private const int ReserveSizeForLength = 4; + private const int TraceIdSize = 16; + private const int SpanIdSize = 8; + + private static readonly Stack> LogsListPool = []; + private static readonly Dictionary> ScopeLogsList = []; + + [ThreadStatic] + private static SerializationState? threadSerializationState; + + internal static int WriteLogsData(byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, ExperimentalOptions experimentalOptions, Resources.Resource? resource, in Batch logRecordBatch) + { + foreach (var logRecord in logRecordBatch) + { + var scopeName = logRecord.Logger.Name; + if (!ScopeLogsList.TryGetValue(scopeName, out var logRecords)) + { + logRecords = LogsListPool.Count > 0 ? LogsListPool.Pop() : []; + ScopeLogsList[scopeName] = logRecords; + } + + logRecords.Add(logRecord); + } + + writePosition = WriteResourceLogs(buffer, writePosition, sdkLimitOptions, experimentalOptions, resource, ScopeLogsList); + ReturnLogRecordListToPool(); + + return writePosition; + } + + internal static void ReturnLogRecordListToPool() + { + if (ScopeLogsList.Count != 0) + { + foreach (var entry in ScopeLogsList) + { + entry.Value.Clear(); + LogsListPool.Push(entry.Value); + } + + ScopeLogsList.Clear(); + } + } + + internal static int WriteResourceLogs(byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, ExperimentalOptions experimentalOptions, Resources.Resource? resource, Dictionary> scopeLogs) + { + writePosition = ProtobufOtlpResourceSerializer.WriteResource(buffer, writePosition, resource); + writePosition = WriteScopeLogs(buffer, writePosition, sdkLimitOptions, experimentalOptions, scopeLogs); + return writePosition; + } + + internal static int WriteScopeLogs(byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, ExperimentalOptions experimentalOptions, Dictionary> scopeLogs) + { + if (scopeLogs != null) + { + foreach (KeyValuePair> entry in scopeLogs) + { + writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, ProtobufOtlpLogFieldNumberConstants.ResourceLogs_Scope_Logs, ProtobufWireType.LEN); + int resourceLogsScopeLogsLengthPosition = writePosition; + writePosition += ReserveSizeForLength; + + writePosition = WriteScopeLog(buffer, writePosition, sdkLimitOptions, experimentalOptions, entry.Value[0].Logger.Name, entry.Value); + ProtobufSerializer.WriteReservedLength(buffer, resourceLogsScopeLogsLengthPosition, writePosition - (resourceLogsScopeLogsLengthPosition + ReserveSizeForLength)); + } + } + + return writePosition; + } + + internal static int WriteScopeLog(byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, ExperimentalOptions experimentalOptions, string loggerName, List logRecords) + { + var value = loggerName.AsSpan(); + var numberOfUtf8CharsInString = ProtobufSerializer.GetNumberOfUtf8CharsInString(value); + var serializedLengthSize = ProtobufSerializer.ComputeVarInt64Size((ulong)numberOfUtf8CharsInString); + + // numberOfUtf8CharsInString + tagSize + length field size. + writePosition = ProtobufSerializer.WriteTagAndLength(buffer, writePosition, numberOfUtf8CharsInString + 1 + serializedLengthSize, ProtobufOtlpLogFieldNumberConstants.ScopeLogs_Scope, ProtobufWireType.LEN); + writePosition = ProtobufSerializer.WriteStringWithTag(buffer, writePosition, ProtobufOtlpLogFieldNumberConstants.InstrumentationScope_Name, numberOfUtf8CharsInString, value); + + for (int i = 0; i < logRecords.Count; i++) + { + writePosition = WriteLogRecord(buffer, writePosition, sdkLimitOptions, experimentalOptions, logRecords[i]); + } + + return writePosition; + } + + internal static int WriteLogRecord(byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, ExperimentalOptions experimentalOptions, LogRecord logRecord) + { + var state = threadSerializationState ??= new(); + + state.AttributeValueLengthLimit = sdkLimitOptions.LogRecordAttributeValueLengthLimit; + state.AttributeCountLimit = sdkLimitOptions.LogRecordAttributeCountLimit ?? int.MaxValue; + state.TagWriterState = new ProtobufOtlpTagWriter.OtlpTagWriterState + { + Buffer = buffer, + WritePosition = writePosition, + TagCount = 0, + DroppedTagCount = 0, + }; + + ref var otlpTagWriterState = ref state.TagWriterState; + + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpLogFieldNumberConstants.ScopeLogs_Log_Records, ProtobufWireType.LEN); + int logRecordLengthPosition = otlpTagWriterState.WritePosition; + otlpTagWriterState.WritePosition += ReserveSizeForLength; + + var timestamp = (ulong)logRecord.Timestamp.ToUnixTimeNanoseconds(); + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteFixed64WithTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpLogFieldNumberConstants.LogRecord_Time_Unix_Nano, timestamp); + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteFixed64WithTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpLogFieldNumberConstants.LogRecord_Observed_Time_Unix_Nano, timestamp); + + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteEnumWithTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpLogFieldNumberConstants.LogRecord_Severity_Number, logRecord.Severity.HasValue ? (int)logRecord.Severity : 0); + + if (!string.IsNullOrWhiteSpace(logRecord.SeverityText)) + { + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteStringWithTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpLogFieldNumberConstants.LogRecord_Severity_Text, logRecord.SeverityText!); + } + else if (logRecord.Severity.HasValue) + { + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteStringWithTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpLogFieldNumberConstants.LogRecord_Severity_Text, logRecord.Severity.Value.ToShortName()); + } + + if (experimentalOptions.EmitLogEventAttributes) + { + if (logRecord.EventId.Id != default) + { + AddLogAttribute(state, ExperimentalOptions.LogRecordEventIdAttribute, logRecord.EventId.Id); + } + + if (!string.IsNullOrEmpty(logRecord.EventId.Name)) + { + AddLogAttribute(state, ExperimentalOptions.LogRecordEventNameAttribute, logRecord.EventId.Name!); + } + } + + if (logRecord.Exception != null) + { + AddLogAttribute(state, SemanticConventions.AttributeExceptionType, logRecord.Exception.GetType().Name); + AddLogAttribute(state, SemanticConventions.AttributeExceptionMessage, logRecord.Exception.Message); + AddLogAttribute(state, SemanticConventions.AttributeExceptionStacktrace, logRecord.Exception.ToInvariantString()); + } + + bool bodyPopulatedFromFormattedMessage = false; + bool isLogRecordBodySet = false; + + if (logRecord.FormattedMessage != null) + { + otlpTagWriterState.WritePosition = WriteLogRecordBody(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, logRecord.FormattedMessage.AsSpan()); + bodyPopulatedFromFormattedMessage = true; + isLogRecordBodySet = true; + } + + if (logRecord.Attributes != null) + { + foreach (var attribute in logRecord.Attributes) + { + // Special casing {OriginalFormat} + // See https://github.com/open-telemetry/opentelemetry-dotnet/pull/3182 + // for explanation. + if (attribute.Key.Equals("{OriginalFormat}") && !bodyPopulatedFromFormattedMessage) + { + otlpTagWriterState.WritePosition = WriteLogRecordBody(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, (attribute.Value as string).AsSpan()); + isLogRecordBodySet = true; + } + else + { + AddLogAttribute(state, attribute); + } + } + + // Supports setting Body directly on LogRecord for the Logs Bridge API. + if (!isLogRecordBodySet && logRecord.Body != null) + { + // If {OriginalFormat} is not present in the attributes, + // use logRecord.Body if it is set. + otlpTagWriterState.WritePosition = WriteLogRecordBody(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, logRecord.Body.AsSpan()); + } + } + + if (logRecord.TraceId != default && logRecord.SpanId != default) + { + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteTagAndLength(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, TraceIdSize, ProtobufOtlpLogFieldNumberConstants.LogRecord_Trace_Id, ProtobufWireType.LEN); + otlpTagWriterState.WritePosition = ProtobufOtlpTraceSerializer.WriteTraceId(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, logRecord.TraceId); + + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteTagAndLength(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, SpanIdSize, ProtobufOtlpLogFieldNumberConstants.LogRecord_Span_Id, ProtobufWireType.LEN); + otlpTagWriterState.WritePosition = ProtobufOtlpTraceSerializer.WriteSpanId(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, logRecord.SpanId); + + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteFixed32WithTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpLogFieldNumberConstants.LogRecord_Flags, (uint)logRecord.TraceFlags); + } + + logRecord.ForEachScope(ProcessScope, state); + + if (otlpTagWriterState.DroppedTagCount > 0) + { + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteTag(buffer, otlpTagWriterState.WritePosition, ProtobufOtlpLogFieldNumberConstants.LogRecord_Dropped_Attributes_Count, ProtobufWireType.VARINT); + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteVarInt32(buffer, otlpTagWriterState.WritePosition, (uint)otlpTagWriterState.DroppedTagCount); + } + + ProtobufSerializer.WriteReservedLength(otlpTagWriterState.Buffer, logRecordLengthPosition, otlpTagWriterState.WritePosition - (logRecordLengthPosition + ReserveSizeForLength)); + + return otlpTagWriterState.WritePosition; + + static void ProcessScope(LogRecordScope scope, SerializationState state) + { + foreach (var scopeItem in scope) + { + if (scopeItem.Key.Equals("{OriginalFormat}") || string.IsNullOrEmpty(scopeItem.Key)) + { + // Ignore if the scope key is empty. + // Ignore if the scope key is {OriginalFormat} + // Attributes should not contain duplicates, + // and it is expensive to de-dup, so this + // exporter is going to pass the scope items as is. + // {OriginalFormat} is going to be the key + // if one uses formatted string for scopes + // and if there are nested scopes, this is + // guaranteed to create duplicate keys. + // Similar for empty keys, which is what the + // key is going to be if user simply + // passes a string as scope. + // To summarize this exporter only allows + // IReadOnlyList> + // or IEnumerable>. + // and expect users to provide unique keys. + // Note: It is possible that we allow users + // to override this exporter feature. So not blocking + // empty/{OriginalFormat} in the SDK itself. + } + else + { + AddLogAttribute(state, scopeItem); + } + } + } + } + + private static int WriteLogRecordBody(byte[] buffer, int writePosition, ReadOnlySpan value) + { + var numberOfUtf8CharsInString = ProtobufSerializer.GetNumberOfUtf8CharsInString(value); + var serializedLengthSize = ProtobufSerializer.ComputeVarInt64Size((ulong)numberOfUtf8CharsInString); + + // length = numberOfUtf8CharsInString + tagSize + length field size. + writePosition = ProtobufSerializer.WriteTagAndLength(buffer, writePosition, numberOfUtf8CharsInString + 1 + serializedLengthSize, ProtobufOtlpLogFieldNumberConstants.LogRecord_Body, ProtobufWireType.LEN); + writePosition = ProtobufSerializer.WriteStringWithTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.AnyValue_String_Value, numberOfUtf8CharsInString, value); + return writePosition; + } + + private static void AddLogAttribute(SerializationState state, KeyValuePair attribute) + { + AddLogAttribute(state, attribute.Key, attribute.Value); + } + + private static void AddLogAttribute(SerializationState state, string key, object? value) + { + if (state.TagWriterState.TagCount == state.AttributeCountLimit) + { + state.TagWriterState.DroppedTagCount++; + } + else + { + state.TagWriterState.WritePosition = ProtobufSerializer.WriteTag(state.TagWriterState.Buffer, state.TagWriterState.WritePosition, ProtobufOtlpLogFieldNumberConstants.LogRecord_Attributes, ProtobufWireType.LEN); + int logAttributesLengthPosition = state.TagWriterState.WritePosition; + state.TagWriterState.WritePosition += ReserveSizeForLength; + + ProtobufOtlpTagWriter.Instance.TryWriteTag(ref state.TagWriterState, key, value, state.AttributeValueLengthLimit); + + var logAttributesLength = state.TagWriterState.WritePosition - (logAttributesLengthPosition + ReserveSizeForLength); + ProtobufSerializer.WriteReservedLength(state.TagWriterState.Buffer, logAttributesLengthPosition, logAttributesLength); + state.TagWriterState.TagCount++; + } + } + + private sealed class SerializationState + { + public int? AttributeValueLengthLimit; + public int AttributeCountLimit; + public ProtobufOtlpTagWriter.OtlpTagWriterState TagWriterState; + } +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpResourceSerializer.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpResourceSerializer.cs new file mode 100644 index 0000000000..0d315778c9 --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpResourceSerializer.cs @@ -0,0 +1,80 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.Resources; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; + +internal static class ProtobufOtlpResourceSerializer +{ + private const int ReserveSizeForLength = 4; + + private static readonly string DefaultServiceName = ResourceBuilder.CreateDefault().Build().Attributes.FirstOrDefault( + kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName).Value as string ?? "unknown_service"; + + internal static int WriteResource(byte[] buffer, int writePosition, Resource? resource) + { + ProtobufOtlpTagWriter.OtlpTagWriterState otlpTagWriterState = new ProtobufOtlpTagWriter.OtlpTagWriterState + { + Buffer = buffer, + WritePosition = writePosition, + }; + + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpTraceFieldNumberConstants.ResourceSpans_Resource, ProtobufWireType.LEN); + int resourceLengthPosition = otlpTagWriterState.WritePosition; + otlpTagWriterState.WritePosition += ReserveSizeForLength; + + bool isServiceNamePresent = false; + if (resource != null && resource != Resource.Empty) + { + if (resource.Attributes is IReadOnlyList> resourceAttributesList) + { + for (int i = 0; i < resourceAttributesList.Count; i++) + { + var attribute = resourceAttributesList[i]; + if (attribute.Key == ResourceSemanticConventions.AttributeServiceName) + { + isServiceNamePresent = true; + } + + otlpTagWriterState = ProcessResourceAttribute(ref otlpTagWriterState, attribute); + } + } + else + { + foreach (var attribute in resource.Attributes) + { + if (attribute.Key == ResourceSemanticConventions.AttributeServiceName) + { + isServiceNamePresent = true; + } + + otlpTagWriterState = ProcessResourceAttribute(ref otlpTagWriterState, attribute); + } + } + } + + if (!isServiceNamePresent) + { + otlpTagWriterState = ProcessResourceAttribute(ref otlpTagWriterState, new KeyValuePair(ResourceSemanticConventions.AttributeServiceName, DefaultServiceName)); + } + + var resourceLength = otlpTagWriterState.WritePosition - (resourceLengthPosition + ReserveSizeForLength); + ProtobufSerializer.WriteReservedLength(otlpTagWriterState.Buffer, resourceLengthPosition, resourceLength); + + return otlpTagWriterState.WritePosition; + } + + private static ProtobufOtlpTagWriter.OtlpTagWriterState ProcessResourceAttribute(ref ProtobufOtlpTagWriter.OtlpTagWriterState otlpTagWriterState, KeyValuePair attribute) + { + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpTraceFieldNumberConstants.Resource_Attributes, ProtobufWireType.LEN); + int resourceAttributesLengthPosition = otlpTagWriterState.WritePosition; + otlpTagWriterState.WritePosition += ReserveSizeForLength; + + ProtobufOtlpTagWriter.Instance.TryWriteTag(ref otlpTagWriterState, attribute.Key, attribute.Value); + + var resourceAttributesLength = otlpTagWriterState.WritePosition - (resourceAttributesLengthPosition + ReserveSizeForLength); + ProtobufSerializer.WriteReservedLength(otlpTagWriterState.Buffer, resourceAttributesLengthPosition, resourceAttributesLength); + return otlpTagWriterState; + } +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTagWriter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTagWriter.cs new file mode 100644 index 0000000000..79b3213a1c --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTagWriter.cs @@ -0,0 +1,153 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; + +internal sealed class ProtobufOtlpTagWriter : TagWriter +{ + private ProtobufOtlpTagWriter() + : base(new OtlpArrayTagWriter()) + { + } + + public static ProtobufOtlpTagWriter Instance { get; } = new(); + + protected override void WriteIntegralTag(ref OtlpTagWriterState state, string key, long value) + { + // Write KeyValue tag + state.WritePosition = ProtobufSerializer.WriteStringWithTag(state.Buffer, state.WritePosition, ProtobufOtlpTraceFieldNumberConstants.KeyValue_Key, key); + + // Write KeyValue.Value tag, length and value. + var size = ProtobufSerializer.ComputeVarInt64Size((ulong)value) + 1; // ComputeVarint64Size(ulong) + TagSize + state.WritePosition = ProtobufSerializer.WriteTagAndLength(state.Buffer, state.WritePosition, size, ProtobufOtlpTraceFieldNumberConstants.KeyValue_Value, ProtobufWireType.LEN); + state.WritePosition = ProtobufSerializer.WriteInt64WithTag(state.Buffer, state.WritePosition, ProtobufOtlpTraceFieldNumberConstants.AnyValue_Int_Value, (ulong)value); + } + + protected override void WriteFloatingPointTag(ref OtlpTagWriterState state, string key, double value) + { + // Write KeyValue tag + state.WritePosition = ProtobufSerializer.WriteStringWithTag(state.Buffer, state.WritePosition, ProtobufOtlpTraceFieldNumberConstants.KeyValue_Key, key); + + // Write KeyValue.Value tag, length and value. + state.WritePosition = ProtobufSerializer.WriteTagAndLength(state.Buffer, state.WritePosition, 9, ProtobufOtlpTraceFieldNumberConstants.KeyValue_Value, ProtobufWireType.LEN); // 8 + TagSize + state.WritePosition = ProtobufSerializer.WriteDoubleWithTag(state.Buffer, state.WritePosition, ProtobufOtlpTraceFieldNumberConstants.AnyValue_Double_Value, value); + } + + protected override void WriteBooleanTag(ref OtlpTagWriterState state, string key, bool value) + { + // Write KeyValue tag + state.WritePosition = ProtobufSerializer.WriteStringWithTag(state.Buffer, state.WritePosition, ProtobufOtlpTraceFieldNumberConstants.KeyValue_Key, key); + + // Write KeyValue.Value tag, length and value. + state.WritePosition = ProtobufSerializer.WriteTagAndLength(state.Buffer, state.WritePosition, 2, ProtobufOtlpTraceFieldNumberConstants.KeyValue_Value, ProtobufWireType.LEN); // 1 + TagSize + state.WritePosition = ProtobufSerializer.WriteBoolWithTag(state.Buffer, state.WritePosition, ProtobufOtlpTraceFieldNumberConstants.AnyValue_Bool_Value, value); + } + + protected override void WriteStringTag(ref OtlpTagWriterState state, string key, ReadOnlySpan value) + { + // Write KeyValue tag + state.WritePosition = ProtobufSerializer.WriteStringWithTag(state.Buffer, state.WritePosition, ProtobufOtlpTraceFieldNumberConstants.KeyValue_Key, key); + + // Write KeyValue.Value tag, length and value. + var numberOfUtf8CharsInString = ProtobufSerializer.GetNumberOfUtf8CharsInString(value); + var serializedLengthSize = ProtobufSerializer.ComputeVarInt64Size((ulong)numberOfUtf8CharsInString); + + // length = numberOfUtf8CharsInString + tagSize + length field size. + state.WritePosition = ProtobufSerializer.WriteTagAndLength(state.Buffer, state.WritePosition, numberOfUtf8CharsInString + 1 + serializedLengthSize, ProtobufOtlpTraceFieldNumberConstants.KeyValue_Value, ProtobufWireType.LEN); + state.WritePosition = ProtobufSerializer.WriteStringWithTag(state.Buffer, state.WritePosition, ProtobufOtlpTraceFieldNumberConstants.AnyValue_String_Value, numberOfUtf8CharsInString, value); + } + + protected override void WriteArrayTag(ref OtlpTagWriterState state, string key, ref OtlpTagWriterArrayState value) + { + // TODO: Expand OtlpTagWriterArrayState.Buffer on IndexOutOfRangeException. + // Write KeyValue tag + state.WritePosition = ProtobufSerializer.WriteStringWithTag(state.Buffer, state.WritePosition, ProtobufOtlpTraceFieldNumberConstants.KeyValue_Key, key); + + // Write KeyValue.Value tag and length + var serializedLengthSize = ProtobufSerializer.ComputeVarInt64Size((ulong)value.WritePosition); + state.WritePosition = ProtobufSerializer.WriteTagAndLength(state.Buffer, state.WritePosition, value.WritePosition + 1 + serializedLengthSize, ProtobufOtlpTraceFieldNumberConstants.KeyValue_Value, ProtobufWireType.LEN); // Array content length + Array tag size + length field size + + // Write Array tag and length + state.WritePosition = ProtobufSerializer.WriteTagAndLength(state.Buffer, state.WritePosition, value.WritePosition, ProtobufOtlpTraceFieldNumberConstants.AnyValue_Array_Value, ProtobufWireType.LEN); + Buffer.BlockCopy(value.Buffer, 0, state.Buffer, state.WritePosition, value.WritePosition); + state.WritePosition += value.WritePosition; + } + + protected override void OnUnsupportedTagDropped( + string tagKey, + string tagValueTypeFullName) => OpenTelemetryProtocolExporterEventSource.Log.UnsupportedAttributeType( + tagValueTypeFullName, + tagKey); + + internal struct OtlpTagWriterState + { + public byte[] Buffer; + public int DroppedTagCount; + public int TagCount; + public int WritePosition; + } + + internal struct OtlpTagWriterArrayState + { + public byte[] Buffer; + public int WritePosition; + } + + private sealed class OtlpArrayTagWriter : ArrayTagWriter + { + [ThreadStatic] + private static byte[]? threadBuffer; + + public override OtlpTagWriterArrayState BeginWriteArray() + { + threadBuffer ??= new byte[2048]; + + return new OtlpTagWriterArrayState + { + Buffer = threadBuffer, + WritePosition = 0, + }; + } + + public override void WriteNullValue(ref OtlpTagWriterArrayState state) + { + state.WritePosition = ProtobufSerializer.WriteTagAndLength(state.Buffer, state.WritePosition, 0, ProtobufOtlpTraceFieldNumberConstants.ArrayValue_Value, ProtobufWireType.LEN); + } + + public override void WriteIntegralValue(ref OtlpTagWriterArrayState state, long value) + { + var size = ProtobufSerializer.ComputeVarInt64Size((ulong)value) + 1; + state.WritePosition = ProtobufSerializer.WriteTagAndLength(state.Buffer, state.WritePosition, size, ProtobufOtlpTraceFieldNumberConstants.ArrayValue_Value, ProtobufWireType.LEN); + state.WritePosition = ProtobufSerializer.WriteInt64WithTag(state.Buffer, state.WritePosition, ProtobufOtlpTraceFieldNumberConstants.AnyValue_Int_Value, (ulong)value); + } + + public override void WriteFloatingPointValue(ref OtlpTagWriterArrayState state, double value) + { + state.WritePosition = ProtobufSerializer.WriteTagAndLength(state.Buffer, state.WritePosition, 9, ProtobufOtlpTraceFieldNumberConstants.ArrayValue_Value, ProtobufWireType.LEN); + state.WritePosition = ProtobufSerializer.WriteDoubleWithTag(state.Buffer, state.WritePosition, ProtobufOtlpTraceFieldNumberConstants.AnyValue_Double_Value, value); + } + + public override void WriteBooleanValue(ref OtlpTagWriterArrayState state, bool value) + { + state.WritePosition = ProtobufSerializer.WriteTagAndLength(state.Buffer, state.WritePosition, 2, ProtobufOtlpTraceFieldNumberConstants.ArrayValue_Value, ProtobufWireType.LEN); + state.WritePosition = ProtobufSerializer.WriteBoolWithTag(state.Buffer, state.WritePosition, ProtobufOtlpTraceFieldNumberConstants.AnyValue_Bool_Value, value); + } + + public override void WriteStringValue(ref OtlpTagWriterArrayState state, ReadOnlySpan value) + { + // Write KeyValue.Value tag, length and value. + var numberOfUtf8CharsInString = ProtobufSerializer.GetNumberOfUtf8CharsInString(value); + var serializedLengthSize = ProtobufSerializer.ComputeVarInt64Size((ulong)numberOfUtf8CharsInString); + + // length = numberOfUtf8CharsInString + tagSize + length field size. + state.WritePosition = ProtobufSerializer.WriteTagAndLength(state.Buffer, state.WritePosition, numberOfUtf8CharsInString + 1 + serializedLengthSize, ProtobufOtlpTraceFieldNumberConstants.ArrayValue_Value, ProtobufWireType.LEN); + state.WritePosition = ProtobufSerializer.WriteStringWithTag(state.Buffer, state.WritePosition, ProtobufOtlpTraceFieldNumberConstants.AnyValue_String_Value, numberOfUtf8CharsInString, value); + } + + public override void EndWriteArray(ref OtlpTagWriterArrayState state) + { + } + } +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTraceFieldNumberConstants.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTraceFieldNumberConstants.cs new file mode 100644 index 0000000000..c7d0198a99 --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTraceFieldNumberConstants.cs @@ -0,0 +1,92 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; + +internal static class ProtobufOtlpTraceFieldNumberConstants +{ + // Resource spans +#pragma warning disable SA1310 // Field names should not contain underscore + internal const int ResourceSpans_Resource = 1; + internal const int ResourceSpans_Scope_Spans = 2; + internal const int ResourceSpans_Schema_Url = 3; + + // Resource + internal const int Resource_Attributes = 1; + + // ScopeSpans + internal const int ScopeSpans_Scope = 1; + internal const int ScopeSpans_Span = 2; + internal const int ScopeSpans_Schema_Url = 3; + + // Span + internal const int Span_Trace_Id = 1; + internal const int Span_Span_Id = 2; + internal const int Span_Trace_State = 3; + internal const int Span_Parent_Span_Id = 4; + internal const int Span_Name = 5; + internal const int Span_Kind = 6; + internal const int Span_Start_Time_Unix_Nano = 7; + internal const int Span_End_Time_Unix_Nano = 8; + internal const int Span_Attributes = 9; + internal const int Span_Dropped_Attributes_Count = 10; + internal const int Span_Events = 11; + internal const int Span_Dropped_Events_Count = 12; + internal const int Span_Links = 13; + internal const int Span_Dropped_Links_Count = 14; + internal const int Span_Status = 15; + internal const int Span_Flags = 16; + + // SpanKind + internal const int SpanKind_Internal = 2; + internal const int SpanKind_Server = 3; + internal const int SpanKind_Client = 4; + internal const int SpanKind_Producer = 5; + internal const int SpanKind_Consumer = 6; + + // Events + internal const int Event_Time_Unix_Nano = 1; + internal const int Event_Name = 2; + internal const int Event_Attributes = 3; + internal const int Event_Dropped_Attributes_Count = 4; + + // Links + internal const int Link_Trace_Id = 1; + internal const int Link_Span_Id = 2; + internal const int Link_Trace_State = 3; + internal const int Link_Attributes = 4; + internal const int Link_Dropped_Attributes_Count = 5; + internal const int Link_Flags = 6; + + // Status + internal const int Status_Message = 2; + internal const int Status_Code = 3; + + // StatusCode + internal const int StatusCode_Unset = 0; + internal const int StatusCode_Ok = 1; + internal const int StatusCode_Error = 2; + + // InstrumentationScope + internal const int InstrumentationScope_Name = 1; + internal const int InstrumentationScope_Version = 2; + internal const int InstrumentationScope_Attributes = 3; + internal const int InstrumentationScope_Dropped_Attributes_Count = 4; + + // KeyValue + internal const int KeyValue_Key = 1; + internal const int KeyValue_Value = 2; + + // AnyValue + internal const int AnyValue_String_Value = 1; + internal const int AnyValue_Bool_Value = 2; + internal const int AnyValue_Int_Value = 3; + internal const int AnyValue_Double_Value = 4; + internal const int AnyValue_Array_Value = 5; + internal const int AnyValue_Kvlist_Value = 6; + internal const int AnyValue_Bytes_Value = 7; + + internal const int ArrayValue_Value = 1; +#pragma warning restore SA1310 // Field names should not contain underscore +} + diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTraceSerializer.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTraceSerializer.cs new file mode 100644 index 0000000000..1301ba22a1 --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTraceSerializer.cs @@ -0,0 +1,484 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Diagnostics; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; + +internal static class ProtobufOtlpTraceSerializer +{ + private const int ReserveSizeForLength = 4; + private const string UnsetStatusCodeTagValue = "UNSET"; + private const string OkStatusCodeTagValue = "OK"; + private const string ErrorStatusCodeTagValue = "ERROR"; + private const int TraceIdSize = 16; + private const int SpanIdSize = 8; + + private static readonly Stack> ActivityListPool = []; + private static readonly Dictionary> ScopeTracesList = []; + + internal static int WriteTraceData(byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, Resources.Resource? resource, in Batch batch) + { + foreach (var activity in batch) + { + var sourceName = activity.Source.Name; + if (!ScopeTracesList.TryGetValue(sourceName, out var activities)) + { + activities = ActivityListPool.Count > 0 ? ActivityListPool.Pop() : new List(); + ScopeTracesList[sourceName] = activities; + } + + activities.Add(activity); + } + + writePosition = WriteResourceSpans(buffer, writePosition, sdkLimitOptions, resource, ScopeTracesList); + ReturnActivityListToPool(); + + return writePosition; + } + + internal static void ReturnActivityListToPool() + { + if (ScopeTracesList.Count != 0) + { + foreach (var entry in ScopeTracesList) + { + entry.Value.Clear(); + ActivityListPool.Push(entry.Value); + } + + ScopeTracesList.Clear(); + } + } + + internal static int WriteResourceSpans(byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, Resources.Resource? resource, Dictionary> scopeTraces) + { + writePosition = ProtobufOtlpResourceSerializer.WriteResource(buffer, writePosition, resource); + writePosition = WriteScopeSpans(buffer, writePosition, sdkLimitOptions, scopeTraces); + + return writePosition; + } + + internal static int WriteScopeSpans(byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, Dictionary> scopeTraces) + { + if (scopeTraces != null) + { + foreach (KeyValuePair> entry in scopeTraces) + { + writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.ResourceSpans_Scope_Spans, ProtobufWireType.LEN); + int resourceSpansScopeSpansLengthPosition = writePosition; + writePosition += ReserveSizeForLength; + + writePosition = WriteScopeSpan(buffer, writePosition, sdkLimitOptions, entry.Value[0].Source, entry.Value); + ProtobufSerializer.WriteReservedLength(buffer, resourceSpansScopeSpansLengthPosition, writePosition - (resourceSpansScopeSpansLengthPosition + ReserveSizeForLength)); + } + } + + return writePosition; + } + + internal static int WriteScopeSpan(byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, ActivitySource activitySource, List activities) + { + writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.ScopeSpans_Scope, ProtobufWireType.LEN); + int instrumentationScopeLengthPosition = writePosition; + writePosition += ReserveSizeForLength; + + writePosition = ProtobufSerializer.WriteStringWithTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.InstrumentationScope_Name, activitySource.Name); + if (activitySource.Version != null) + { + writePosition = ProtobufSerializer.WriteStringWithTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.InstrumentationScope_Version, activitySource.Version); + } + + if (activitySource.Tags != null) + { + var maxAttributeCount = sdkLimitOptions.SpanAttributeCountLimit ?? int.MaxValue; + var maxAttributeValueLength = sdkLimitOptions.AttributeValueLengthLimit ?? int.MaxValue; + ProtobufOtlpTagWriter.OtlpTagWriterState otlpTagWriterState = new ProtobufOtlpTagWriter.OtlpTagWriterState + { + Buffer = buffer, + WritePosition = writePosition, + TagCount = 0, + DroppedTagCount = 0, + }; + + if (activitySource.Tags is IReadOnlyList> activitySourceTagsList) + { + for (int i = 0; i < activitySourceTagsList.Count; i++) + { + if (otlpTagWriterState.TagCount < maxAttributeCount) + { + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpTraceFieldNumberConstants.InstrumentationScope_Attributes, ProtobufWireType.LEN); + int instrumentationScopeAttributesLengthPosition = otlpTagWriterState.WritePosition; + otlpTagWriterState.WritePosition += ReserveSizeForLength; + + ProtobufOtlpTagWriter.Instance.TryWriteTag(ref otlpTagWriterState, activitySourceTagsList[i].Key, activitySourceTagsList[i].Value, maxAttributeValueLength); + + var instrumentationScopeAttributesLength = otlpTagWriterState.WritePosition - (instrumentationScopeAttributesLengthPosition + ReserveSizeForLength); + ProtobufSerializer.WriteReservedLength(otlpTagWriterState.Buffer, instrumentationScopeAttributesLengthPosition, instrumentationScopeAttributesLength); + otlpTagWriterState.TagCount++; + } + else + { + otlpTagWriterState.DroppedTagCount++; + } + } + } + else + { + foreach (var tag in activitySource.Tags) + { + if (otlpTagWriterState.TagCount < maxAttributeCount) + { + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpTraceFieldNumberConstants.InstrumentationScope_Attributes, ProtobufWireType.LEN); + int instrumentationScopeAttributesLengthPosition = otlpTagWriterState.WritePosition; + otlpTagWriterState.WritePosition += ReserveSizeForLength; + + ProtobufOtlpTagWriter.Instance.TryWriteTag(ref otlpTagWriterState, tag.Key, tag.Value, maxAttributeValueLength); + + var instrumentationScopeAttributesLength = otlpTagWriterState.WritePosition - (instrumentationScopeAttributesLengthPosition + ReserveSizeForLength); + ProtobufSerializer.WriteReservedLength(otlpTagWriterState.Buffer, instrumentationScopeAttributesLengthPosition, instrumentationScopeAttributesLength); + otlpTagWriterState.TagCount++; + } + else + { + otlpTagWriterState.DroppedTagCount++; + } + } + } + + if (otlpTagWriterState.DroppedTagCount > 0) + { + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteTag(buffer, otlpTagWriterState.WritePosition, ProtobufOtlpTraceFieldNumberConstants.InstrumentationScope_Dropped_Attributes_Count, ProtobufWireType.VARINT); + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteVarInt32(buffer, otlpTagWriterState.WritePosition, (uint)otlpTagWriterState.DroppedTagCount); + } + + writePosition = otlpTagWriterState.WritePosition; + } + + ProtobufSerializer.WriteReservedLength(buffer, instrumentationScopeLengthPosition, writePosition - (instrumentationScopeLengthPosition + ReserveSizeForLength)); + + for (int i = 0; i < activities.Count; i++) + { + writePosition = WriteSpan(buffer, writePosition, sdkLimitOptions, activities[i]); + } + + return writePosition; + } + + internal static int WriteSpan(byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, Activity activity) + { + writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.ScopeSpans_Span, ProtobufWireType.LEN); + int spanLengthPosition = writePosition; + writePosition += ReserveSizeForLength; + + writePosition = ProtobufSerializer.WriteTagAndLength(buffer, writePosition, TraceIdSize, ProtobufOtlpTraceFieldNumberConstants.Span_Trace_Id, ProtobufWireType.LEN); + writePosition = WriteTraceId(buffer, writePosition, activity.TraceId); + + writePosition = ProtobufSerializer.WriteTagAndLength(buffer, writePosition, SpanIdSize, ProtobufOtlpTraceFieldNumberConstants.Span_Span_Id, ProtobufWireType.LEN); + writePosition = WriteSpanId(buffer, writePosition, activity.SpanId); + + if (activity.TraceStateString != null) + { + writePosition = ProtobufSerializer.WriteStringWithTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.Span_Trace_State, activity.TraceStateString); + } + + if (activity.ParentSpanId != default) + { + writePosition = ProtobufSerializer.WriteTagAndLength(buffer, writePosition, SpanIdSize, ProtobufOtlpTraceFieldNumberConstants.Span_Parent_Span_Id, ProtobufWireType.LEN); + writePosition = WriteSpanId(buffer, writePosition, activity.ParentSpanId); + } + + writePosition = WriteTraceFlags(buffer, writePosition, activity.ActivityTraceFlags, activity.HasRemoteParent, ProtobufOtlpTraceFieldNumberConstants.Span_Flags); + writePosition = ProtobufSerializer.WriteStringWithTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.Span_Name, activity.DisplayName); + writePosition = ProtobufSerializer.WriteEnumWithTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.Span_Kind, (int)activity.Kind + 1); + writePosition = ProtobufSerializer.WriteFixed64WithTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.Span_Start_Time_Unix_Nano, (ulong)activity.StartTimeUtc.ToUnixTimeNanoseconds()); + writePosition = ProtobufSerializer.WriteFixed64WithTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.Span_End_Time_Unix_Nano, (ulong)(activity.StartTimeUtc.ToUnixTimeNanoseconds() + activity.Duration.ToNanoseconds())); + + (writePosition, StatusCode? statusCode, string? statusMessage) = WriteActivityTags(buffer, writePosition, sdkLimitOptions, activity); + writePosition = WriteSpanEvents(buffer, writePosition, sdkLimitOptions, activity); + writePosition = WriteSpanLinks(buffer, writePosition, sdkLimitOptions, activity); + writePosition = WriteSpanStatus(buffer, writePosition, activity, statusCode, statusMessage); + ProtobufSerializer.WriteReservedLength(buffer, spanLengthPosition, writePosition - (spanLengthPosition + ReserveSizeForLength)); + + return writePosition; + } + + internal static int WriteTraceId(byte[] buffer, int position, ActivityTraceId activityTraceId) + { + var traceBytes = new Span(buffer, position, TraceIdSize); + activityTraceId.CopyTo(traceBytes); + return position + TraceIdSize; + } + + internal static int WriteSpanId(byte[] buffer, int position, ActivitySpanId activitySpanId) + { + var spanIdBytes = new Span(buffer, position, SpanIdSize); + activitySpanId.CopyTo(spanIdBytes); + return position + SpanIdSize; + } + + internal static int WriteTraceFlags(byte[] buffer, int position, ActivityTraceFlags activityTraceFlags, bool hasRemoteParent, int fieldNumber) + { + uint spanFlags = (uint)activityTraceFlags & (byte)0x000000FF; + + spanFlags |= 0x00000100; + if (hasRemoteParent) + { + spanFlags |= 0x00000200; + } + + position = ProtobufSerializer.WriteFixed32WithTag(buffer, position, fieldNumber, spanFlags); + + return position; + } + + internal static (int Position, StatusCode? StatusCode, string? StatusMessage) WriteActivityTags(byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, Activity activity) + { + StatusCode? statusCode = null; + string? statusMessage = null; + int maxAttributeCount = sdkLimitOptions.SpanAttributeCountLimit ?? int.MaxValue; + int maxAttributeValueLength = sdkLimitOptions.AttributeValueLengthLimit ?? int.MaxValue; + ProtobufOtlpTagWriter.OtlpTagWriterState otlpTagWriterState = new ProtobufOtlpTagWriter.OtlpTagWriterState + { + Buffer = buffer, + WritePosition = writePosition, + TagCount = 0, + DroppedTagCount = 0, + }; + + foreach (ref readonly var tag in activity.EnumerateTagObjects()) + { + switch (tag.Key) + { + case "otel.status_code": + + statusCode = tag.Value switch + { + /* + * Note: Order here does matter for perf. Unset is + * first because assumption is most spans will be + * Unset, then Error. Ok is not set by the SDK. + */ + not null when UnsetStatusCodeTagValue.Equals(tag.Value as string, StringComparison.OrdinalIgnoreCase) => StatusCode.Unset, + not null when ErrorStatusCodeTagValue.Equals(tag.Value as string, StringComparison.OrdinalIgnoreCase) => StatusCode.Error, + not null when OkStatusCodeTagValue.Equals(tag.Value as string, StringComparison.OrdinalIgnoreCase) => StatusCode.Ok, + _ => null, + }; + continue; + case "otel.status_description": + statusMessage = tag.Value as string; + continue; + } + + if (otlpTagWriterState.TagCount < maxAttributeCount) + { + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpTraceFieldNumberConstants.Span_Attributes, ProtobufWireType.LEN); + int spanAttributesLengthPosition = otlpTagWriterState.WritePosition; + otlpTagWriterState.WritePosition += ReserveSizeForLength; + + ProtobufOtlpTagWriter.Instance.TryWriteTag(ref otlpTagWriterState, tag.Key, tag.Value, maxAttributeValueLength); + + ProtobufSerializer.WriteReservedLength(buffer, spanAttributesLengthPosition, otlpTagWriterState.WritePosition - (spanAttributesLengthPosition + 4)); + otlpTagWriterState.TagCount++; + } + else + { + otlpTagWriterState.DroppedTagCount++; + } + } + + if (otlpTagWriterState.DroppedTagCount > 0) + { + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteTag(buffer, otlpTagWriterState.WritePosition, ProtobufOtlpTraceFieldNumberConstants.Span_Dropped_Attributes_Count, ProtobufWireType.VARINT); + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteVarInt32(buffer, otlpTagWriterState.WritePosition, (uint)otlpTagWriterState.DroppedTagCount); + } + + return (otlpTagWriterState.WritePosition, statusCode, statusMessage); + } + + internal static int WriteSpanEvents(byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, Activity activity) + { + int maxEventCountLimit = sdkLimitOptions.SpanEventCountLimit ?? int.MaxValue; + int eventCount = 0; + int droppedEventCount = 0; + foreach (ref readonly var evnt in activity.EnumerateEvents()) + { + if (eventCount < maxEventCountLimit) + { + writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.Span_Events, ProtobufWireType.LEN); + int spanEventsLengthPosition = writePosition; + writePosition += ReserveSizeForLength; // Reserve 4 bytes for length + + writePosition = ProtobufSerializer.WriteStringWithTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.Event_Name, evnt.Name); + writePosition = ProtobufSerializer.WriteFixed64WithTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.Event_Time_Unix_Nano, (ulong)evnt.Timestamp.ToUnixTimeNanoseconds()); + writePosition = WriteEventAttributes(ref buffer, writePosition, sdkLimitOptions, evnt); + + ProtobufSerializer.WriteReservedLength(buffer, spanEventsLengthPosition, writePosition - (spanEventsLengthPosition + ReserveSizeForLength)); + eventCount++; + } + else + { + droppedEventCount++; + } + } + + if (droppedEventCount > 0) + { + writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.Span_Dropped_Events_Count, ProtobufWireType.VARINT); + writePosition = ProtobufSerializer.WriteVarInt32(buffer, writePosition, (uint)droppedEventCount); + } + + return writePosition; + } + + internal static int WriteEventAttributes(ref byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, ActivityEvent evnt) + { + int maxAttributeCount = sdkLimitOptions.SpanEventAttributeCountLimit ?? int.MaxValue; + int maxAttributeValueLength = sdkLimitOptions.AttributeValueLengthLimit ?? int.MaxValue; + + ProtobufOtlpTagWriter.OtlpTagWriterState otlpTagWriterState = new ProtobufOtlpTagWriter.OtlpTagWriterState + { + Buffer = buffer, + WritePosition = writePosition, + TagCount = 0, + DroppedTagCount = 0, + }; + + foreach (ref readonly var tag in evnt.EnumerateTagObjects()) + { + if (otlpTagWriterState.TagCount < maxAttributeCount) + { + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpTraceFieldNumberConstants.Event_Attributes, ProtobufWireType.LEN); + int eventAttributesLengthPosition = otlpTagWriterState.WritePosition; + otlpTagWriterState.WritePosition += ReserveSizeForLength; + ProtobufOtlpTagWriter.Instance.TryWriteTag(ref otlpTagWriterState, tag.Key, tag.Value, maxAttributeValueLength); + ProtobufSerializer.WriteReservedLength(buffer, eventAttributesLengthPosition, otlpTagWriterState.WritePosition - (eventAttributesLengthPosition + ReserveSizeForLength)); + otlpTagWriterState.TagCount++; + } + else + { + otlpTagWriterState.DroppedTagCount++; + } + } + + if (otlpTagWriterState.DroppedTagCount > 0) + { + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteTag(buffer, otlpTagWriterState.WritePosition, ProtobufOtlpTraceFieldNumberConstants.Event_Dropped_Attributes_Count, ProtobufWireType.VARINT); + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteVarInt32(buffer, otlpTagWriterState.WritePosition, (uint)otlpTagWriterState.DroppedTagCount); + } + + return otlpTagWriterState.WritePosition; + } + + internal static int WriteSpanLinks(byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, Activity activity) + { + int maxLinksCount = sdkLimitOptions.SpanLinkCountLimit ?? int.MaxValue; + int linkCount = 0; + int droppedLinkCount = 0; + + foreach (ref readonly var link in activity.EnumerateLinks()) + { + if (linkCount < maxLinksCount) + { + writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.Span_Links, ProtobufWireType.LEN); + int spanLinksLengthPosition = writePosition; + writePosition += ReserveSizeForLength; // Reserve 4 bytes for length + + writePosition = ProtobufSerializer.WriteTagAndLength(buffer, writePosition, TraceIdSize, ProtobufOtlpTraceFieldNumberConstants.Link_Trace_Id, ProtobufWireType.LEN); + writePosition = WriteTraceId(buffer, writePosition, link.Context.TraceId); + writePosition = ProtobufSerializer.WriteTagAndLength(buffer, writePosition, SpanIdSize, ProtobufOtlpTraceFieldNumberConstants.Link_Span_Id, ProtobufWireType.LEN); + writePosition = WriteSpanId(buffer, writePosition, link.Context.SpanId); + if (link.Context.TraceState != null) + { + writePosition = ProtobufSerializer.WriteStringWithTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.Span_Trace_State, link.Context.TraceState); + } + + writePosition = WriteLinkAttributes(buffer, writePosition, sdkLimitOptions, link); + writePosition = WriteTraceFlags(buffer, writePosition, link.Context.TraceFlags, link.Context.IsRemote, ProtobufOtlpTraceFieldNumberConstants.Link_Flags); + + ProtobufSerializer.WriteReservedLength(buffer, spanLinksLengthPosition, writePosition - (spanLinksLengthPosition + ReserveSizeForLength)); + linkCount++; + } + else + { + droppedLinkCount++; + } + } + + if (droppedLinkCount > 0) + { + writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, ProtobufOtlpTraceFieldNumberConstants.Span_Dropped_Links_Count, ProtobufWireType.VARINT); + writePosition = ProtobufSerializer.WriteVarInt32(buffer, writePosition, (uint)droppedLinkCount); + } + + return writePosition; + } + + internal static int WriteLinkAttributes(byte[] buffer, int writePosition, SdkLimitOptions sdkLimitOptions, ActivityLink link) + { + int maxAttributeCount = sdkLimitOptions.SpanLinkAttributeCountLimit ?? int.MaxValue; + int maxAttributeValueLength = sdkLimitOptions.AttributeValueLengthLimit ?? int.MaxValue; + ProtobufOtlpTagWriter.OtlpTagWriterState otlpTagWriterState = new ProtobufOtlpTagWriter.OtlpTagWriterState + { + Buffer = buffer, + WritePosition = writePosition, + TagCount = 0, + DroppedTagCount = 0, + }; + + foreach (ref readonly var tag in link.EnumerateTagObjects()) + { + if (otlpTagWriterState.TagCount < maxAttributeCount) + { + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpTraceFieldNumberConstants.Link_Attributes, ProtobufWireType.LEN); + int linkAttributesLengthPosition = otlpTagWriterState.WritePosition; + otlpTagWriterState.WritePosition += ReserveSizeForLength; + ProtobufOtlpTagWriter.Instance.TryWriteTag(ref otlpTagWriterState, tag.Key, tag.Value, maxAttributeValueLength); + ProtobufSerializer.WriteReservedLength(buffer, linkAttributesLengthPosition, otlpTagWriterState.WritePosition - (linkAttributesLengthPosition + ReserveSizeForLength)); + otlpTagWriterState.TagCount++; + } + else + { + otlpTagWriterState.DroppedTagCount++; + } + } + + if (otlpTagWriterState.DroppedTagCount > 0) + { + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteTag(buffer, otlpTagWriterState.WritePosition, ProtobufOtlpTraceFieldNumberConstants.Link_Dropped_Attributes_Count, ProtobufWireType.VARINT); + otlpTagWriterState.WritePosition = ProtobufSerializer.WriteVarInt32(buffer, otlpTagWriterState.WritePosition, (uint)otlpTagWriterState.DroppedTagCount); + } + + return otlpTagWriterState.WritePosition; + } + + internal static int WriteSpanStatus(byte[] buffer, int position, Activity activity, StatusCode? statusCode, string? statusMessage) + { + if (activity.Status == ActivityStatusCode.Unset && statusCode == null) + { + return position; + } + + var useActivity = activity.Status != ActivityStatusCode.Unset; + var isError = useActivity ? activity.Status == ActivityStatusCode.Error : statusCode == StatusCode.Error; + var description = useActivity ? activity.StatusDescription : statusMessage; + + if (isError && description != null) + { + var descriptionSpan = description.AsSpan(); + var numberOfUtf8CharsInString = ProtobufSerializer.GetNumberOfUtf8CharsInString(descriptionSpan); + position = ProtobufSerializer.WriteTagAndLength(buffer, position, numberOfUtf8CharsInString + 4, ProtobufOtlpTraceFieldNumberConstants.Span_Status, ProtobufWireType.LEN); + position = ProtobufSerializer.WriteStringWithTag(buffer, position, ProtobufOtlpTraceFieldNumberConstants.Status_Message, numberOfUtf8CharsInString, descriptionSpan); + } + else + { + position = ProtobufSerializer.WriteTagAndLength(buffer, position, 2, ProtobufOtlpTraceFieldNumberConstants.Span_Status, ProtobufWireType.LEN); + } + + var finalStatusCode = useActivity ? (int)activity.Status : (statusCode != null && statusCode != StatusCode.Unset) ? (int)statusCode! : (int)StatusCode.Unset; + position = ProtobufSerializer.WriteEnumWithTag(buffer, position, ProtobufOtlpTraceFieldNumberConstants.Status_Code, finalStatusCode); + + return position; + } +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufSerializer.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufSerializer.cs new file mode 100644 index 0000000000..763a56db97 --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufSerializer.cs @@ -0,0 +1,337 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Buffers.Binary; +using System.Diagnostics; +using System.Runtime.CompilerServices; +#if NETFRAMEWORK || NETSTANDARD2_0 +using System.Runtime.InteropServices; +#endif +using System.Text; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; + +internal static class ProtobufSerializer +{ + private const uint UInt128 = 0x80; + private const ulong ULong128 = 0x80; + private const int Fixed32Size = 4; + private const int Fixed64Size = 8; + + private static readonly Encoding Utf8Encoding = Encoding.UTF8; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static uint GetTagValue(int fieldNumber, ProtobufWireType wireType) => ((uint)(fieldNumber << 3)) | (uint)wireType; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteTag(byte[] buffer, int writePosition, int fieldNumber, ProtobufWireType type) => WriteVarInt32(buffer, writePosition, GetTagValue(fieldNumber, type)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteLength(byte[] buffer, int writePosition, int length) => WriteVarInt32(buffer, writePosition, (uint)length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteTagAndLength(byte[] buffer, int writePosition, int contentLength, int fieldNumber, ProtobufWireType type) + { + writePosition = WriteTag(buffer, writePosition, fieldNumber, type); + writePosition = WriteLength(buffer, writePosition, contentLength); + + return writePosition; + } + + [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; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteBoolWithTag(byte[] buffer, int writePosition, int fieldNumber, bool value) + { + writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.VARINT); + buffer[writePosition++] = value ? (byte)1 : (byte)0; + return writePosition; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteEnumWithTag(byte[] buffer, int writePosition, int fieldNumber, int value) + { + writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.VARINT); + buffer[writePosition++] = (byte)value; + return writePosition; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteFixed32LittleEndianFormat(byte[] buffer, int writePosition, uint value) + { + Span span = new(buffer, writePosition, Fixed32Size); + BinaryPrimitives.WriteUInt32LittleEndian(span, value); + writePosition += Fixed32Size; + + return writePosition; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteFixed64LittleEndianFormat(byte[] buffer, int writePosition, ulong value) + { + Span span = new(buffer, writePosition, Fixed64Size); + BinaryPrimitives.WriteUInt64LittleEndian(span, value); + writePosition += Fixed64Size; + + return writePosition; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteFixed32WithTag(byte[] buffer, int writePosition, int fieldNumber, uint value) + { + writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.I32); + writePosition = WriteFixed32LittleEndianFormat(buffer, writePosition, value); + + return writePosition; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteFixed64WithTag(byte[] buffer, int writePosition, int fieldNumber, ulong value) + { + writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.I64); + writePosition = WriteFixed64LittleEndianFormat(buffer, writePosition, value); + + return writePosition; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteVarInt32(byte[] buffer, int writePosition, uint value) + { + while (value >= UInt128) + { + buffer[writePosition++] = (byte)(0x80 | (value & 0x7F)); + value >>= 7; + } + + buffer[writePosition++] = (byte)value; + return writePosition; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteVarInt64(byte[] buffer, int writePosition, ulong value) + { + while (value >= ULong128) + { + buffer[writePosition++] = (byte)(0x80 | (value & 0x7F)); + value >>= 7; + } + + buffer[writePosition++] = (byte)value; + return writePosition; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteInt64WithTag(byte[] buffer, int writePosition, int fieldNumber, ulong value) + { + writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.VARINT); + writePosition = WriteVarInt64(buffer, writePosition, value); + + return writePosition; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteDoubleWithTag(byte[] buffer, int writePosition, int fieldNumber, double value) + { + writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.I64); + writePosition = WriteFixed64LittleEndianFormat(buffer, writePosition, (ulong)BitConverter.DoubleToInt64Bits(value)); + + return writePosition; + } + + /// + /// Computes the number of bytes required to encode a 64-bit unsigned integer in Protocol Buffers' varint format. + /// + /// + /// Protocol Buffers uses variable-length encoding (varint) to serialize integers efficiently: + /// - Each byte uses 7 bits to encode the number and 1 bit (MSB) to indicate if more bytes follow + /// - The algorithm checks how many significant bits the number contains by shifting and masking + /// - Numbers are encoded in groups of 7 bits, from least to most significant + /// - Each group requires one byte, so the method returns the number of 7-bit groups needed + /// + /// Examples: + /// - Values 0-127 (7 bits) require 1 byte + /// - Values 128-16383 (14 bits) require 2 bytes + /// - Values 16384-2097151 (21 bits) require 3 bytes + /// And so on... + /// + /// For more details, see: + /// - Protocol Buffers encoding reference: https://developers.google.com/protocol-buffers/docs/encoding#varints. + /// + /// The unsigned 64-bit integer to be encoded. + /// Number of bytes needed to encode the value. + internal static int ComputeVarInt64Size(ulong value) + { + if ((value & (0xffffffffffffffffL << 7)) == 0) + { + return 1; + } + + if ((value & (0xffffffffffffffffL << 14)) == 0) + { + return 2; + } + + if ((value & (0xffffffffffffffffL << 21)) == 0) + { + return 3; + } + + if ((value & (0xffffffffffffffffL << 28)) == 0) + { + return 4; + } + + if ((value & (0xffffffffffffffffL << 35)) == 0) + { + return 5; + } + + if ((value & (0xffffffffffffffffL << 42)) == 0) + { + return 6; + } + + if ((value & (0xffffffffffffffffL << 49)) == 0) + { + return 7; + } + + if ((value & (0xffffffffffffffffL << 56)) == 0) + { + return 8; + } + + if ((value & (0xffffffffffffffffL << 63)) == 0) + { + return 9; + } + + return 10; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteStringWithTag(byte[] buffer, int writePosition, int fieldNumber, string value) + { + Debug.Assert(value != null, "value was null"); + + return WriteStringWithTag(buffer, writePosition, fieldNumber, value.AsSpan()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int GetNumberOfUtf8CharsInString(ReadOnlySpan value) + { +#if NETFRAMEWORK || NETSTANDARD2_0 + int numberOfUtf8CharsInString; + unsafe + { + fixed (char* strPtr = &GetNonNullPinnableReference(value)) + { + numberOfUtf8CharsInString = Utf8Encoding.GetByteCount(strPtr, value.Length); + } + } +#else + int numberOfUtf8CharsInString = Utf8Encoding.GetByteCount(value); +#endif + return numberOfUtf8CharsInString; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteStringWithTag(byte[] buffer, int writePosition, int fieldNumber, ReadOnlySpan value) + { + var numberOfUtf8CharsInString = GetNumberOfUtf8CharsInString(value); + return WriteStringWithTag(buffer, writePosition, fieldNumber, numberOfUtf8CharsInString, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int WriteStringWithTag(byte[] buffer, int writePosition, int fieldNumber, int numberOfUtf8CharsInString, ReadOnlySpan value) + { + writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.LEN); + writePosition = WriteLength(buffer, writePosition, numberOfUtf8CharsInString); + +#if NETFRAMEWORK || NETSTANDARD2_0 + unsafe + { + fixed (char* strPtr = &GetNonNullPinnableReference(value)) + { + fixed (byte* bufferPtr = buffer) + { + var bytesWritten = Utf8Encoding.GetBytes(strPtr, value.Length, bufferPtr + writePosition, numberOfUtf8CharsInString); + Debug.Assert(bytesWritten == numberOfUtf8CharsInString, "bytesWritten did not match numberOfUtf8CharsInString"); + } + } + } +#else + var bytesWritten = Utf8Encoding.GetBytes(value, buffer.AsSpan().Slice(writePosition)); + Debug.Assert(bytesWritten == numberOfUtf8CharsInString, "bytesWritten did not match numberOfUtf8CharsInString"); +#endif + + writePosition += numberOfUtf8CharsInString; + return writePosition; + } + +#if NETFRAMEWORK || NETSTANDARD2_0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe ref T GetNonNullPinnableReference(ReadOnlySpan span) + => ref (span.Length != 0) ? ref Unsafe.AsRef(in MemoryMarshal.GetReference(span)) : ref Unsafe.AsRef((void*)1); +#endif +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufWireType.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufWireType.cs new file mode 100644 index 0000000000..b3b5fe4031 --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufWireType.cs @@ -0,0 +1,47 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; + +/// +/// Wire types within protobuf encoding. +/// https://protobuf.dev/programming-guides/encoding/#structure. +/// +internal enum ProtobufWireType : uint +{ + /// + /// Variable-length integer. + /// Used for int32, int64, uint32, uint64, sint32, sint64, bool, enum. + /// + VARINT = 0, + + /// + /// A fixed-length 64-bit value. + /// Used for fixed64, sfixed64, double. + /// + I64 = 1, + + /// + /// A length-delimited value. + /// Used for string, bytes, embedded messages, packed repeated fields. + /// + LEN = 2, + + /// + /// Group Start value. + /// (Deprecated). + /// + SGROUP = 3, + + /// + /// Group End value. + /// (Deprecated). + /// + EGROUP = 4, + + /// + /// A fixed-length 32-bit value. + /// Used for fixed32, sfixed32, float. + /// + I32 = 5, +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterPersistentStorageTransmissionHandler.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterPersistentStorageTransmissionHandler.cs index e8ed3da211..9d4d86c71d 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterPersistentStorageTransmissionHandler.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterPersistentStorageTransmissionHandler.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using Google.Protobuf; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterRetryTransmissionHandler.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterRetryTransmissionHandler.cs index d4be5c9d64..6904dc8dc4 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterRetryTransmissionHandler.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterRetryTransmissionHandler.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Transmission; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterTransmissionHandler.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterTransmissionHandler.cs index 2b56e16dd7..12b76bdf83 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterTransmissionHandler.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/OtlpExporterTransmissionHandler.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; using OpenTelemetry.Internal; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/RetryHelper.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/RetryHelper.cs index cf663a5f79..f68d1255e4 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/RetryHelper.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Transmission/RetryHelper.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Transmission; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/opentelemetry/proto/collector/profiles/v1experimental/profiles_service_http.yaml b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/opentelemetry/proto/collector/profiles/v1experimental/profiles_service_http.yaml index 6bb7cf740b..ea501afbbb 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/opentelemetry/proto/collector/profiles/v1experimental/profiles_service_http.yaml +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/opentelemetry/proto/collector/profiles/v1experimental/profiles_service_http.yaml @@ -4,6 +4,6 @@ type: google.api.Service config_version: 3 http: rules: - - selector: opentelemetry.proto.collector.profiles.v1.ProfilesService.Export + - selector: opentelemetry.proto.collector.profiles.v1experimental.ProfilesService.Export post: /v1experimental/profiles body: "*" diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/opentelemetry/proto/profiles/v1experimental/pprofextended.proto b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/opentelemetry/proto/profiles/v1experimental/pprofextended.proto index bd30083554..b5b5b88fce 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/opentelemetry/proto/profiles/v1experimental/pprofextended.proto +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/opentelemetry/proto/profiles/v1experimental/pprofextended.proto @@ -59,6 +59,8 @@ package opentelemetry.proto.profiles.v1experimental; import "opentelemetry/proto/common/v1/common.proto"; option csharp_namespace = "OpenTelemetry.Proto.Profiles.V1Experimental"; +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.profiles.v1experimental"; option go_package = "go.opentelemetry.io/proto/otlp/profiles/v1experimental"; // Represents a complete profile, including sample types, samples, @@ -203,7 +205,7 @@ enum AggregationTemporality { 11. A request is received, the system measures 1 request. 12. The 1 second collection cycle ends. A metric is exported for the number of requests received over the interval of time t_1 to - t_0+1 with a value of 1. + t_1+1 with a value of 1. Note: Even though, when reporting changes since last report time, using CUMULATIVE is valid, it is not recommended. */ diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/opentelemetry/proto/profiles/v1experimental/profiles.proto b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/opentelemetry/proto/profiles/v1experimental/profiles.proto index bbc2b2931d..84b0108f86 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/opentelemetry/proto/profiles/v1experimental/profiles.proto +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/opentelemetry/proto/profiles/v1experimental/profiles.proto @@ -55,7 +55,7 @@ option go_package = "go.opentelemetry.io/proto/otlp/profiles/v1experimental"; // ┌──────────────────┐ // │ Profile │ // └──────────────────┘ -// │ 1-n +// │ n-1 // │ 1-n ┌───────────────────────────────────────┐ // ▼ │ ▽ // ┌──────────────────┐ 1-n ┌──────────────┐ ┌──────────┐ diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj index f5922910ec..cfeae2fa85 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj @@ -5,8 +5,6 @@ $(PackageTags);OTLP core- - - disable BUILDING_INTERNAL_PERSISTENT_STORAGE;$(DefineConstants) true @@ -18,6 +16,7 @@ https://github.com/open-telemetry/opentelemetry-dotnet/pull/5520#discussion_r1556221048 and https://github.com/dotnet/runtime/issues/92509 --> $(NoWarn);SYSLIB1100;SYSLIB1101 + true @@ -25,7 +24,7 @@ - + diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs index 7c14310d17..355ab37674 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; #if NETFRAMEWORK using System.Net.Http; @@ -32,7 +30,7 @@ public class OtlpExporterOptions : IOtlpExporterOptions internal static readonly KeyValuePair[] StandardHeaders = new KeyValuePair[] { - new KeyValuePair("User-Agent", GetUserAgentString()), + new("User-Agent", GetUserAgentString()), }; internal readonly Func DefaultHttpClientFactory; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs index ba68de1326..b755e15880 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs @@ -8,7 +8,7 @@ using Grpc.Core; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; -#if NETSTANDARD2_1 || NET6_0_OR_GREATER +#if NETSTANDARD2_1 || NET using Grpc.Net.Client; #endif using System.Diagnostics; @@ -22,7 +22,7 @@ namespace OpenTelemetry.Exporter; internal static class OtlpExporterOptionsExtensions { -#if NETSTANDARD2_1 || NET6_0_OR_GREATER +#if NETSTANDARD2_1 || NET public static GrpcChannel CreateChannel(this OtlpExporterOptions options) #else public static Channel CreateChannel(this OtlpExporterOptions options) @@ -33,7 +33,7 @@ public static Channel CreateChannel(this OtlpExporterOptions options) throw new NotSupportedException($"Endpoint URI scheme ({options.Endpoint.Scheme}) is not supported. Currently only \"http\" and \"https\" are supported."); } -#if NETSTANDARD2_1 || NET6_0_OR_GREATER +#if NETSTANDARD2_1 || NET return GrpcChannel.ForAddress(options.Endpoint); #else ChannelCredentials channelCredentials; @@ -233,13 +233,13 @@ public static void TryEnableIHttpClientFactoryIntegration(this OtlpExporterOptio { options.HttpClientFactory = () => { - Type httpClientFactoryType = Type.GetType("System.Net.Http.IHttpClientFactory, Microsoft.Extensions.Http", throwOnError: false); + Type? httpClientFactoryType = Type.GetType("System.Net.Http.IHttpClientFactory, Microsoft.Extensions.Http", throwOnError: false); if (httpClientFactoryType != null) { - object httpClientFactory = serviceProvider.GetService(httpClientFactoryType); + object? httpClientFactory = serviceProvider.GetService(httpClientFactoryType); if (httpClientFactory != null) { - MethodInfo createClientMethod = httpClientFactoryType.GetMethod( + MethodInfo? createClientMethod = httpClientFactoryType.GetMethod( "CreateClient", BindingFlags.Public | BindingFlags.Instance, binder: null, @@ -247,11 +247,14 @@ public static void TryEnableIHttpClientFactoryIntegration(this OtlpExporterOptio modifiers: null); if (createClientMethod != null) { - HttpClient client = (HttpClient)createClientMethod.Invoke(httpClientFactory, new object[] { httpClientName }); + HttpClient? client = (HttpClient?)createClientMethod.Invoke(httpClientFactory, new object[] { httpClientName }); - client.Timeout = TimeSpan.FromMilliseconds(options.TimeoutMilliseconds); + if (client != null) + { + client.Timeout = TimeSpan.FromMilliseconds(options.TimeoutMilliseconds); - return client; + return client; + } } } } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs index b3781c4c34..dec5e8cc3e 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporter.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Transmission; @@ -49,7 +47,7 @@ internal OtlpLogExporter( Debug.Assert(sdkLimitOptions != null, "sdkLimitOptions was null"); Debug.Assert(experimentalOptions != null, "experimentalOptions was null"); - this.transmissionHandler = transmissionHandler ?? exporterOptions.GetLogsExportTransmissionHandler(experimentalOptions!); + this.transmissionHandler = transmissionHandler ?? exporterOptions!.GetLogsExportTransmissionHandler(experimentalOptions!); this.otlpLogRecordTransformer = new OtlpLogRecordTransformer(sdkLimitOptions!, experimentalOptions!); } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporterHelperExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporterHelperExtensions.cs index ca5a759caf..42faf42535 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporterHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpLogExporterHelperExtensions.cs @@ -1,12 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; -#if EXPOSE_EXPERIMENTAL_FEATURES && NET8_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -#endif using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -28,6 +23,7 @@ public static class OtlpLogExporterHelperExtensions /// /// options to use. /// The instance of to chain the calls. + // TODO: [Obsolete("Call LoggerProviderBuilder.AddOtlpExporter instead this method will be removed in a future version.")] public static OpenTelemetryLoggerOptions AddOtlpExporter(this OpenTelemetryLoggerOptions loggerOptions) => AddOtlpExporter(loggerOptions, name: null, configure: null); @@ -37,6 +33,7 @@ public static OpenTelemetryLoggerOptions AddOtlpExporter(this OpenTelemetryLogge /// options to use. /// Callback action for configuring . /// The instance of to chain the calls. + // TODO: [Obsolete("Call LoggerProviderBuilder.AddOtlpExporter instead this method will be removed in a future version.")] public static OpenTelemetryLoggerOptions AddOtlpExporter( this OpenTelemetryLoggerOptions loggerOptions, Action configure) @@ -49,6 +46,7 @@ public static OpenTelemetryLoggerOptions AddOtlpExporter( /// Optional name which is used when retrieving options. /// Optional callback action for configuring . /// The instance of to chain the calls. + // TODO: [Obsolete("Call LoggerProviderBuilder.AddOtlpExporter instead this method will be removed in a future version.")] public static OpenTelemetryLoggerOptions AddOtlpExporter( this OpenTelemetryLoggerOptions loggerOptions, string? name, @@ -81,6 +79,7 @@ public static OpenTelemetryLoggerOptions AddOtlpExporter( /// options to use. /// Callback action for configuring and . /// The instance of to chain the calls. + // TODO: [Obsolete("Call LoggerProviderBuilder.AddOtlpExporter instead this method will be removed in a future version.")] public static OpenTelemetryLoggerOptions AddOtlpExporter( this OpenTelemetryLoggerOptions loggerOptions, Action configureExporterAndProcessor) @@ -93,6 +92,7 @@ public static OpenTelemetryLoggerOptions AddOtlpExporter( /// Optional name which is used when retrieving options. /// Optional callback action for configuring and . /// The instance of to chain the calls. + // TODO: [Obsolete("Call LoggerProviderBuilder.AddOtlpExporter instead this method will be removed in a future version.")] public static OpenTelemetryLoggerOptions AddOtlpExporter( this OpenTelemetryLoggerOptions loggerOptions, string? name, @@ -119,86 +119,34 @@ public static OpenTelemetryLoggerOptions AddOtlpExporter( }); } -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Adds an OTLP exporter to the LoggerProvider. /// - /// WARNING: This is an experimental API which might change or be removed in the future. Use at your own risk. /// builder to use. /// The instance of to chain the calls. -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else - /// - /// Adds an OTLP exporter to the LoggerProvider. - /// - /// builder to use. - /// The instance of to chain the calls. - internal -#endif - static LoggerProviderBuilder AddOtlpExporter(this LoggerProviderBuilder builder) + public static LoggerProviderBuilder AddOtlpExporter(this LoggerProviderBuilder builder) => AddOtlpExporter(builder, name: null, configureExporter: null); -#if EXPOSE_EXPERIMENTAL_FEATURES - /// - /// Adds an OTLP exporter to the LoggerProvider. - /// - /// WARNING: This is an experimental API which might change or be removed in the future. Use at your own risk. - /// builder to use. - /// Callback action for configuring . - /// The instance of to chain the calls. -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else /// /// Adds an OTLP exporter to the LoggerProvider. /// /// builder to use. /// Callback action for configuring . /// The instance of to chain the calls. - internal -#endif - static LoggerProviderBuilder AddOtlpExporter(this LoggerProviderBuilder builder, Action configureExporter) + public static LoggerProviderBuilder AddOtlpExporter(this LoggerProviderBuilder builder, Action configureExporter) => AddOtlpExporter(builder, name: null, configureExporter); -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Adds an OTLP exporter to the LoggerProvider. /// - /// WARNING: This is an experimental API which might change or be removed in the future. Use at your own risk. /// builder to use. /// Callback action for /// configuring and . /// The instance of to chain the calls. -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else - internal -#endif - static LoggerProviderBuilder AddOtlpExporter(this LoggerProviderBuilder builder, Action configureExporterAndProcessor) + public static LoggerProviderBuilder AddOtlpExporter(this LoggerProviderBuilder builder, Action configureExporterAndProcessor) => AddOtlpExporter(builder, name: null, configureExporterAndProcessor); -#if EXPOSE_EXPERIMENTAL_FEATURES - /// - /// Adds OpenTelemetry Protocol (OTLP) exporter to the LoggerProvider. - /// - /// WARNING: This is an experimental API which might change or be removed in the future. Use at your own risk. - /// builder to use. - /// Optional name which is used when retrieving options. - /// Optional callback action for configuring . - /// The instance of to chain the calls. -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else /// /// Adds OpenTelemetry Protocol (OTLP) exporter to the LoggerProvider. /// @@ -206,9 +154,7 @@ static LoggerProviderBuilder AddOtlpExporter(this LoggerProviderBuilder builder, /// Optional name which is used when retrieving options. /// Optional callback action for configuring . /// The instance of to chain the calls. - internal -#endif - static LoggerProviderBuilder AddOtlpExporter( + public static LoggerProviderBuilder AddOtlpExporter( this LoggerProviderBuilder builder, string? name, Action? configureExporter) @@ -265,22 +211,6 @@ static LoggerProviderBuilder AddOtlpExporter( }); } -#if EXPOSE_EXPERIMENTAL_FEATURES - /// - /// Adds an OTLP exporter to the LoggerProvider. - /// - /// WARNING: This is an experimental API which might change or be removed in the future. Use at your own risk. - /// builder to use. - /// Optional name which is used when retrieving options. - /// Optional callback action for - /// configuring and . - /// The instance of to chain the calls. -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else /// /// Adds an OTLP exporter to the LoggerProvider. /// @@ -290,9 +220,7 @@ static LoggerProviderBuilder AddOtlpExporter( /// configuring and . /// The instance of to chain the calls. - internal -#endif - static LoggerProviderBuilder AddOtlpExporter( + public static LoggerProviderBuilder AddOtlpExporter( this LoggerProviderBuilder builder, string? name, Action? configureExporterAndProcessor) @@ -358,7 +286,7 @@ internal static BaseProcessor BuildOtlpLogExporter( if (!skipUseOtlpExporterRegistrationCheck) { - serviceProvider.EnsureNoUseOtlpExporterRegistrations(); + serviceProvider!.EnsureNoUseOtlpExporterRegistrations(); } /* diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporter.cs index b2b0c3e576..aef2bdc4fe 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporter.cs @@ -18,7 +18,7 @@ public class OtlpMetricExporter : BaseExporter { private readonly OtlpExporterTransmissionHandler transmissionHandler; - private OtlpResource.Resource processResource; + private OtlpResource.Resource? processResource; /// /// Initializes a new instance of the class. @@ -38,12 +38,12 @@ public OtlpMetricExporter(OtlpExporterOptions options) internal OtlpMetricExporter( OtlpExporterOptions exporterOptions, ExperimentalOptions experimentalOptions, - OtlpExporterTransmissionHandler transmissionHandler = null) + OtlpExporterTransmissionHandler? transmissionHandler = null) { Debug.Assert(exporterOptions != null, "exporterOptions was null"); Debug.Assert(experimentalOptions != null, "experimentalOptions was null"); - this.transmissionHandler = transmissionHandler ?? exporterOptions.GetMetricsExportTransmissionHandler(experimentalOptions); + this.transmissionHandler = transmissionHandler ?? exporterOptions!.GetMetricsExportTransmissionHandler(experimentalOptions!); } internal OtlpResource.Resource ProcessResource => this.processResource ??= this.ParentProvider.GetResource().ToOtlpResource(); diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs index 57ad3dd47e..1a8ef2f1b4 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -172,12 +170,12 @@ internal static MetricReader BuildOtlpExporterMetricReader( if (!skipUseOtlpExporterRegistrationCheck) { - serviceProvider.EnsureNoUseOtlpExporterRegistrations(); + serviceProvider!.EnsureNoUseOtlpExporterRegistrations(); } - exporterOptions.TryEnableIHttpClientFactoryIntegration(serviceProvider, "OtlpMetricExporter"); + exporterOptions!.TryEnableIHttpClientFactoryIntegration(serviceProvider!, "OtlpMetricExporter"); - BaseExporter metricExporter = new OtlpMetricExporter(exporterOptions, experimentalOptions); + BaseExporter metricExporter = new OtlpMetricExporter(exporterOptions!, experimentalOptions!); if (configureExporterInstance != null) { diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporter.cs index b02d348bd8..da92667e03 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporter.cs @@ -18,7 +18,7 @@ public class OtlpTraceExporter : BaseExporter private readonly SdkLimitOptions sdkLimitOptions; private readonly OtlpExporterTransmissionHandler transmissionHandler; - private OtlpResource.Resource processResource; + private OtlpResource.Resource? processResource; /// /// Initializes a new instance of the class. @@ -40,14 +40,14 @@ internal OtlpTraceExporter( OtlpExporterOptions exporterOptions, SdkLimitOptions sdkLimitOptions, ExperimentalOptions experimentalOptions, - OtlpExporterTransmissionHandler transmissionHandler = null) + OtlpExporterTransmissionHandler? transmissionHandler = null) { Debug.Assert(exporterOptions != null, "exporterOptions was null"); Debug.Assert(sdkLimitOptions != null, "sdkLimitOptions was null"); - this.sdkLimitOptions = sdkLimitOptions; + this.sdkLimitOptions = sdkLimitOptions!; - this.transmissionHandler = transmissionHandler ?? exporterOptions.GetTraceExportTransmissionHandler(experimentalOptions); + this.transmissionHandler = transmissionHandler ?? exporterOptions!.GetTraceExportTransmissionHandler(experimentalOptions); } internal OtlpResource.Resource ProcessResource => this.processResource ??= this.ParentProvider.GetResource().ToOtlpResource(); diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporterHelperExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporterHelperExtensions.cs index 036e9a90dd..3a18b3da42 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporterHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporterHelperExtensions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -133,12 +131,12 @@ internal static BaseProcessor BuildOtlpExporterProcessor( if (!skipUseOtlpExporterRegistrationCheck) { - serviceProvider.EnsureNoUseOtlpExporterRegistrations(); + serviceProvider!.EnsureNoUseOtlpExporterRegistrations(); } - exporterOptions.TryEnableIHttpClientFactoryIntegration(serviceProvider, "OtlpTraceExporter"); + exporterOptions!.TryEnableIHttpClientFactoryIntegration(serviceProvider!, "OtlpTraceExporter"); - BaseExporter otlpExporter = new OtlpTraceExporter(exporterOptions, sdkLimitOptions, experimentalOptions); + BaseExporter otlpExporter = new OtlpTraceExporter(exporterOptions!, sdkLimitOptions!, experimentalOptions!); if (configureExporterInstance != null) { diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/DirectorySizeTracker.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/DirectorySizeTracker.cs index a60715d185..2bbc352817 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/DirectorySizeTracker.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/DirectorySizeTracker.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.PersistentStorage.FileSystem; /// diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlob.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlob.cs index 337c322624..cfb1c395d0 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlob.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlob.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Internal; using OpenTelemetry.PersistentStorage.Abstractions; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlobProvider.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlobProvider.cs index c9afdf34ce..4d79046995 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlobProvider.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/FileBlobProvider.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics.CodeAnalysis; using System.Timers; using OpenTelemetry.Internal; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentBlob.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentBlob.cs index 2f0912feb7..aca1491ba2 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentBlob.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentBlob.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics.CodeAnalysis; namespace OpenTelemetry.PersistentStorage.Abstractions; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentBlobProvider.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentBlobProvider.cs index 5d4895f8d4..29d3dee8a0 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentBlobProvider.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentBlobProvider.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics.CodeAnalysis; namespace OpenTelemetry.PersistentStorage.Abstractions; diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentStorageHelper.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentStorageHelper.cs index 538597dc18..63005646fc 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentStorageHelper.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/PersistentStorage/PersistentStorageHelper.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Globalization; using System.Runtime.CompilerServices; diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Shipped.txt b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Shipped.txt index e69de29bb2..7dc5c58110 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt index 99d1e9e493..92eaa1e411 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt @@ -4,18 +4,18 @@ OpenTelemetry.Exporter.PrometheusAspNetCoreOptions OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.DisableTotalNameSuffixForCounters.get -> bool OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.DisableTotalNameSuffixForCounters.set -> void OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.PrometheusAspNetCoreOptions() -> void -OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeEndpointPath.get -> string +OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeEndpointPath.get -> string? OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeEndpointPath.set -> void OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeResponseCacheDurationMilliseconds.get -> int OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeResponseCacheDurationMilliseconds.set -> void OpenTelemetry.Metrics.PrometheusExporterMeterProviderBuilderExtensions -static Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions.UseOpenTelemetryPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) -> Microsoft.AspNetCore.Builder.IApplicationBuilder -static Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions.UseOpenTelemetryPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, OpenTelemetry.Metrics.MeterProvider meterProvider, System.Func predicate, string path, System.Action configureBranchedPipeline, string optionsName) -> Microsoft.AspNetCore.Builder.IApplicationBuilder -static Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions.UseOpenTelemetryPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, string path) -> Microsoft.AspNetCore.Builder.IApplicationBuilder -static Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions.UseOpenTelemetryPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, System.Func predicate) -> Microsoft.AspNetCore.Builder.IApplicationBuilder -static Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions.MapPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder -static Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions.MapPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string path) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder -static Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions.MapPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string path, OpenTelemetry.Metrics.MeterProvider meterProvider, System.Action configureBranchedPipeline, string optionsName) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder -static OpenTelemetry.Metrics.PrometheusExporterMeterProviderBuilderExtensions.AddPrometheusExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.PrometheusExporterMeterProviderBuilderExtensions.AddPrometheusExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, string name, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.PrometheusExporterMeterProviderBuilderExtensions.AddPrometheusExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder +static Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions.UseOpenTelemetryPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! +static Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions.UseOpenTelemetryPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app, OpenTelemetry.Metrics.MeterProvider? meterProvider, System.Func? predicate, string? path, System.Action? configureBranchedPipeline, string? optionsName) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! +static Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions.UseOpenTelemetryPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app, string! path) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! +static Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions.UseOpenTelemetryPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app, System.Func! predicate) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! +static Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions.MapPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder! +static Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions.MapPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! path) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder! +static Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions.MapPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string? path, OpenTelemetry.Metrics.MeterProvider? meterProvider, System.Action? configureBranchedPipeline, string? optionsName) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder! +static OpenTelemetry.Metrics.PrometheusExporterMeterProviderBuilderExtensions.AddPrometheusExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.PrometheusExporterMeterProviderBuilderExtensions.AddPrometheusExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, string? name, System.Action? configure) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.PrometheusExporterMeterProviderBuilderExtensions.AddPrometheusExporter(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Action! configure) -> OpenTelemetry.Metrics.MeterProviderBuilder! diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md index 703074b0be..33cf84f17d 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md @@ -1,7 +1,34 @@ # Changelog +This file contains individual changes for the +OpenTelemetry.Exporter.Prometheus.AspNetCore package. For highlights and +announcements covering all components see: [Release +Notes](../../RELEASENOTES.md). + ## Unreleased +* Added meter-level tags to Prometheus exporter + ([#5837](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5837)) + +## 1.9.0-beta.2 + +Released 2024-Jun-24 + +* Fixed a bug which lead to empty responses when the internal buffer is resized + processing a collection request + ([#5676](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5676)) + +## 1.9.0-beta.1 + +Released 2024-Jun-14 + +## 1.9.0-alpha.2 + +Released 2024-May-29 + +* Fixed issue with OpenMetrics suffixes for Prometheus + ([#5646](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5646)) + ## 1.9.0-alpha.1 Released 2024-May-20 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 a143d39ac1..f6a49fe6a6 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj @@ -7,9 +7,6 @@ $(PackageTags);prometheus;metrics coreunstable- $(DefineConstants);PROMETHEUS_ASPNETCORE - - - disable @@ -25,7 +22,6 @@ - @@ -36,6 +32,8 @@ + + diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs index 75acb56517..ed8186ec88 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs @@ -15,7 +15,7 @@ public class PrometheusAspNetCoreOptions /// /// Gets or sets the path to use for the scraping endpoint. Default value: "/metrics". /// - public string ScrapeEndpointPath { get; set; } = DefaultScrapeEndpointPath; + public string? ScrapeEndpointPath { get; set; } = DefaultScrapeEndpointPath; /// /// Gets or sets a value indicating whether addition of _total suffix for counter metric names is disabled. Default value: . diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterApplicationBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterApplicationBuilderExtensions.cs index 946f07d05a..38559e6b20 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterApplicationBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterApplicationBuilderExtensions.cs @@ -93,11 +93,11 @@ public static IApplicationBuilder UseOpenTelemetryPrometheusScrapingEndpoint(thi /// cref="IApplicationBuilder"/> for chaining calls. public static IApplicationBuilder UseOpenTelemetryPrometheusScrapingEndpoint( this IApplicationBuilder app, - MeterProvider meterProvider, - Func predicate, - string path, - Action configureBranchedPipeline, - string optionsName) + MeterProvider? meterProvider, + Func? predicate, + string? path, + Action? configureBranchedPipeline, + string? optionsName) { // Note: Order is important here. MeterProvider is accessed before // GetOptions so that any changes made to diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterEndpointRouteBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterEndpointRouteBuilderExtensions.cs index 4e70f2f76c..45639b378f 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterEndpointRouteBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterEndpointRouteBuilderExtensions.cs @@ -64,10 +64,10 @@ public static IEndpointConventionBuilder MapPrometheusScrapingEndpoint(this IEnd /// A convention routes for the Prometheus scraping endpoint. public static IEndpointConventionBuilder MapPrometheusScrapingEndpoint( this IEndpointRouteBuilder endpoints, - string path, - MeterProvider meterProvider, - Action configureBranchedPipeline, - string optionsName) + string? path, + MeterProvider? meterProvider, + Action? configureBranchedPipeline, + string? optionsName) { var builder = endpoints.CreateApplicationBuilder(); diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs index 6a075fc1b9..f73455d3b0 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs @@ -37,13 +37,13 @@ public static MeterProviderBuilder AddPrometheusExporter( /// Adds to the . /// /// builder to use. - /// Name which is used when retrieving options. - /// Callback action for configuring . + /// Optional name which is used when retrieving options. + /// Optional callback action for configuring . /// The instance of to chain the calls. public static MeterProviderBuilder AddPrometheusExporter( this MeterProviderBuilder builder, - string name, - Action configure) + string? name, + Action? configure) { Guard.ThrowIfNull(builder); diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs index fdfcd2b711..f91a93f66b 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs @@ -25,8 +25,9 @@ internal sealed class PrometheusExporterMiddleware public PrometheusExporterMiddleware(MeterProvider meterProvider, RequestDelegate next) { Guard.ThrowIfNull(meterProvider); + Guard.ThrowIfNull(next); - if (!meterProvider.TryFindExporter(out PrometheusExporter exporter)) + if (!meterProvider.TryFindExporter(out PrometheusExporter? exporter)) { throw new ArgumentException("A PrometheusExporter could not be found configured on the provided MeterProvider."); } @@ -36,6 +37,8 @@ public PrometheusExporterMiddleware(MeterProvider meterProvider, RequestDelegate internal PrometheusExporterMiddleware(PrometheusExporter exporter) { + Debug.Assert(exporter != null, "exporter was null"); + this.exporter = exporter; } @@ -62,7 +65,7 @@ public async Task InvokeAsync(HttpContext httpContext) if (dataView.Count > 0) { response.StatusCode = 200; -#if NET8_0_OR_GREATER +#if NET response.Headers.Append("Last-Modified", collectionResponse.GeneratedAtUtc.ToString("R")); #else response.Headers.Add("Last-Modified", collectionResponse.GeneratedAtUtc.ToString("R")); @@ -71,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!, 0, dataView.Count).ConfigureAwait(false); } else { @@ -93,8 +96,6 @@ public async Task InvokeAsync(HttpContext httpContext) response.StatusCode = 500; } } - - this.exporter.OnExport = null; } private static bool AcceptsOpenMetrics(HttpRequest request) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Shipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Shipped.txt index e69de29bb2..7dc5c58110 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt index d05f12424e..6caa1a77cb 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt @@ -1,12 +1,12 @@ OpenTelemetry.Exporter.PrometheusHttpListenerOptions OpenTelemetry.Exporter.PrometheusHttpListenerOptions.DisableTotalNameSuffixForCounters.get -> bool OpenTelemetry.Exporter.PrometheusHttpListenerOptions.DisableTotalNameSuffixForCounters.set -> void -OpenTelemetry.Exporter.PrometheusHttpListenerOptions.UriPrefixes.get -> System.Collections.Generic.IReadOnlyCollection +OpenTelemetry.Exporter.PrometheusHttpListenerOptions.UriPrefixes.get -> System.Collections.Generic.IReadOnlyCollection! OpenTelemetry.Exporter.PrometheusHttpListenerOptions.UriPrefixes.set -> void OpenTelemetry.Exporter.PrometheusHttpListenerOptions.PrometheusHttpListenerOptions() -> void -OpenTelemetry.Exporter.PrometheusHttpListenerOptions.ScrapeEndpointPath.get -> string +OpenTelemetry.Exporter.PrometheusHttpListenerOptions.ScrapeEndpointPath.get -> string? OpenTelemetry.Exporter.PrometheusHttpListenerOptions.ScrapeEndpointPath.set -> void OpenTelemetry.Metrics.PrometheusHttpListenerMeterProviderBuilderExtensions -static OpenTelemetry.Metrics.PrometheusHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder builder) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.PrometheusHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder builder, string name, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.PrometheusHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder +static OpenTelemetry.Metrics.PrometheusHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder! builder) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.PrometheusHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, string? name, System.Action? configure) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.PrometheusHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Action! configure) -> OpenTelemetry.Metrics.MeterProviderBuilder! diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md index abb4d7287f..5873577fb8 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md @@ -1,7 +1,34 @@ # Changelog +This file contains individual changes for the +OpenTelemetry.Exporter.Prometheus.HttpListener package. For highlights and +announcements covering all components see: [Release +Notes](../../RELEASENOTES.md). + ## Unreleased +* Added meter-level tags to Prometheus exporter + ([#5837](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5837)) + +## 1.9.0-beta.2 + +Released 2024-Jun-24 + +* Fixed a bug which lead to empty responses when the internal buffer is resized + processing a collection request + ([#5676](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5676)) + +## 1.9.0-beta.1 + +Released 2024-Jun-14 + +## 1.9.0-alpha.2 + +Released 2024-May-29 + +* Fixed issue with OpenMetrics suffixes for Prometheus + ([#5646](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5646)) + ## 1.9.0-alpha.1 Released 2024-May-20 diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs index b93812176f..1c74e36bcf 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Diagnostics; using System.Runtime.CompilerServices; using OpenTelemetry.Metrics; @@ -12,7 +13,7 @@ internal sealed class PrometheusCollectionManager private readonly PrometheusExporter exporter; private readonly int scrapeResponseCacheDurationMilliseconds; - private readonly Func, ExportResult> onCollectRef; + private readonly PrometheusExporter.ExportFunc onCollectRef; private readonly Dictionary metricsCache; private readonly HashSet scopes; private int metricsCacheCount; @@ -26,7 +27,7 @@ internal sealed class PrometheusCollectionManager private DateTime? previousOpenMetricsDataViewGeneratedAtUtc; private int readerCount; private bool collectionRunning; - private TaskCompletionSource collectionTcs; + private TaskCompletionSource? collectionTcs; public PrometheusCollectionManager(PrometheusExporter exporter) { @@ -37,7 +38,7 @@ public PrometheusCollectionManager(PrometheusExporter exporter) this.scopes = new HashSet(); } -#if NET6_0_OR_GREATER +#if NET public ValueTask EnterCollect(bool openMetricsRequested) #else public Task EnterCollect(bool openMetricsRequested) @@ -57,7 +58,7 @@ public Task EnterCollect(bool openMetricsRequested) { Interlocked.Increment(ref this.readerCount); this.ExitGlobalLock(); -#if NET6_0_OR_GREATER +#if NET return new ValueTask(new CollectionResponse(this.previousOpenMetricsDataView, this.previousPlainTextDataView, previousDataViewGeneratedAtUtc.Value, fromCache: true)); #else return Task.FromResult(new CollectionResponse(this.previousOpenMetricsDataView, this.previousPlainTextDataView, previousDataViewGeneratedAtUtc.Value, fromCache: true)); @@ -74,7 +75,7 @@ public Task EnterCollect(bool openMetricsRequested) Interlocked.Increment(ref this.readerCount); this.ExitGlobalLock(); -#if NET6_0_OR_GREATER +#if NET return new ValueTask(this.collectionTcs.Task); #else return this.collectionTcs.Task; @@ -115,7 +116,7 @@ public Task EnterCollect(bool openMetricsRequested) ? this.previousOpenMetricsDataViewGeneratedAtUtc : this.previousPlainTextDataViewGeneratedAtUtc; - response = new CollectionResponse(this.previousOpenMetricsDataView, this.previousPlainTextDataView, previousDataViewGeneratedAtUtc.Value, fromCache: false); + response = new CollectionResponse(this.previousOpenMetricsDataView, this.previousPlainTextDataView, previousDataViewGeneratedAtUtc!.Value, fromCache: false); } else { @@ -134,7 +135,7 @@ public Task EnterCollect(bool openMetricsRequested) this.ExitGlobalLock(); -#if NET6_0_OR_GREATER +#if NET return new ValueTask(response); #else return Task.FromResult(response); @@ -188,23 +189,25 @@ private void WaitForReadersToComplete() [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool ExecuteCollect(bool openMetricsRequested) { + Debug.Assert(this.exporter.Collect != null, "this.exporter.Collect was null"); + this.exporter.OnExport = this.onCollectRef; this.exporter.OpenMetricsRequested = openMetricsRequested; - var result = this.exporter.Collect(Timeout.Infinite); + var result = this.exporter.Collect!(Timeout.Infinite); this.exporter.OnExport = null; return result; } - private ExportResult OnCollect(Batch metrics) + private ExportResult OnCollect(in Batch metrics) { var cursor = 0; - var buffer = this.exporter.OpenMetricsRequested ? this.openMetricsBuffer : this.plainTextBuffer; + ref byte[] buffer = ref (this.exporter.OpenMetricsRequested ? ref this.openMetricsBuffer : ref this.plainTextBuffer); try { if (this.exporter.OpenMetricsRequested) { - cursor = this.WriteTargetInfo(); + cursor = this.WriteTargetInfo(ref buffer); this.scopes.Clear(); @@ -291,11 +294,11 @@ private ExportResult OnCollect(Batch metrics) if (this.exporter.OpenMetricsRequested) { - this.previousOpenMetricsDataView = new ArraySegment(this.openMetricsBuffer, 0, cursor); + this.previousOpenMetricsDataView = new ArraySegment(buffer, 0, cursor); } else { - this.previousPlainTextDataView = new ArraySegment(this.plainTextBuffer, 0, cursor); + this.previousPlainTextDataView = new ArraySegment(buffer, 0, cursor); } return ExportResult.Success; @@ -315,7 +318,7 @@ private ExportResult OnCollect(Batch metrics) } } - private int WriteTargetInfo() + private int WriteTargetInfo(ref byte[] buffer) { if (this.targetInfoBufferLength < 0) { @@ -323,13 +326,13 @@ private int WriteTargetInfo() { try { - this.targetInfoBufferLength = PrometheusSerializer.WriteTargetInfo(this.openMetricsBuffer, 0, this.exporter.Resource); + this.targetInfoBufferLength = PrometheusSerializer.WriteTargetInfo(buffer, 0, this.exporter.Resource); break; } catch (IndexOutOfRangeException) { - if (!this.IncreaseBufferSize(ref this.openMetricsBuffer)) + if (!this.IncreaseBufferSize(ref buffer)) { throw; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs index 292b0aa7c3..c5b0e0e64d 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Diagnostics; using OpenTelemetry.Internal; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; @@ -13,9 +14,9 @@ namespace OpenTelemetry.Exporter.Prometheus; [ExportModes(ExportModes.Pull)] internal sealed class PrometheusExporter : BaseExporter, IPullMetricExporter { - private Func funcCollect; - private Func, ExportResult> funcExport; - private Resource resource; + private Func? funcCollect; + private ExportFunc? funcExport; + private Resource? resource; private bool disposed; /// @@ -32,22 +33,24 @@ public PrometheusExporter(PrometheusExporterOptions options) this.CollectionManager = new PrometheusCollectionManager(this); } + public delegate ExportResult ExportFunc(in Batch batch); + /// /// Gets or sets the Collect delegate. /// - public Func Collect + public Func? Collect { get => this.funcCollect; set => this.funcCollect = value; } - internal Func, ExportResult> OnExport + internal ExportFunc? OnExport { get => this.funcExport; set => this.funcExport = value; } - internal Action OnDispose { get; set; } + internal Action? OnDispose { get; set; } internal PrometheusCollectionManager CollectionManager { get; } @@ -62,7 +65,9 @@ internal Func, ExportResult> OnExport /// public override ExportResult Export(in Batch metrics) { - return this.OnExport(metrics); + Debug.Assert(this.OnExport != null, "this.OnExport was null"); + + return this.OnExport!(in metrics); } /// diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusHeadersParser.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusHeadersParser.cs index 81576b723d..2c2c8f5b7d 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusHeadersParser.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusHeadersParser.cs @@ -7,7 +7,7 @@ internal static class PrometheusHeadersParser { private const string OpenMetricsMediaType = "application/openmetrics-text"; - internal static bool AcceptsOpenMetrics(string contentType) + internal static bool AcceptsOpenMetrics(string? contentType) { var value = contentType.AsSpan(); diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusMetric.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusMetric.cs index a39a426136..800963454a 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusMetric.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusMetric.cs @@ -1,6 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Text; using OpenTelemetry.Metrics; @@ -27,47 +29,60 @@ public PrometheusMetric(string name, string unit, PrometheusType type, bool disa // consecutive `_` characters MUST be replaced with a single `_` character. // https://github.com/open-telemetry/opentelemetry-specification/blob/b2f923fb1650dde1f061507908b834035506a796/specification/compatibility/prometheus_and_openmetrics.md#L230-L233 var sanitizedName = SanitizeMetricName(name); + var openMetricsName = SanitizeOpenMetricsName(sanitizedName); - string sanitizedUnit = null; + string? sanitizedUnit = null; if (!string.IsNullOrEmpty(unit)) { sanitizedUnit = GetUnit(unit); // The resulting unit SHOULD be added to the metric as // [OpenMetrics UNIT metadata](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#metricfamily) - // and as a suffix to the metric name unless the metric name already contains the - // unit, or the unit MUST be omitted. The unit suffix comes before any - // type-specific suffixes. - // https://github.com/open-telemetry/opentelemetry-specification/blob/b2f923fb1650dde1f061507908b834035506a796/specification/compatibility/prometheus_and_openmetrics.md#L242-L246 - if (!sanitizedName.Contains(sanitizedUnit)) + // 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)) { - sanitizedName = sanitizedName + "_" + sanitizedUnit; + sanitizedName += $"_{sanitizedUnit}"; + openMetricsName += $"_{sanitizedUnit}"; } } // If the metric name for monotonic Sum metric points does not end in a suffix of `_total` a suffix of `_total` MUST be added by default, otherwise the name MUST remain unchanged. // 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) { sanitizedName += "_total"; } - // Special case: Converting "1" to "ratio". - // https://github.com/open-telemetry/opentelemetry-specification/blob/b2f923fb1650dde1f061507908b834035506a796/specification/compatibility/prometheus_and_openmetrics.md#L239 - if (type == PrometheusType.Gauge && unit == "1" && !sanitizedName.Contains("ratio")) + // 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/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#counter-1 + if (type == PrometheusType.Counter && !openMetricsName.EndsWith("_total")) { - sanitizedName += "_ratio"; + openMetricsName += "_total"; } + // In OpenMetrics format, the UNIT, TYPE and HELP metadata must be suffixed with the unit (handled above), and not the '_total' suffix, as in the case for counters. + // https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#unit + var openMetricsMetadataName = type == PrometheusType.Counter + ? SanitizeOpenMetricsName(openMetricsName) + : sanitizedName; + this.Name = sanitizedName; + this.OpenMetricsName = openMetricsName; + this.OpenMetricsMetadataName = openMetricsMetadataName; this.Unit = sanitizedUnit; this.Type = type; } public string Name { get; } - public string Unit { get; } + public string OpenMetricsName { get; } + + public string OpenMetricsMetadataName { get; } + + public string? Unit { get; } public PrometheusType Type { get; } @@ -78,7 +93,7 @@ public static PrometheusMetric Create(Metric metric, bool disableTotalNameSuffix internal static string SanitizeMetricName(string metricName) { - StringBuilder sb = null; + StringBuilder? sb = null; var lastCharUnderscore = false; for (var i = 0; i < metricName.Length; i++) @@ -121,7 +136,7 @@ internal static string RemoveAnnotations(string unit) // https://ucum.org/ucum#section-Character-Set-and-Lexical-Rules // What should happen if they are nested isn't defined. // Right now the remove annotations code doesn't attempt to balance multiple start and end braces. - StringBuilder sb = null; + StringBuilder? sb = null; var hasOpenBrace = false; var startOpenBraceIndex = 0; @@ -155,10 +170,23 @@ internal static string RemoveAnnotations(string unit) return unit; } - sb.Append(unit, lastWriteIndex, unit.Length - lastWriteIndex); + Debug.Assert(sb != null, "sb was null"); + + sb!.Append(unit, lastWriteIndex, unit.Length - lastWriteIndex); + return sb.ToString(); } + private static string SanitizeOpenMetricsName(string metricName) + { + if (metricName.EndsWith("_total")) + { + return metricName.Substring(0, metricName.Length - 6); + } + + return metricName; + } + private static string GetUnit(string unit) { // Dropping the portions of the Unit within brackets (e.g. {packet}). Brackets MUST NOT be included in the resulting unit. A "count of foo" is considered unitless in Prometheus. @@ -181,7 +209,7 @@ private static string GetUnit(string unit) return updatedUnit; } - private static bool TryProcessRateUnits(string updatedUnit, out string updatedPerUnit) + private static bool TryProcessRateUnits(string updatedUnit, [NotNullWhen(true)] out string? updatedPerUnit) { updatedPerUnit = null; diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs index 719f21a0c6..b182b52150 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs @@ -28,7 +28,7 @@ public static int WriteDouble(byte[] buffer, int cursor, double value) { if (MathHelper.IsFinite(value)) { -#if NET6_0_OR_GREATER +#if NET Span span = stackalloc char[128]; var result = value.TryFormat(span, out var cchWritten, "G", CultureInfo.InvariantCulture); @@ -62,7 +62,7 @@ public static int WriteDouble(byte[] buffer, int cursor, double value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int WriteLong(byte[] buffer, int cursor, long value) { -#if NET6_0_OR_GREATER +#if NET Span span = stackalloc char[20]; var result = value.TryFormat(span, out var cchWritten, "G", CultureInfo.InvariantCulture); @@ -177,7 +177,7 @@ public static int WriteLabelValue(byte[] buffer, int cursor, string value) { Debug.Assert(value != null, $"{nameof(value)} should not be null."); - for (int i = 0; i < value.Length; i++) + for (int i = 0; i < value!.Length; i++) { var ordinal = (ushort)value[i]; switch (ordinal) @@ -204,7 +204,7 @@ public static int WriteLabelValue(byte[] buffer, int cursor, string value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteLabel(byte[] buffer, int cursor, string labelKey, object labelValue) + public static int WriteLabel(byte[] buffer, int cursor, string labelKey, object? labelValue) { cursor = WriteLabelKey(buffer, cursor, labelKey); buffer[cursor++] = unchecked((byte)'='); @@ -216,7 +216,7 @@ public static int WriteLabel(byte[] buffer, int cursor, string labelKey, object return cursor; - static string GetLabelValueString(object labelValue) + static string GetLabelValueString(object? labelValue) { // TODO: Attribute values should be written as their JSON representation. Extra logic may need to be added here to correctly convert other .NET types. // More detail: https://github.com/open-telemetry/opentelemetry-dotnet/issues/4822#issuecomment-1707328495 @@ -230,12 +230,33 @@ static string GetLabelValueString(object labelValue) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteMetricName(byte[] buffer, int cursor, PrometheusMetric metric) + public static int WriteMetricName(byte[] buffer, int cursor, PrometheusMetric metric, bool openMetricsRequested) + { + // Metric name has already been escaped. + var name = openMetricsRequested ? metric.OpenMetricsName : metric.Name; + + Debug.Assert(!string.IsNullOrWhiteSpace(name), "name was null or whitespace"); + + for (int i = 0; i < name.Length; i++) + { + var ordinal = (ushort)name[i]; + buffer[cursor++] = unchecked((byte)ordinal); + } + + return cursor; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteMetricMetadataName(byte[] buffer, int cursor, PrometheusMetric metric, bool openMetricsRequested) { // Metric name has already been escaped. - for (int i = 0; i < metric.Name.Length; i++) + var name = openMetricsRequested ? metric.OpenMetricsMetadataName : metric.Name; + + Debug.Assert(!string.IsNullOrWhiteSpace(name), "name was null or whitespace"); + + for (int i = 0; i < name.Length; i++) { - var ordinal = (ushort)metric.Name[i]; + var ordinal = (ushort)name[i]; buffer[cursor++] = unchecked((byte)ordinal); } @@ -252,7 +273,7 @@ public static int WriteEof(byte[] buffer, int cursor) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteHelpMetadata(byte[] buffer, int cursor, PrometheusMetric metric, string metricDescription) + public static int WriteHelpMetadata(byte[] buffer, int cursor, PrometheusMetric metric, string metricDescription, bool openMetricsRequested) { if (string.IsNullOrEmpty(metricDescription)) { @@ -260,7 +281,7 @@ public static int WriteHelpMetadata(byte[] buffer, int cursor, PrometheusMetric } cursor = WriteAsciiStringNoEscape(buffer, cursor, "# HELP "); - cursor = WriteMetricName(buffer, cursor, metric); + cursor = WriteMetricMetadataName(buffer, cursor, metric, openMetricsRequested); if (!string.IsNullOrEmpty(metricDescription)) { @@ -274,14 +295,14 @@ public static int WriteHelpMetadata(byte[] buffer, int cursor, PrometheusMetric } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteTypeMetadata(byte[] buffer, int cursor, PrometheusMetric metric) + public static int WriteTypeMetadata(byte[] buffer, int cursor, PrometheusMetric metric, bool openMetricsRequested) { var metricType = MapPrometheusType(metric.Type); Debug.Assert(!string.IsNullOrEmpty(metricType), $"{nameof(metricType)} should not be null or empty."); cursor = WriteAsciiStringNoEscape(buffer, cursor, "# TYPE "); - cursor = WriteMetricName(buffer, cursor, metric); + cursor = WriteMetricMetadataName(buffer, cursor, metric, openMetricsRequested); buffer[cursor++] = unchecked((byte)' '); cursor = WriteAsciiStringNoEscape(buffer, cursor, metricType); @@ -291,7 +312,7 @@ public static int WriteTypeMetadata(byte[] buffer, int cursor, PrometheusMetric } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteUnitMetadata(byte[] buffer, int cursor, PrometheusMetric metric) + public static int WriteUnitMetadata(byte[] buffer, int cursor, PrometheusMetric metric, bool openMetricsRequested) { if (string.IsNullOrEmpty(metric.Unit)) { @@ -299,12 +320,12 @@ public static int WriteUnitMetadata(byte[] buffer, int cursor, PrometheusMetric } cursor = WriteAsciiStringNoEscape(buffer, cursor, "# UNIT "); - cursor = WriteMetricName(buffer, cursor, metric); + cursor = WriteMetricMetadataName(buffer, cursor, metric, openMetricsRequested); buffer[cursor++] = unchecked((byte)' '); // Unit name has already been escaped. - for (int i = 0; i < metric.Unit.Length; i++) + for (int i = 0; i < metric.Unit!.Length; i++) { var ordinal = (ushort)metric.Unit[i]; buffer[cursor++] = unchecked((byte)ordinal); @@ -383,6 +404,15 @@ public static int WriteTags(byte[] buffer, int cursor, Metric metric, ReadOnlyTa buffer[cursor++] = unchecked((byte)','); } + if (metric.MeterTags != null) + { + foreach (var tag in metric.MeterTags) + { + cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); + buffer[cursor++] = unchecked((byte)','); + } + } + foreach (var tag in tags) { cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index 1523ef7c16..70d67f468b 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -24,9 +24,9 @@ public static bool CanWriteMetric(Metric metric) public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric, bool openMetricsRequested = false) { - cursor = WriteTypeMetadata(buffer, cursor, prometheusMetric); - cursor = WriteUnitMetadata(buffer, cursor, prometheusMetric); - cursor = WriteHelpMetadata(buffer, cursor, prometheusMetric, metric.Description); + cursor = WriteTypeMetadata(buffer, cursor, prometheusMetric, openMetricsRequested); + cursor = WriteUnitMetadata(buffer, cursor, prometheusMetric, openMetricsRequested); + cursor = WriteHelpMetadata(buffer, cursor, prometheusMetric, metric.Description, openMetricsRequested); if (!metric.MetricType.IsHistogram()) { @@ -35,7 +35,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe var timestamp = metricPoint.EndTime.ToUnixTimeMilliseconds(); // Counter and Gauge - cursor = WriteMetricName(buffer, cursor, prometheusMetric); + cursor = WriteMetricName(buffer, cursor, prometheusMetric, openMetricsRequested); cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags); buffer[cursor++] = unchecked((byte)' '); @@ -85,7 +85,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe { totalCount += histogramMeasurement.BucketCount; - cursor = WriteMetricName(buffer, cursor, prometheusMetric); + cursor = WriteMetricName(buffer, cursor, prometheusMetric, openMetricsRequested); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_bucket{"); cursor = WriteTags(buffer, cursor, metric, tags, writeEnclosingBraces: false); @@ -111,7 +111,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe } // Histogram sum - cursor = WriteMetricName(buffer, cursor, prometheusMetric); + cursor = WriteMetricName(buffer, cursor, prometheusMetric, openMetricsRequested); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_sum"); cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags); @@ -125,7 +125,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe buffer[cursor++] = ASCII_LINEFEED; // Histogram count - cursor = WriteMetricName(buffer, cursor, prometheusMetric); + cursor = WriteMetricName(buffer, cursor, prometheusMetric, openMetricsRequested); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_count"); cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags); 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 3766816a7e..4e087919be 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj @@ -5,9 +5,6 @@ Stand-alone HttpListener for hosting OpenTelemetry .NET Prometheus Exporter $(PackageTags);prometheus;metrics coreunstable- - - - disable @@ -19,9 +16,11 @@ + - + + diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs index 8576873f52..cecda73f7c 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs @@ -11,10 +11,10 @@ internal sealed class PrometheusHttpListener : IDisposable { private readonly PrometheusExporter exporter; private readonly HttpListener httpListener = new(); - private readonly object syncObject = new(); + private readonly Lock syncObject = new(); - private CancellationTokenSource tokenSource; - private Task workerThread; + private CancellationTokenSource? tokenSource; + private Task? workerThread; /// /// Initializes a new instance of the class. @@ -28,7 +28,7 @@ public PrometheusHttpListener(PrometheusExporter exporter, PrometheusHttpListene this.exporter = exporter; - string path = options.ScrapeEndpointPath; + string path = options.ScrapeEndpointPath ?? PrometheusHttpListenerOptions.DefaultScrapeEndpointPath; if (!path.StartsWith("/")) { @@ -83,7 +83,7 @@ public void Stop() } this.tokenSource.Cancel(); - this.workerThread.Wait(); + this.workerThread!.Wait(); this.tokenSource = null; } } @@ -116,7 +116,7 @@ private void WorkerProc() try { using var scope = SuppressInstrumentationScope.Begin(); - while (!this.tokenSource.IsCancellationRequested) + while (!this.tokenSource!.IsCancellationRequested) { var ctxTask = this.httpListener.GetContextAsync(); ctxTask.Wait(this.tokenSource.Token); @@ -164,7 +164,7 @@ 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); + await context.Response.OutputStream.WriteAsync(dataView.Array!, 0, dataView.Count).ConfigureAwait(false); } else { diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs index 929774a11f..7289432cdc 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs @@ -37,13 +37,13 @@ public static MeterProviderBuilder AddPrometheusHttpListener( /// Adds PrometheusHttpListener to MeterProviderBuilder. /// /// builder to use. - /// Name which is used when retrieving options. - /// Callback action for configuring . + /// Optional name which is used when retrieving options. + /// Optional callback action for configuring . /// The instance of to chain calls. public static MeterProviderBuilder AddPrometheusHttpListener( this MeterProviderBuilder builder, - string name, - Action configure) + string? name, + Action? configure) { Guard.ThrowIfNull(builder); diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs index d0c6bd2edf..dbe20b726c 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs @@ -10,12 +10,14 @@ namespace OpenTelemetry.Exporter; /// public class PrometheusHttpListenerOptions { + internal const string DefaultScrapeEndpointPath = "/metrics"; + private IReadOnlyCollection uriPrefixes = new[] { "http://localhost:9464/" }; /// /// Gets or sets the path to use for the scraping endpoint. Default value: "/metrics". /// - public string ScrapeEndpointPath { get; set; } = "/metrics"; + public string? ScrapeEndpointPath { get; set; } = DefaultScrapeEndpointPath; /// /// Gets or sets a value indicating whether addition of _total suffix for counter metric names is disabled. Default value: . diff --git a/src/OpenTelemetry.Exporter.Zipkin/.publicApi/PublicAPI.Shipped.txt b/src/OpenTelemetry.Exporter.Zipkin/.publicApi/PublicAPI.Shipped.txt index 9e2e613e19..2e1bf086dc 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/.publicApi/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry.Exporter.Zipkin/.publicApi/PublicAPI.Shipped.txt @@ -1,13 +1,14 @@ +#nullable enable OpenTelemetry.Exporter.ZipkinExporter -OpenTelemetry.Exporter.ZipkinExporter.ZipkinExporter(OpenTelemetry.Exporter.ZipkinExporterOptions options, System.Net.Http.HttpClient client = null) -> void +OpenTelemetry.Exporter.ZipkinExporter.ZipkinExporter(OpenTelemetry.Exporter.ZipkinExporterOptions! options, System.Net.Http.HttpClient? client = null) -> void OpenTelemetry.Exporter.ZipkinExporterOptions -OpenTelemetry.Exporter.ZipkinExporterOptions.BatchExportProcessorOptions.get -> OpenTelemetry.BatchExportProcessorOptions OpenTelemetry.Exporter.ZipkinExporterOptions.BatchExportProcessorOptions.set -> void -OpenTelemetry.Exporter.ZipkinExporterOptions.Endpoint.get -> System.Uri +OpenTelemetry.Exporter.ZipkinExporterOptions.BatchExportProcessorOptions.get -> OpenTelemetry.BatchExportProcessorOptions! +OpenTelemetry.Exporter.ZipkinExporterOptions.HttpClientFactory.get -> System.Func! +OpenTelemetry.Exporter.ZipkinExporterOptions.Endpoint.get -> System.Uri! OpenTelemetry.Exporter.ZipkinExporterOptions.Endpoint.set -> void OpenTelemetry.Exporter.ZipkinExporterOptions.ExportProcessorType.get -> OpenTelemetry.ExportProcessorType OpenTelemetry.Exporter.ZipkinExporterOptions.ExportProcessorType.set -> void -OpenTelemetry.Exporter.ZipkinExporterOptions.HttpClientFactory.get -> System.Func OpenTelemetry.Exporter.ZipkinExporterOptions.HttpClientFactory.set -> void OpenTelemetry.Exporter.ZipkinExporterOptions.MaxPayloadSizeInBytes.get -> int? OpenTelemetry.Exporter.ZipkinExporterOptions.MaxPayloadSizeInBytes.set -> void @@ -15,7 +16,7 @@ OpenTelemetry.Exporter.ZipkinExporterOptions.UseShortTraceIds.get -> bool OpenTelemetry.Exporter.ZipkinExporterOptions.UseShortTraceIds.set -> void OpenTelemetry.Exporter.ZipkinExporterOptions.ZipkinExporterOptions() -> void OpenTelemetry.Trace.ZipkinExporterHelperExtensions -override OpenTelemetry.Exporter.ZipkinExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult -static OpenTelemetry.Trace.ZipkinExporterHelperExtensions.AddZipkinExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder) -> OpenTelemetry.Trace.TracerProviderBuilder -static OpenTelemetry.Trace.ZipkinExporterHelperExtensions.AddZipkinExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder, string name, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder -static OpenTelemetry.Trace.ZipkinExporterHelperExtensions.AddZipkinExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder +override OpenTelemetry.Exporter.ZipkinExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult +static OpenTelemetry.Trace.ZipkinExporterHelperExtensions.AddZipkinExporter(this OpenTelemetry.Trace.TracerProviderBuilder! builder) -> OpenTelemetry.Trace.TracerProviderBuilder! +static OpenTelemetry.Trace.ZipkinExporterHelperExtensions.AddZipkinExporter(this OpenTelemetry.Trace.TracerProviderBuilder! builder, string? name, System.Action? configure) -> OpenTelemetry.Trace.TracerProviderBuilder! +static OpenTelemetry.Trace.ZipkinExporterHelperExtensions.AddZipkinExporter(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action! configure) -> OpenTelemetry.Trace.TracerProviderBuilder! diff --git a/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md b/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md index 15dcc2f339..617098116d 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md @@ -1,7 +1,38 @@ # Changelog +This file contains individual changes for the OpenTelemetry.Exporter.Zipkin +package. For highlights and announcements covering all components see: [Release +Notes](../../RELEASENOTES.md). + ## Unreleased +* Added direct reference to `System.Text.Json` for the `net8.0` target with + minimum version of `8.0.5` in response to + [CVE-2024-30105](https://github.com/advisories/GHSA-hh2w-p6rv-4g7w) & + [CVE-2024-43485](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-43485). + ([#5874](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5874), + [#5891](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5891)) + +## 1.10.0-beta.1 + +Released 2024-Sep-30 + +* **Breaking change**: Non-primitive tag values converted using + `Convert.ToString` will now format using `CultureInfo.InvariantCulture`. + ([#5700](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5700)) + +* Fixed `PlatformNotSupportedException`s being thrown during export when running + on mobile platforms which caused telemetry to be dropped silently. + ([#5821](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/5821)) + +## 1.9.0 + +Released 2024-Jun-14 + +## 1.9.0-rc.1 + +Released 2024-Jun-07 + ## 1.9.0-alpha.1 Released 2024-May-20 diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs index 802591d052..2753f51f50 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs @@ -21,13 +21,13 @@ internal static ZipkinSpan ToZipkinSpan(this Activity activity, ZipkinEndpoint l { var context = activity.Context; - string parentId = activity.ParentSpanId == default ? + string? parentId = activity.ParentSpanId == default ? null : EncodeSpanId(activity.ParentSpanId); var tagState = new TagEnumerationState { - Tags = PooledList>.Create(), + Tags = PooledList>.Create(), }; tagState.EnumerateTags(activity); @@ -38,9 +38,9 @@ internal static ZipkinSpan ToZipkinSpan(this Activity activity, ZipkinEndpoint l { if (activity.Status == ActivityStatusCode.Ok) { - PooledList>.Add( + PooledList>.Add( ref tagState.Tags, - new KeyValuePair( + new KeyValuePair( SpanAttributeConstants.StatusCodeKey, "OK")); } @@ -48,16 +48,16 @@ internal static ZipkinSpan ToZipkinSpan(this Activity activity, ZipkinEndpoint l // activity.Status is Error else { - PooledList>.Add( + PooledList>.Add( ref tagState.Tags, - new KeyValuePair( + 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( + PooledList>.Add( ref tagState.Tags, - new KeyValuePair( + new KeyValuePair( ZipkinErrorFlagTagName, activity.StatusDescription ?? string.Empty)); } @@ -67,18 +67,18 @@ internal static ZipkinSpan ToZipkinSpan(this Activity activity, ZipkinEndpoint l // activity status takes precedence over status tag. else if (tagState.StatusCode.HasValue && tagState.StatusCode != StatusCode.Unset) { - PooledList>.Add( + PooledList>.Add( ref tagState.Tags, - new KeyValuePair( + 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( + PooledList>.Add( ref tagState.Tags, - new KeyValuePair( + new KeyValuePair( ZipkinErrorFlagTagName, tagState.StatusDescription ?? string.Empty)); } @@ -87,30 +87,30 @@ internal static ZipkinSpan ToZipkinSpan(this Activity activity, ZipkinEndpoint l var activitySource = activity.Source; if (!string.IsNullOrEmpty(activitySource.Name)) { - PooledList>.Add(ref tagState.Tags, new KeyValuePair("otel.scope.name", 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)); + 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)); + 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)); + PooledList>.Add(ref tagState.Tags, new KeyValuePair("otel.library.version", activitySource.Version)); } } - ZipkinEndpoint remoteEndpoint = null; + ZipkinEndpoint? remoteEndpoint = null; if (activity.Kind == ActivityKind.Client || activity.Kind == ActivityKind.Producer) { - PeerServiceResolver.Resolve(ref tagState, out string peerServiceName, out bool addAsTag); + 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)); + PooledList>.Add(ref tagState.Tags, new KeyValuePair(SemanticConventions.AttributePeerService, peerServiceName)); } } } @@ -172,7 +172,7 @@ private static string EncodeTraceId(ActivityTraceId traceId, bool useShortTraceI return id; } - private static string ToActivityKind(Activity activity) + private static string? ToActivityKind(Activity activity) { return activity.Kind switch { @@ -186,15 +186,15 @@ private static string ToActivityKind(Activity activity) internal struct TagEnumerationState : PeerServiceResolver.IPeerServiceState { - public PooledList> Tags; + public PooledList> Tags; - public string PeerService { get; set; } + public string? PeerService { get; set; } public int? PeerServicePriority { get; set; } - public string HostName { get; set; } + public string? HostName { get; set; } - public string IpAddress { get; set; } + public string? IpAddress { get; set; } public long Port { get; set; } @@ -239,7 +239,7 @@ public void EnumerateTags(Activity activity) PeerServiceResolver.InspectTag(ref this, key, intVal); } - PooledList>.Add(ref this.Tags, tag); + PooledList>.Add(ref this.Tags, tag); } } } diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinEndpoint.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinEndpoint.cs index c6c9908b38..cec7cc7156 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinEndpoint.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinEndpoint.cs @@ -13,11 +13,11 @@ public ZipkinEndpoint(string serviceName) } public ZipkinEndpoint( - string serviceName, - string ipv4, - string ipv6, + string? serviceName, + string? ipv4, + string? ipv6, int? port, - Dictionary tags) + Dictionary? tags) { this.ServiceName = serviceName; this.Ipv4 = ipv4; @@ -26,15 +26,15 @@ public ZipkinEndpoint( this.Tags = tags; } - public string ServiceName { get; } + public string? ServiceName { get; } - public string Ipv4 { get; } + public string? Ipv4 { get; } - public string Ipv6 { get; } + public string? Ipv6 { get; } public int? Port { get; } - public Dictionary Tags { get; } + public Dictionary? Tags { get; } public static ZipkinEndpoint Create(string serviceName) { diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinSpan.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinSpan.cs index 8038492a0b..a6ea86f6ce 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinSpan.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinSpan.cs @@ -11,16 +11,16 @@ internal readonly struct ZipkinSpan { public ZipkinSpan( string traceId, - string parentId, + string? parentId, string id, - string kind, + string? kind, string name, long? timestamp, long? duration, ZipkinEndpoint localEndpoint, - ZipkinEndpoint remoteEndpoint, + ZipkinEndpoint? remoteEndpoint, in PooledList annotations, - in PooledList> tags, + in PooledList> tags, bool? debug, bool? shared) { @@ -44,11 +44,11 @@ public ZipkinSpan( public string TraceId { get; } - public string ParentId { get; } + public string? ParentId { get; } public string Id { get; } - public string Kind { get; } + public string? Kind { get; } public string Name { get; } @@ -58,11 +58,11 @@ public ZipkinSpan( public ZipkinEndpoint LocalEndpoint { get; } - public ZipkinEndpoint RemoteEndpoint { get; } + public ZipkinEndpoint? RemoteEndpoint { get; } public PooledList Annotations { get; } - public PooledList> Tags { get; } + public PooledList> Tags { get; } public bool? Debug { get; } @@ -148,7 +148,7 @@ public void Write(Utf8JsonWriter writer) writer.WriteEndArray(); } - if (!this.Tags.IsEmpty || this.LocalEndpoint.Tags != null) + if (!this.Tags.IsEmpty || this.LocalEndpoint!.Tags != null) { writer.WritePropertyName(ZipkinSpanJsonHelper.TagsPropertyName); writer.WriteStartObject(); @@ -161,7 +161,7 @@ public void Write(Utf8JsonWriter writer) try { - foreach (var tag in this.LocalEndpoint.Tags ?? Enumerable.Empty>()) + foreach (var tag in this.LocalEndpoint!.Tags! ?? Enumerable.Empty>()) { ZipkinTagWriter.Instance.TryWriteTag(ref writer, tag); } diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagWriter.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagWriter.cs index d40d126b92..30e1eb112e 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagWriter.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagWriter.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Buffers.Text; using System.Globalization; using System.Text.Json; diff --git a/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj b/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj index 3ca65ced4d..78d44fd3ee 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj +++ b/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj @@ -1,12 +1,11 @@ + $(TargetFrameworksForLibraries) Zipkin exporter for OpenTelemetry .NET $(PackageTags);Zipkin;distributed-tracing core- - - - disable + true @@ -30,11 +29,6 @@ - - - - - diff --git a/src/OpenTelemetry.Exporter.Zipkin/README.md b/src/OpenTelemetry.Exporter.Zipkin/README.md index aa277b7fdc..ededcf9b07 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/README.md +++ b/src/OpenTelemetry.Exporter.Zipkin/README.md @@ -15,7 +15,7 @@ dotnet add package OpenTelemetry.Exporter.Zipkin ## Enable/Add Zipkin as a tracing exporter -You can enable the the `ZipkinExporter` with the `AddZipkinExporter()` extension +You can enable the `ZipkinExporter` with the `AddZipkinExporter()` extension method on `TracerProviderBuilder`. ## Configuration diff --git a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporter.cs b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporter.cs index e7a9e0ebcc..c65f145428 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporter.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporter.cs @@ -24,22 +24,35 @@ public class ZipkinExporter : BaseExporter private readonly ZipkinExporterOptions options; private readonly int maxPayloadSizeInBytes; private readonly HttpClient httpClient; +#if NET + private readonly bool synchronousSendSupportedByCurrentPlatform; +#endif /// /// Initializes a new instance of the class. /// /// Configuration options. /// Http client to use to upload telemetry. - public ZipkinExporter(ZipkinExporterOptions options, HttpClient client = null) + public ZipkinExporter(ZipkinExporterOptions options, HttpClient? client = null) { Guard.ThrowIfNull(options); this.options = options; - this.maxPayloadSizeInBytes = (!options.MaxPayloadSizeInBytes.HasValue || options.MaxPayloadSizeInBytes <= 0) ? ZipkinExporterOptions.DefaultMaxPayloadSizeInBytes : options.MaxPayloadSizeInBytes.Value; + this.maxPayloadSizeInBytes = (!options.MaxPayloadSizeInBytes.HasValue || options.MaxPayloadSizeInBytes <= 0) + ? ZipkinExporterOptions.DefaultMaxPayloadSizeInBytes + : options.MaxPayloadSizeInBytes.Value; this.httpClient = client ?? options.HttpClientFactory?.Invoke() ?? throw new InvalidOperationException("ZipkinExporter was missing HttpClientFactory or it returned null."); + +#if NET + // See: https://github.com/dotnet/runtime/blob/280f2a0c60ce0378b8db49adc0eecc463d00fe5d/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.AnyMobile.cs#L767 + this.synchronousSendSupportedByCurrentPlatform = !OperatingSystem.IsAndroid() + && !OperatingSystem.IsIOS() + && !OperatingSystem.IsTvOS() + && !OperatingSystem.IsBrowser(); +#endif } - internal ZipkinEndpoint LocalEndpoint { get; private set; } + internal ZipkinEndpoint? LocalEndpoint { get; private set; } /// public override ExportResult Export(in Batch batch) @@ -61,8 +74,10 @@ public override ExportResult Export(in Batch batch) Content = new JsonContent(this, batch), }; -#if NET6_0_OR_GREATER - using var response = this.httpClient.Send(request, CancellationToken.None); +#if NET + using var response = this.synchronousSendSupportedByCurrentPlatform + ? this.httpClient.Send(request, CancellationToken.None) + : this.httpClient.SendAsync(request, CancellationToken.None).GetAwaiter().GetResult(); #else using var response = this.httpClient.SendAsync(request, CancellationToken.None).GetAwaiter().GetResult(); #endif @@ -83,15 +98,15 @@ internal void SetLocalEndpointFromResource(Resource resource) { var hostName = ResolveHostName(); - string ipv4 = null; - string ipv6 = null; + string? ipv4 = null; + string? ipv6 = null; if (!string.IsNullOrEmpty(hostName)) { - ipv4 = ResolveHostAddress(hostName, AddressFamily.InterNetwork); - ipv6 = ResolveHostAddress(hostName, AddressFamily.InterNetworkV6); + ipv4 = ResolveHostAddress(hostName!, AddressFamily.InterNetwork); + ipv6 = ResolveHostAddress(hostName!, AddressFamily.InterNetworkV6); } - string serviceName = null; + string? serviceName = null; foreach (var label in resource.Attributes) { if (label.Key == ResourceSemanticConventions.AttributeServiceName) @@ -115,9 +130,9 @@ internal void SetLocalEndpointFromResource(Resource resource) tags: null); } - private static string ResolveHostAddress(string hostName, AddressFamily family) + private static string? ResolveHostAddress(string hostName, AddressFamily family) { - string result = null; + string? result = null; try { @@ -145,9 +160,9 @@ private static string ResolveHostAddress(string hostName, AddressFamily family) return result; } - private static string ResolveHostName() + private static string? ResolveHostName() { - string result = null; + string? result = null; try { @@ -180,7 +195,7 @@ private sealed class JsonContent : HttpContent private readonly ZipkinExporter exporter; private readonly Batch batch; - private Utf8JsonWriter writer; + private Utf8JsonWriter? writer; public JsonContent(ZipkinExporter exporter, in Batch batch) { @@ -190,14 +205,14 @@ public JsonContent(ZipkinExporter exporter, in Batch batch) this.Headers.ContentType = JsonHeader; } -#if NET6_0_OR_GREATER - protected override void SerializeToStream(Stream stream, TransportContext context, CancellationToken cancellationToken) +#if NET + protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellationToken) { this.SerializeToStreamInternal(stream); } #endif - protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) + protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) { this.SerializeToStreamInternal(stream); return Task.CompletedTask; @@ -226,7 +241,7 @@ private void SerializeToStreamInternal(Stream stream) foreach (var activity in this.batch) { - var zipkinSpan = activity.ToZipkinSpan(this.exporter.LocalEndpoint, this.exporter.options.UseShortTraceIds); + var zipkinSpan = activity.ToZipkinSpan(this.exporter.LocalEndpoint!, this.exporter.options.UseShortTraceIds); zipkinSpan.Write(this.writer); diff --git a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterHelperExtensions.cs b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterHelperExtensions.cs index ca858c649a..b7142b985a 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterHelperExtensions.cs @@ -39,13 +39,13 @@ public static TracerProviderBuilder AddZipkinExporter(this TracerProviderBuilder /// Adds Zipkin exporter to the TracerProvider. /// /// builder to use. - /// Name which is used when retrieving options. - /// Callback action for configuring . + /// Optional name which is used when retrieving options. + /// Optional callback action for configuring . /// The instance of to chain the calls. public static TracerProviderBuilder AddZipkinExporter( this TracerProviderBuilder builder, - string name, - Action configure) + string? name, + Action? configure) { Guard.ThrowIfNull(builder); @@ -81,13 +81,13 @@ private static BaseProcessor BuildZipkinExporterProcessor( { options.HttpClientFactory = () => { - Type httpClientFactoryType = Type.GetType("System.Net.Http.IHttpClientFactory, Microsoft.Extensions.Http", throwOnError: false); + Type? httpClientFactoryType = Type.GetType("System.Net.Http.IHttpClientFactory, Microsoft.Extensions.Http", throwOnError: false); if (httpClientFactoryType != null) { - object httpClientFactory = serviceProvider.GetService(httpClientFactoryType); + object? httpClientFactory = serviceProvider.GetService(httpClientFactoryType); if (httpClientFactory != null) { - MethodInfo createClientMethod = httpClientFactoryType.GetMethod( + MethodInfo? createClientMethod = httpClientFactoryType.GetMethod( "CreateClient", BindingFlags.Public | BindingFlags.Instance, binder: null, @@ -95,7 +95,9 @@ private static BaseProcessor BuildZipkinExporterProcessor( modifiers: null); if (createClientMethod != null) { - return (HttpClient)createClientMethod.Invoke(httpClientFactory, new object[] { "ZipkinExporter" }); + var parameters = new object[] { "ZipkinExporter" }; + var client = (HttpClient?)createClientMethod.Invoke(httpClientFactory, parameters); + return client ?? new HttpClient(); } } } diff --git a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs index bc94307351..1f4bcd1468 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs @@ -40,12 +40,12 @@ internal ZipkinExporterOptions( Debug.Assert(configuration != null, "configuration was null"); Debug.Assert(defaultBatchOptions != null, "defaultBatchOptions was null"); - if (configuration.TryGetUriValue(ZipkinExporterEventSource.Log, ZipkinEndpointEnvVar, out var endpoint)) + if (configuration!.TryGetUriValue(ZipkinExporterEventSource.Log, ZipkinEndpointEnvVar, out var endpoint)) { - this.Endpoint = endpoint; + this.Endpoint = endpoint!; } - this.BatchExportProcessorOptions = defaultBatchOptions; + this.BatchExportProcessorOptions = defaultBatchOptions!; } /// diff --git a/src/OpenTelemetry.Extensions.Hosting/.publicApi/Experimental/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions.Hosting/.publicApi/Experimental/PublicAPI.Unshipped.txt index f83d7ca4a0..e69de29bb2 100644 --- a/src/OpenTelemetry.Extensions.Hosting/.publicApi/Experimental/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Extensions.Hosting/.publicApi/Experimental/PublicAPI.Unshipped.txt @@ -1,3 +0,0 @@ -OpenTelemetry.OpenTelemetryBuilder.WithLogging() -> OpenTelemetry.OpenTelemetryBuilder! -OpenTelemetry.OpenTelemetryBuilder.WithLogging(System.Action! configure) -> OpenTelemetry.OpenTelemetryBuilder! -OpenTelemetry.OpenTelemetryBuilder.WithLogging(System.Action? configureBuilder, System.Action? configureOptions) -> OpenTelemetry.OpenTelemetryBuilder! diff --git a/src/OpenTelemetry.Extensions.Hosting/.publicApi/Stable/PublicAPI.Shipped.txt b/src/OpenTelemetry.Extensions.Hosting/.publicApi/Stable/PublicAPI.Shipped.txt index b0f847bf1e..57bce9162b 100644 --- a/src/OpenTelemetry.Extensions.Hosting/.publicApi/Stable/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry.Extensions.Hosting/.publicApi/Stable/PublicAPI.Shipped.txt @@ -3,6 +3,9 @@ Microsoft.Extensions.DependencyInjection.OpenTelemetryServicesExtensions OpenTelemetry.OpenTelemetryBuilder OpenTelemetry.OpenTelemetryBuilder.ConfigureResource(System.Action! configure) -> OpenTelemetry.OpenTelemetryBuilder! OpenTelemetry.OpenTelemetryBuilder.Services.get -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +OpenTelemetry.OpenTelemetryBuilder.WithLogging() -> OpenTelemetry.OpenTelemetryBuilder! +OpenTelemetry.OpenTelemetryBuilder.WithLogging(System.Action! configure) -> OpenTelemetry.OpenTelemetryBuilder! +OpenTelemetry.OpenTelemetryBuilder.WithLogging(System.Action? configureBuilder, System.Action? configureOptions) -> OpenTelemetry.OpenTelemetryBuilder! OpenTelemetry.OpenTelemetryBuilder.WithMetrics() -> OpenTelemetry.OpenTelemetryBuilder! OpenTelemetry.OpenTelemetryBuilder.WithMetrics(System.Action! configure) -> OpenTelemetry.OpenTelemetryBuilder! OpenTelemetry.OpenTelemetryBuilder.WithTracing() -> OpenTelemetry.OpenTelemetryBuilder! diff --git a/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md b/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md index a5921a6dd9..65cd938384 100644 --- a/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md +++ b/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md @@ -1,7 +1,32 @@ # Changelog +This file contains individual changes for the OpenTelemetry.Extensions.Hosting +package. For highlights and announcements covering all components see: [Release +Notes](../../RELEASENOTES.md). + ## Unreleased +## 1.10.0-beta.1 + +Released 2024-Sep-30 + +* Updated `Microsoft.Extensions.Hosting.Abstractions` package + version to `9.0.0-rc.1.24431.7`. + ([#5853](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5853)) + +## 1.9.0 + +Released 2024-Jun-14 + +## 1.9.0-rc.1 + +Released 2024-Jun-07 + +* The experimental APIs previously covered by `OTEL1000` + (`OpenTelemetryBuilder.WithLogging` method) are now be part of the public API + and supported in stable builds. + ([#5648](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5648)) + ## 1.9.0-alpha.1 Released 2024-May-20 diff --git a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilder.cs b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilder.cs index 042a211e76..5512befb59 100644 --- a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilder.cs +++ b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilder.cs @@ -1,9 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if EXPOSE_EXPERIMENTAL_FEATURES && NET8_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -#endif using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.Metrics; using Microsoft.Extensions.Logging; @@ -112,13 +109,10 @@ public OpenTelemetryBuilder WithTracing(Action configure) return this; } -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Adds logging services into the builder. /// /// - /// WARNING: This is an experimental API which might change or - /// be removed in the future. Use at your own risk. /// Notes: /// /// This is safe to be called multiple times and by library authors. @@ -131,33 +125,9 @@ public OpenTelemetryBuilder WithTracing(Action configure) /// /// The supplied for chaining /// calls. -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else - /// - /// Adds logging services into the builder. - /// - /// - /// Notes: - /// - /// This is safe to be called multiple times and by library authors. - /// Only a single will be created for a given - /// . - /// This method automatically registers an named 'OpenTelemetry' into the . - /// - /// - /// The supplied for chaining - /// calls. - internal -#endif - OpenTelemetryBuilder WithLogging() + public OpenTelemetryBuilder WithLogging() => this.WithLogging(configureBuilder: null, configureOptions: null); -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Adds logging services into the builder. /// @@ -166,44 +136,13 @@ OpenTelemetryBuilder WithLogging() /// configuration callback. /// The supplied for chaining /// calls. -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else - /// - /// Adds logging services into the builder. - /// - /// - /// - /// configuration callback. - /// The supplied for chaining - /// calls. - internal -#endif - OpenTelemetryBuilder WithLogging(Action configure) + public OpenTelemetryBuilder WithLogging(Action configure) { Guard.ThrowIfNull(configure); return this.WithLogging(configureBuilder: configure, configureOptions: null); } -#if EXPOSE_EXPERIMENTAL_FEATURES - /// - /// Adds logging services into the builder. - /// - /// - /// Optional configuration callback. - /// Optional configuration callback. - /// The supplied for chaining - /// calls. -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else /// /// Adds logging services into the builder. /// @@ -211,14 +150,15 @@ OpenTelemetryBuilder WithLogging(Action configure) /// Optional configuration callback. /// Optional configuration callback. + /// cref="OpenTelemetryLoggerOptions"/> configuration callback. are used by the named 'OpenTelemetry' automatically registered + /// by this method. /// The supplied for chaining /// calls. - internal -#endif - OpenTelemetryBuilder WithLogging( - Action? configureBuilder, - Action? configureOptions) + public OpenTelemetryBuilder WithLogging( + Action? configureBuilder, + Action? configureOptions) { OpenTelemetryBuilderSdkExtensions.WithLogging(this, configureBuilder, configureOptions); diff --git a/src/OpenTelemetry.Extensions.Propagators/.publicApi/PublicAPI.Shipped.txt b/src/OpenTelemetry.Extensions.Propagators/.publicApi/PublicAPI.Shipped.txt index eadd932c93..3379641fda 100644 --- a/src/OpenTelemetry.Extensions.Propagators/.publicApi/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry.Extensions.Propagators/.publicApi/PublicAPI.Shipped.txt @@ -1,11 +1,12 @@ +#nullable enable OpenTelemetry.Extensions.Propagators.B3Propagator OpenTelemetry.Extensions.Propagators.B3Propagator.B3Propagator() -> void OpenTelemetry.Extensions.Propagators.B3Propagator.B3Propagator(bool singleHeader) -> void OpenTelemetry.Extensions.Propagators.JaegerPropagator OpenTelemetry.Extensions.Propagators.JaegerPropagator.JaegerPropagator() -> void -override OpenTelemetry.Extensions.Propagators.B3Propagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func> getter) -> OpenTelemetry.Context.Propagation.PropagationContext -override OpenTelemetry.Extensions.Propagators.B3Propagator.Fields.get -> System.Collections.Generic.ISet -override OpenTelemetry.Extensions.Propagators.B3Propagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action setter) -> void -override OpenTelemetry.Extensions.Propagators.JaegerPropagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func> getter) -> OpenTelemetry.Context.Propagation.PropagationContext -override OpenTelemetry.Extensions.Propagators.JaegerPropagator.Fields.get -> System.Collections.Generic.ISet -override OpenTelemetry.Extensions.Propagators.JaegerPropagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action setter) -> void +override OpenTelemetry.Extensions.Propagators.B3Propagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func?>! getter) -> OpenTelemetry.Context.Propagation.PropagationContext +override OpenTelemetry.Extensions.Propagators.B3Propagator.Fields.get -> System.Collections.Generic.ISet! +override OpenTelemetry.Extensions.Propagators.B3Propagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action! setter) -> void +override OpenTelemetry.Extensions.Propagators.JaegerPropagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func?>! getter) -> OpenTelemetry.Context.Propagation.PropagationContext +override OpenTelemetry.Extensions.Propagators.JaegerPropagator.Fields.get -> System.Collections.Generic.ISet! +override OpenTelemetry.Extensions.Propagators.JaegerPropagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action! setter) -> void diff --git a/src/OpenTelemetry.Extensions.Propagators/B3Propagator.cs b/src/OpenTelemetry.Extensions.Propagators/B3Propagator.cs index 37ced1218e..45239981f0 100644 --- a/src/OpenTelemetry.Extensions.Propagators/B3Propagator.cs +++ b/src/OpenTelemetry.Extensions.Propagators/B3Propagator.cs @@ -62,7 +62,7 @@ public B3Propagator(bool singleHeader) public override ISet Fields => AllFields; /// - public override PropagationContext Extract(PropagationContext context, T carrier, Func> getter) + public override PropagationContext Extract(PropagationContext context, T carrier, Func?> getter) { if (context.ActivityContext.IsValid()) { @@ -138,7 +138,7 @@ public override void Inject(PropagationContext context, T carrier, Action(PropagationContext context, T carrier, Func> getter) + private static PropagationContext ExtractFromMultipleHeaders(PropagationContext context, T carrier, Func?> getter) { try { @@ -171,7 +171,8 @@ private static PropagationContext ExtractFromMultipleHeaders(PropagationConte } var traceOptions = ActivityTraceFlags.None; - if (SampledValues.Contains(getter(carrier, XB3Sampled)?.FirstOrDefault()) + var xb3Sampled = getter(carrier, XB3Sampled)?.FirstOrDefault(); + if ((xb3Sampled != null && SampledValues.Contains(xb3Sampled)) || FlagsValue.Equals(getter(carrier, XB3Flags)?.FirstOrDefault(), StringComparison.Ordinal)) { traceOptions |= ActivityTraceFlags.Recorded; @@ -188,7 +189,7 @@ private static PropagationContext ExtractFromMultipleHeaders(PropagationConte } } - private static PropagationContext ExtractFromSingleHeader(PropagationContext context, T carrier, Func> getter) + private static PropagationContext ExtractFromSingleHeader(PropagationContext context, T carrier, Func?> getter) { try { @@ -198,7 +199,7 @@ private static PropagationContext ExtractFromSingleHeader(PropagationContext 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 68d9cd7747..60128b5a7d 100644 --- a/src/OpenTelemetry.Extensions.Propagators/CHANGELOG.md +++ b/src/OpenTelemetry.Extensions.Propagators/CHANGELOG.md @@ -1,7 +1,23 @@ # Changelog +This file contains individual changes for the +OpenTelemetry.Extensions.Propagators package. For highlights and announcements +covering all components see: [Release Notes](../../RELEASENOTES.md). + ## Unreleased +## 1.10.0-beta.1 + +Released 2024-Sep-30 + +## 1.9.0 + +Released 2024-Jun-14 + +## 1.9.0-rc.1 + +Released 2024-Jun-07 + ## 1.9.0-alpha.1 Released 2024-May-20 diff --git a/src/OpenTelemetry.Extensions.Propagators/JaegerPropagator.cs b/src/OpenTelemetry.Extensions.Propagators/JaegerPropagator.cs index 472b719ded..045fd93e58 100644 --- a/src/OpenTelemetry.Extensions.Propagators/JaegerPropagator.cs +++ b/src/OpenTelemetry.Extensions.Propagators/JaegerPropagator.cs @@ -26,7 +26,7 @@ public class JaegerPropagator : TextMapPropagator public override ISet Fields => new HashSet { JaegerHeader }; /// - public override PropagationContext Extract(PropagationContext context, T carrier, Func> getter) + public override PropagationContext Extract(PropagationContext context, T carrier, Func?> getter) { if (context.ActivityContext.IsValid()) { @@ -56,7 +56,7 @@ public override PropagationContext Extract(PropagationContext context, T carr 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) { diff --git a/src/OpenTelemetry.Extensions.Propagators/OpenTelemetry.Extensions.Propagators.csproj b/src/OpenTelemetry.Extensions.Propagators/OpenTelemetry.Extensions.Propagators.csproj index c099b609a2..01ae76f4a1 100644 --- a/src/OpenTelemetry.Extensions.Propagators/OpenTelemetry.Extensions.Propagators.csproj +++ b/src/OpenTelemetry.Extensions.Propagators/OpenTelemetry.Extensions.Propagators.csproj @@ -5,9 +5,6 @@ $(PackageTags);distributed-tracing;AspNet;AspNetCore;B3 core- true - - - disable diff --git a/src/OpenTelemetry.Shims.OpenTracing/.publicApi/PublicAPI.Shipped.txt b/src/OpenTelemetry.Shims.OpenTracing/.publicApi/PublicAPI.Shipped.txt index e69de29bb2..7dc5c58110 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/.publicApi/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry.Shims.OpenTracing/.publicApi/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/OpenTelemetry.Shims.OpenTracing/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Shims.OpenTracing/.publicApi/PublicAPI.Unshipped.txt index 90f671f8a1..c5d387112f 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Shims.OpenTracing/.publicApi/PublicAPI.Unshipped.txt @@ -1,8 +1,8 @@ OpenTelemetry.Shims.OpenTracing.TracerShim -OpenTelemetry.Shims.OpenTracing.TracerShim.ActiveSpan.get -> OpenTracing.ISpan -OpenTelemetry.Shims.OpenTracing.TracerShim.BuildSpan(string operationName) -> OpenTracing.ISpanBuilder -OpenTelemetry.Shims.OpenTracing.TracerShim.Extract(OpenTracing.Propagation.IFormat format, TCarrier carrier) -> OpenTracing.ISpanContext -OpenTelemetry.Shims.OpenTracing.TracerShim.Inject(OpenTracing.ISpanContext spanContext, OpenTracing.Propagation.IFormat format, TCarrier carrier) -> void -OpenTelemetry.Shims.OpenTracing.TracerShim.ScopeManager.get -> OpenTracing.IScopeManager -OpenTelemetry.Shims.OpenTracing.TracerShim.TracerShim(OpenTelemetry.Trace.TracerProvider tracerProvider) -> void -OpenTelemetry.Shims.OpenTracing.TracerShim.TracerShim(OpenTelemetry.Trace.TracerProvider tracerProvider, OpenTelemetry.Context.Propagation.TextMapPropagator textFormat) -> void +OpenTelemetry.Shims.OpenTracing.TracerShim.ActiveSpan.get -> OpenTracing.ISpan? +OpenTelemetry.Shims.OpenTracing.TracerShim.BuildSpan(string! operationName) -> OpenTracing.ISpanBuilder! +OpenTelemetry.Shims.OpenTracing.TracerShim.Extract(OpenTracing.Propagation.IFormat! format, TCarrier carrier) -> OpenTracing.ISpanContext? +OpenTelemetry.Shims.OpenTracing.TracerShim.Inject(OpenTracing.ISpanContext! spanContext, OpenTracing.Propagation.IFormat! format, TCarrier carrier) -> void +OpenTelemetry.Shims.OpenTracing.TracerShim.ScopeManager.get -> OpenTracing.IScopeManager! +OpenTelemetry.Shims.OpenTracing.TracerShim.TracerShim(OpenTelemetry.Trace.TracerProvider! tracerProvider) -> void +OpenTelemetry.Shims.OpenTracing.TracerShim.TracerShim(OpenTelemetry.Trace.TracerProvider! tracerProvider, OpenTelemetry.Context.Propagation.TextMapPropagator? textFormat) -> void diff --git a/src/OpenTelemetry.Shims.OpenTracing/CHANGELOG.md b/src/OpenTelemetry.Shims.OpenTracing/CHANGELOG.md index b0cb58b6b5..23775d1cdd 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/CHANGELOG.md +++ b/src/OpenTelemetry.Shims.OpenTracing/CHANGELOG.md @@ -1,7 +1,27 @@ # Changelog +This file contains individual changes for the OpenTelemetry.Shims.OpenTracing +package. For highlights and announcements covering all components see: [Release +Notes](../../RELEASENOTES.md). + ## Unreleased +* Fixed an issue causing all tag values added via the `ISpanBuilder` API to be + converted to strings on the `ISpan` started from the builder. + ([#5797](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5797)) + +## 1.9.0-beta.2 + +Released 2024-Jun-24 + +## 1.9.0-beta.1 + +Released 2024-Jun-14 + +## 1.9.0-alpha.2 + +Released 2024-May-29 + ## 1.9.0-alpha.1 Released 2024-May-20 diff --git a/src/OpenTelemetry.Shims.OpenTracing/OpenTelemetry.Shims.OpenTracing.csproj b/src/OpenTelemetry.Shims.OpenTracing/OpenTelemetry.Shims.OpenTracing.csproj index 645c9e49e5..127cbfb3aa 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/OpenTelemetry.Shims.OpenTracing.csproj +++ b/src/OpenTelemetry.Shims.OpenTracing/OpenTelemetry.Shims.OpenTracing.csproj @@ -4,9 +4,6 @@ OpenTracing shim for OpenTelemetry .NET $(PackageTags);distributed-tracing;OpenTracing coreunstable- - - - disable @@ -22,7 +19,9 @@ + + diff --git a/src/OpenTelemetry.Shims.OpenTracing/ScopeManagerShim.cs b/src/OpenTelemetry.Shims.OpenTracing/ScopeManagerShim.cs index f9464d966a..6b2fb78335 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/ScopeManagerShim.cs +++ b/src/OpenTelemetry.Shims.OpenTracing/ScopeManagerShim.cs @@ -19,7 +19,7 @@ internal sealed class ScopeManagerShim : IScopeManager #endif /// - public IScope Active + public IScope? Active { get { @@ -56,7 +56,7 @@ public IScope Activate(ISpan span, bool finishSpanOnDispose) Interlocked.Decrement(ref this.spanScopeTableCount); } #endif - scope.Dispose(); + scope!.Dispose(); }); SpanScopeTable.Add(shim.Span, instrumentation); @@ -69,9 +69,9 @@ public IScope Activate(ISpan span, bool finishSpanOnDispose) private sealed class ScopeInstrumentation : IScope { - private readonly Action disposeAction; + private readonly Action? disposeAction; - public ScopeInstrumentation(TelemetrySpan span, Action disposeAction = null) + public ScopeInstrumentation(TelemetrySpan span, Action? disposeAction = null) { this.Span = new SpanShim(span); this.disposeAction = disposeAction; diff --git a/src/OpenTelemetry.Shims.OpenTracing/SpanBuilderShim.cs b/src/OpenTelemetry.Shims.OpenTracing/SpanBuilderShim.cs index 0359762ac3..f95b1b77be 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/SpanBuilderShim.cs +++ b/src/OpenTelemetry.Shims.OpenTracing/SpanBuilderShim.cs @@ -32,12 +32,12 @@ internal sealed class SpanBuilderShim : ISpanBuilder /// /// The OpenTelemetry attributes. These correspond to OpenTracing Tags. /// - private readonly List> attributes = new(); + private readonly SpanAttributes attributes = new(); /// /// The parent as an TelemetrySpan, if any. /// - private TelemetrySpan parentSpan; + private TelemetrySpan? parentSpan; /// /// The parent as an SpanContext, if any. @@ -70,7 +70,7 @@ public SpanBuilderShim(Tracer tracer, string spanName) private bool ParentSet => this.parentSpan != null || this.parentSpanContext.IsValid; /// - public ISpanBuilder AsChildOf(ISpanContext parent) + public ISpanBuilder AsChildOf(ISpanContext? parent) { if (parent == null) { @@ -81,7 +81,7 @@ public ISpanBuilder AsChildOf(ISpanContext parent) } /// - public ISpanBuilder AsChildOf(ISpan parent) + public ISpanBuilder AsChildOf(ISpan? parent) { if (parent == null) { @@ -98,7 +98,7 @@ public ISpanBuilder AsChildOf(ISpan parent) } /// - public ISpanBuilder AddReference(string referenceType, ISpanContext referencedContext) + public ISpanBuilder AddReference(string referenceType, ISpanContext? referencedContext) { if (referencedContext == null) { @@ -132,30 +132,25 @@ public ISpanBuilder IgnoreActiveSpan() /// public ISpan Start() { - TelemetrySpan span = null; + TelemetrySpan? span = null; // If specified, this takes precedence. if (this.ignoreActiveSpan) { - span = this.tracer.StartRootSpan(this.spanName, this.spanKind, default, this.links, this.explicitStartTime ?? default); + span = this.tracer.StartRootSpan(this.spanName, this.spanKind, this.attributes, this.links, this.explicitStartTime ?? default); } else if (this.parentSpan != null) { - span = this.tracer.StartSpan(this.spanName, this.spanKind, this.parentSpan, default, this.links, this.explicitStartTime ?? default); + span = this.tracer.StartSpan(this.spanName, this.spanKind, this.parentSpan, this.attributes, this.links, this.explicitStartTime ?? default); } else if (this.parentSpanContext.IsValid) { - span = this.tracer.StartSpan(this.spanName, this.spanKind, this.parentSpanContext, default, this.links, this.explicitStartTime ?? default); + 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), default, null, this.explicitStartTime ?? default); - } - - foreach (var kvp in this.attributes) - { - span.SetAttribute(kvp.Key, kvp.Value.ToString()); + span = this.tracer.StartSpan(this.spanName, this.spanKind, default(SpanContext), this.attributes, null, this.explicitStartTime ?? default); } if (this.error) @@ -184,8 +179,13 @@ public ISpanBuilder WithStartTimestamp(DateTimeOffset timestamp) } /// - public ISpanBuilder WithTag(string key, string value) + public ISpanBuilder WithTag(string key, string? value) { + if (key == null) + { + return this; + } + // see https://opentracing.io/specification/conventions/ for special key handling. if (global::OpenTracing.Tag.Tags.SpanKind.Key.Equals(key, StringComparison.Ordinal)) { @@ -204,12 +204,7 @@ public ISpanBuilder WithTag(string key, string value) } else { - // Keys must be non-null. - // Null values => string.Empty. - if (key != null) - { - this.attributes.Add(new KeyValuePair(key, value ?? string.Empty)); - } + this.attributes.Add(key, value); } return this; @@ -224,7 +219,7 @@ public ISpanBuilder WithTag(string key, bool value) } else { - this.attributes.Add(new KeyValuePair(key, value)); + this.attributes.Add(key, value); } return this; @@ -233,31 +228,31 @@ public ISpanBuilder WithTag(string key, bool value) /// public ISpanBuilder WithTag(string key, int value) { - this.attributes.Add(new KeyValuePair(key, value)); + this.attributes.Add(key, value); return this; } /// public ISpanBuilder WithTag(string key, double value) { - this.attributes.Add(new KeyValuePair(key, value)); + this.attributes.Add(key, value); return this; } /// public ISpanBuilder WithTag(global::OpenTracing.Tag.BooleanTag tag, bool value) { - Guard.ThrowIfNull(tag?.Key); + Guard.ThrowIfNull(tag); return this.WithTag(tag.Key, value); } /// - public ISpanBuilder WithTag(global::OpenTracing.Tag.IntOrStringTag tag, string value) + public ISpanBuilder WithTag(global::OpenTracing.Tag.IntOrStringTag tag, string? value) { - Guard.ThrowIfNull(tag?.Key); + Guard.ThrowIfNull(tag); - if (int.TryParse(value, out var result)) + if (value != null && int.TryParse(value, out var result)) { return this.WithTag(tag.Key, result); } @@ -268,15 +263,15 @@ public ISpanBuilder WithTag(global::OpenTracing.Tag.IntOrStringTag tag, string v /// public ISpanBuilder WithTag(global::OpenTracing.Tag.IntTag tag, int value) { - Guard.ThrowIfNull(tag?.Key); + Guard.ThrowIfNull(tag); return this.WithTag(tag.Key, value); } /// - public ISpanBuilder WithTag(global::OpenTracing.Tag.StringTag tag, string value) + public ISpanBuilder WithTag(global::OpenTracing.Tag.StringTag tag, string? value) { - Guard.ThrowIfNull(tag?.Key); + Guard.ThrowIfNull(tag); return this.WithTag(tag.Key, value); } diff --git a/src/OpenTelemetry.Shims.OpenTracing/SpanShim.cs b/src/OpenTelemetry.Shims.OpenTracing/SpanShim.cs index 2ab6fee4fe..c1bde927a1 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/SpanShim.cs +++ b/src/OpenTelemetry.Shims.OpenTracing/SpanShim.cs @@ -39,7 +39,7 @@ public SpanShim(TelemetrySpan span) /// public ISpanContext Context => this.spanContextShim; - public TelemetrySpan Span { get; private set; } + public TelemetrySpan Span { get; } /// public void Finish() @@ -54,7 +54,7 @@ public void Finish(DateTimeOffset finishTimestamp) } /// - public string GetBaggageItem(string key) + public string? GetBaggageItem(string key) => Baggage.GetBaggage(key); /// @@ -137,7 +137,7 @@ public ISpan Log(DateTimeOffset timestamp, string @event) } /// - public ISpan SetBaggageItem(string key, string value) + public ISpan SetBaggageItem(string key, string? value) { Baggage.SetBaggage(key, value); return this; @@ -153,7 +153,7 @@ public ISpan SetOperationName(string operationName) } /// - public ISpan SetTag(string key, string value) + public ISpan SetTag(string key, string? value) { Guard.ThrowIfNull(key); @@ -201,30 +201,38 @@ public ISpan SetTag(string key, double value) /// public ISpan SetTag(global::OpenTracing.Tag.BooleanTag tag, bool value) { - return this.SetTag(tag?.Key, value); + Guard.ThrowIfNull(tag); + + return this.SetTag(tag.Key, value); } /// - public ISpan SetTag(global::OpenTracing.Tag.IntOrStringTag tag, string value) + public ISpan SetTag(global::OpenTracing.Tag.IntOrStringTag tag, string? value) { - if (int.TryParse(value, out var result)) + Guard.ThrowIfNull(tag); + + if (value != null && int.TryParse(value, out var result)) { - return this.SetTag(tag?.Key, result); + return this.SetTag(tag.Key, result); } - return this.SetTag(tag?.Key, value); + return this.SetTag(tag.Key, value); } /// public ISpan SetTag(global::OpenTracing.Tag.IntTag tag, int value) { - return this.SetTag(tag?.Key, value); + Guard.ThrowIfNull(tag); + + return this.SetTag(tag.Key, value); } /// - public ISpan SetTag(global::OpenTracing.Tag.StringTag tag, string value) + public ISpan SetTag(global::OpenTracing.Tag.StringTag tag, string? value) { - return this.SetTag(tag?.Key, value); + Guard.ThrowIfNull(tag); + + return this.SetTag(tag.Key, value); } /// @@ -234,7 +242,7 @@ public ISpan SetTag(global::OpenTracing.Tag.StringTag tag, string value) /// A 2-Tuple containing the event name and payload information. private static Tuple> ConvertToEventPayload(IEnumerable> fields) { - string eventName = null; + string? eventName = null; var attributes = new Dictionary(); foreach (var field in fields) @@ -268,7 +276,7 @@ private static Tuple> ConvertToEventPayload( else { // TODO should we completely ignore unsupported types? - attributes.Add(field.Key, field.Value.ToString()); + attributes.Add(field.Key, field.Value.ToString()!); } } diff --git a/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs b/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs index 3ed448da94..504b28c5a5 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs +++ b/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs @@ -14,7 +14,7 @@ namespace OpenTelemetry.Shims.OpenTracing; public class TracerShim : global::OpenTracing.ITracer { private readonly Trace.Tracer tracer; - private readonly TextMapPropagator definedPropagator; + private readonly TextMapPropagator? definedPropagator; /// /// Initializes a new instance of the class. @@ -30,7 +30,7 @@ public TracerShim(Trace.TracerProvider tracerProvider) /// /// . /// . - public TracerShim(Trace.TracerProvider tracerProvider, TextMapPropagator textFormat) + public TracerShim(Trace.TracerProvider tracerProvider, TextMapPropagator? textFormat) { Guard.ThrowIfNull(tracerProvider); @@ -46,7 +46,7 @@ public TracerShim(Trace.TracerProvider tracerProvider, TextMapPropagator textFor public global::OpenTracing.IScopeManager ScopeManager { get; } /// - public global::OpenTracing.ISpan ActiveSpan => this.ScopeManager.Active?.Span; + public global::OpenTracing.ISpan? ActiveSpan => this.ScopeManager.Active?.Span; private TextMapPropagator Propagator { @@ -63,7 +63,7 @@ private TextMapPropagator Propagator } /// - public global::OpenTracing.ISpanContext Extract(IFormat format, TCarrier carrier) + public global::OpenTracing.ISpanContext? Extract(IFormat format, TCarrier carrier) { Guard.ThrowIfNull(format); Guard.ThrowIfNull(carrier); @@ -79,7 +79,7 @@ private TextMapPropagator Propagator carrierMap.Add(entry.Key, new[] { entry.Value }); } - static IEnumerable GetCarrierKeyValue(Dictionary> source, string key) + static IEnumerable? GetCarrierKeyValue(Dictionary> source, string key) { if (key == null || !source.TryGetValue(key, out var value)) { diff --git a/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt index 22a73f5d6b..7c9a6d046a 100644 --- a/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt @@ -1,8 +1,6 @@ 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 -OpenTelemetry.Logs.LoggerProviderBuilderExtensions -OpenTelemetry.Logs.LoggerProviderExtensions OpenTelemetry.Logs.LogRecord.Logger.get -> OpenTelemetry.Logs.Logger! OpenTelemetry.Logs.LogRecord.Severity.get -> OpenTelemetry.Logs.LogRecordSeverity? OpenTelemetry.Logs.LogRecord.Severity.set -> void @@ -19,25 +17,11 @@ 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 -OpenTelemetry.Metrics.MetricStreamConfiguration.CardinalityLimit.get -> int? -OpenTelemetry.Metrics.MetricStreamConfiguration.CardinalityLimit.set -> void OpenTelemetry.Metrics.MetricStreamConfiguration.ExemplarReservoirFactory.get -> System.Func? OpenTelemetry.Metrics.MetricStreamConfiguration.ExemplarReservoirFactory.set -> void override sealed OpenTelemetry.Metrics.FixedSizeExemplarReservoir.Collect() -> OpenTelemetry.Metrics.ReadOnlyExemplarCollection -static OpenTelemetry.Logs.LoggerProviderBuilderExtensions.AddProcessor(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.Logs.LoggerProviderBuilder! -static OpenTelemetry.Logs.LoggerProviderBuilderExtensions.AddProcessor(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, System.Func!>! implementationFactory) -> OpenTelemetry.Logs.LoggerProviderBuilder! -static OpenTelemetry.Logs.LoggerProviderBuilderExtensions.AddProcessor(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder) -> OpenTelemetry.Logs.LoggerProviderBuilder! -static OpenTelemetry.Logs.LoggerProviderBuilderExtensions.Build(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder) -> OpenTelemetry.Logs.LoggerProvider! -static OpenTelemetry.Logs.LoggerProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, System.Action! configure) -> OpenTelemetry.Logs.LoggerProviderBuilder! -static OpenTelemetry.Logs.LoggerProviderBuilderExtensions.SetResourceBuilder(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, OpenTelemetry.Resources.ResourceBuilder! resourceBuilder) -> OpenTelemetry.Logs.LoggerProviderBuilder! -static OpenTelemetry.Logs.LoggerProviderExtensions.AddProcessor(this OpenTelemetry.Logs.LoggerProvider! provider, OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.Logs.LoggerProvider! -static OpenTelemetry.Logs.LoggerProviderExtensions.ForceFlush(this OpenTelemetry.Logs.LoggerProvider! provider, int timeoutMilliseconds = -1) -> bool -static OpenTelemetry.Logs.LoggerProviderExtensions.Shutdown(this OpenTelemetry.Logs.LoggerProvider! provider, int timeoutMilliseconds = -1) -> bool -static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.WithLogging(this OpenTelemetry.IOpenTelemetryBuilder! builder) -> OpenTelemetry.IOpenTelemetryBuilder! -static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.WithLogging(this OpenTelemetry.IOpenTelemetryBuilder! builder, System.Action! configure) -> OpenTelemetry.IOpenTelemetryBuilder! -static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.WithLogging(this OpenTelemetry.IOpenTelemetryBuilder! builder, System.Action? configureBuilder, System.Action? configureOptions) -> OpenTelemetry.IOpenTelemetryBuilder! -static OpenTelemetry.Sdk.CreateLoggerProviderBuilder() -> OpenTelemetry.Logs.LoggerProviderBuilder! 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 diff --git a/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Shipped.txt index 6a4426d4ce..43e084ad58 100644 --- a/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Shipped.txt @@ -56,6 +56,8 @@ OpenTelemetry.ExportResult.Failure = 1 -> OpenTelemetry.ExportResult OpenTelemetry.ExportResult.Success = 0 -> OpenTelemetry.ExportResult OpenTelemetry.Logs.BatchExportLogRecordProcessorOptions OpenTelemetry.Logs.BatchExportLogRecordProcessorOptions.BatchExportLogRecordProcessorOptions() -> void +OpenTelemetry.Logs.LoggerProviderBuilderExtensions +OpenTelemetry.Logs.LoggerProviderExtensions OpenTelemetry.Logs.LogRecord OpenTelemetry.Logs.LogRecord.Attributes.get -> System.Collections.Generic.IReadOnlyList>? OpenTelemetry.Logs.LogRecord.Attributes.set -> void @@ -129,6 +131,18 @@ OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set - OpenTelemetry.Metrics.BaseExportingMetricReader OpenTelemetry.Metrics.BaseExportingMetricReader.BaseExportingMetricReader(OpenTelemetry.BaseExporter! exporter) -> void OpenTelemetry.Metrics.BaseExportingMetricReader.SupportedExportModes.get -> OpenTelemetry.Metrics.ExportModes +OpenTelemetry.Metrics.Exemplar +OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double +OpenTelemetry.Metrics.Exemplar.Exemplar() -> void +OpenTelemetry.Metrics.Exemplar.FilteredTags.get -> OpenTelemetry.ReadOnlyFilteredTagCollection +OpenTelemetry.Metrics.Exemplar.LongValue.get -> long +OpenTelemetry.Metrics.Exemplar.SpanId.get -> System.Diagnostics.ActivitySpanId +OpenTelemetry.Metrics.Exemplar.Timestamp.get -> System.DateTimeOffset +OpenTelemetry.Metrics.Exemplar.TraceId.get -> System.Diagnostics.ActivityTraceId +OpenTelemetry.Metrics.ExemplarFilterType +OpenTelemetry.Metrics.ExemplarFilterType.AlwaysOff = 0 -> OpenTelemetry.Metrics.ExemplarFilterType +OpenTelemetry.Metrics.ExemplarFilterType.AlwaysOn = 1 -> OpenTelemetry.Metrics.ExemplarFilterType +OpenTelemetry.Metrics.ExemplarFilterType.TraceBased = 2 -> OpenTelemetry.Metrics.ExemplarFilterType OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.Boundaries.get -> double[]? OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.Boundaries.set -> void @@ -195,6 +209,7 @@ OpenTelemetry.Metrics.MetricPoint.GetSumLong() -> long OpenTelemetry.Metrics.MetricPoint.MetricPoint() -> void OpenTelemetry.Metrics.MetricPoint.StartTime.get -> System.DateTimeOffset OpenTelemetry.Metrics.MetricPoint.Tags.get -> OpenTelemetry.ReadOnlyTagCollection +OpenTelemetry.Metrics.MetricPoint.TryGetExemplars(out OpenTelemetry.Metrics.ReadOnlyExemplarCollection exemplars) -> bool OpenTelemetry.Metrics.MetricPoint.TryGetHistogramMinMaxValues(out double min, out double max) -> bool OpenTelemetry.Metrics.MetricPointsAccessor OpenTelemetry.Metrics.MetricPointsAccessor.Enumerator @@ -245,8 +260,22 @@ OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions.ExportIntervalMillise OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions.ExportTimeoutMilliseconds.get -> int? OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions.ExportTimeoutMilliseconds.set -> void OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions.PeriodicExportingMetricReaderOptions() -> void +OpenTelemetry.Metrics.ReadOnlyExemplarCollection +OpenTelemetry.Metrics.ReadOnlyExemplarCollection.Enumerator +OpenTelemetry.Metrics.ReadOnlyExemplarCollection.Enumerator.Current.get -> OpenTelemetry.Metrics.Exemplar +OpenTelemetry.Metrics.ReadOnlyExemplarCollection.Enumerator.Enumerator() -> void +OpenTelemetry.Metrics.ReadOnlyExemplarCollection.Enumerator.MoveNext() -> bool +OpenTelemetry.Metrics.ReadOnlyExemplarCollection.GetEnumerator() -> OpenTelemetry.Metrics.ReadOnlyExemplarCollection.Enumerator +OpenTelemetry.Metrics.ReadOnlyExemplarCollection.ReadOnlyExemplarCollection() -> void OpenTelemetry.OpenTelemetryBuilderSdkExtensions OpenTelemetry.ProviderExtensions +OpenTelemetry.ReadOnlyFilteredTagCollection +OpenTelemetry.ReadOnlyFilteredTagCollection.Enumerator +OpenTelemetry.ReadOnlyFilteredTagCollection.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair +OpenTelemetry.ReadOnlyFilteredTagCollection.Enumerator.Enumerator() -> void +OpenTelemetry.ReadOnlyFilteredTagCollection.Enumerator.MoveNext() -> bool +OpenTelemetry.ReadOnlyFilteredTagCollection.GetEnumerator() -> OpenTelemetry.ReadOnlyFilteredTagCollection.Enumerator +OpenTelemetry.ReadOnlyFilteredTagCollection.ReadOnlyFilteredTagCollection() -> void OpenTelemetry.ReadOnlyTagCollection OpenTelemetry.ReadOnlyTagCollection.Count.get -> int OpenTelemetry.ReadOnlyTagCollection.Enumerator @@ -289,8 +318,8 @@ OpenTelemetry.Trace.AlwaysOnSampler.AlwaysOnSampler() -> void OpenTelemetry.Trace.BatchExportActivityProcessorOptions OpenTelemetry.Trace.BatchExportActivityProcessorOptions.BatchExportActivityProcessorOptions() -> void OpenTelemetry.Trace.ParentBasedSampler -OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler! rootSampler) -> void OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler! rootSampler, OpenTelemetry.Trace.Sampler? remoteParentSampled = null, OpenTelemetry.Trace.Sampler? remoteParentNotSampled = null, OpenTelemetry.Trace.Sampler? localParentSampled = null, OpenTelemetry.Trace.Sampler? localParentNotSampled = null) -> void +OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler! rootSampler) -> void OpenTelemetry.Trace.Sampler OpenTelemetry.Trace.Sampler.Description.get -> string! OpenTelemetry.Trace.Sampler.Description.set -> void @@ -314,10 +343,10 @@ OpenTelemetry.Trace.SamplingResult.Decision.get -> OpenTelemetry.Trace.SamplingD OpenTelemetry.Trace.SamplingResult.Equals(OpenTelemetry.Trace.SamplingResult other) -> bool OpenTelemetry.Trace.SamplingResult.SamplingResult() -> void OpenTelemetry.Trace.SamplingResult.SamplingResult(bool isSampled) -> void -OpenTelemetry.Trace.SamplingResult.SamplingResult(OpenTelemetry.Trace.SamplingDecision decision) -> void OpenTelemetry.Trace.SamplingResult.SamplingResult(OpenTelemetry.Trace.SamplingDecision decision, string? traceStateString) -> void -OpenTelemetry.Trace.SamplingResult.SamplingResult(OpenTelemetry.Trace.SamplingDecision decision, System.Collections.Generic.IEnumerable>? attributes) -> void OpenTelemetry.Trace.SamplingResult.SamplingResult(OpenTelemetry.Trace.SamplingDecision decision, System.Collections.Generic.IEnumerable>? attributes, string? traceStateString) -> void +OpenTelemetry.Trace.SamplingResult.SamplingResult(OpenTelemetry.Trace.SamplingDecision decision, System.Collections.Generic.IEnumerable>? attributes) -> void +OpenTelemetry.Trace.SamplingResult.SamplingResult(OpenTelemetry.Trace.SamplingDecision decision) -> void OpenTelemetry.Trace.SamplingResult.TraceStateString.get -> string? OpenTelemetry.Trace.TraceIdRatioBasedSampler OpenTelemetry.Trace.TraceIdRatioBasedSampler.TraceIdRatioBasedSampler(double probability) -> void @@ -366,8 +395,17 @@ override OpenTelemetry.Trace.TracerProviderBuilderBase.AddSource(params string![ override sealed OpenTelemetry.BaseExportProcessor.OnStart(T! data) -> void readonly OpenTelemetry.BaseExportProcessor.exporter -> OpenTelemetry.BaseExporter! readonly OpenTelemetry.Metrics.BaseExportingMetricReader.exporter -> OpenTelemetry.BaseExporter! -static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder) -> Microsoft.Extensions.Logging.ILoggingBuilder! static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static OpenTelemetry.Logs.LoggerProviderBuilderExtensions.AddProcessor(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.LoggerProviderBuilderExtensions.AddProcessor(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, System.Func!>! implementationFactory) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.LoggerProviderBuilderExtensions.AddProcessor(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.LoggerProviderBuilderExtensions.Build(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder) -> OpenTelemetry.Logs.LoggerProvider! +static OpenTelemetry.Logs.LoggerProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, System.Action! configure) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.LoggerProviderBuilderExtensions.SetResourceBuilder(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, OpenTelemetry.Resources.ResourceBuilder! resourceBuilder) -> OpenTelemetry.Logs.LoggerProviderBuilder! +static OpenTelemetry.Logs.LoggerProviderExtensions.AddProcessor(this OpenTelemetry.Logs.LoggerProvider! provider, OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.Logs.LoggerProvider! +static OpenTelemetry.Logs.LoggerProviderExtensions.ForceFlush(this OpenTelemetry.Logs.LoggerProvider! provider, int timeoutMilliseconds = -1) -> bool +static OpenTelemetry.Logs.LoggerProviderExtensions.Shutdown(this OpenTelemetry.Logs.LoggerProvider! provider, int timeoutMilliseconds = -1) -> bool static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddReader(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, OpenTelemetry.Metrics.MetricReader! reader) -> OpenTelemetry.Metrics.MeterProviderBuilder! static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddReader(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, System.Func! implementationFactory) -> OpenTelemetry.Metrics.MeterProviderBuilder! static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddReader(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder) -> OpenTelemetry.Metrics.MeterProviderBuilder! @@ -376,6 +414,7 @@ static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddView(this OpenTel static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddView(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, System.Func! viewConfig) -> OpenTelemetry.Metrics.MeterProviderBuilder! static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.Build(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder) -> OpenTelemetry.Metrics.MeterProvider! static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, System.Action! configure) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetExemplarFilter(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, OpenTelemetry.Metrics.ExemplarFilterType exemplarFilter) -> OpenTelemetry.Metrics.MeterProviderBuilder! static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetMaxMetricPointsPerMetricStream(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, int maxMetricPointsPerMetricStream) -> OpenTelemetry.Metrics.MeterProviderBuilder! static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetMaxMetricStreams(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, int maxMetricStreams) -> OpenTelemetry.Metrics.MeterProviderBuilder! static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetResourceBuilder(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, OpenTelemetry.Resources.ResourceBuilder! resourceBuilder) -> OpenTelemetry.Metrics.MeterProviderBuilder! @@ -388,10 +427,13 @@ static OpenTelemetry.Metrics.MetricTypeExtensions.IsHistogram(this OpenTelemetry static OpenTelemetry.Metrics.MetricTypeExtensions.IsLong(this OpenTelemetry.Metrics.MetricType self) -> bool static OpenTelemetry.Metrics.MetricTypeExtensions.IsSum(this OpenTelemetry.Metrics.MetricType self) -> bool static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.ConfigureResource(this OpenTelemetry.IOpenTelemetryBuilder! builder, System.Action! configure) -> OpenTelemetry.IOpenTelemetryBuilder! -static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.WithMetrics(this OpenTelemetry.IOpenTelemetryBuilder! builder) -> OpenTelemetry.IOpenTelemetryBuilder! +static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.WithLogging(this OpenTelemetry.IOpenTelemetryBuilder! builder, System.Action! configure) -> OpenTelemetry.IOpenTelemetryBuilder! +static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.WithLogging(this OpenTelemetry.IOpenTelemetryBuilder! builder, System.Action? configureBuilder, System.Action? configureOptions) -> OpenTelemetry.IOpenTelemetryBuilder! +static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.WithLogging(this OpenTelemetry.IOpenTelemetryBuilder! builder) -> OpenTelemetry.IOpenTelemetryBuilder! static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.WithMetrics(this OpenTelemetry.IOpenTelemetryBuilder! builder, System.Action! configure) -> OpenTelemetry.IOpenTelemetryBuilder! -static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.WithTracing(this OpenTelemetry.IOpenTelemetryBuilder! builder) -> OpenTelemetry.IOpenTelemetryBuilder! +static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.WithMetrics(this OpenTelemetry.IOpenTelemetryBuilder! builder) -> OpenTelemetry.IOpenTelemetryBuilder! static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.WithTracing(this OpenTelemetry.IOpenTelemetryBuilder! builder, System.Action! configure) -> OpenTelemetry.IOpenTelemetryBuilder! +static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.WithTracing(this OpenTelemetry.IOpenTelemetryBuilder! builder) -> OpenTelemetry.IOpenTelemetryBuilder! static OpenTelemetry.ProviderExtensions.GetDefaultResource(this OpenTelemetry.BaseProvider! baseProvider) -> OpenTelemetry.Resources.Resource! static OpenTelemetry.ProviderExtensions.GetResource(this OpenTelemetry.BaseProvider! baseProvider) -> OpenTelemetry.Resources.Resource! static OpenTelemetry.Resources.Resource.Empty.get -> OpenTelemetry.Resources.Resource! diff --git a/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Unshipped.txt index 3ad610b842..e79caee69b 100644 --- a/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Unshipped.txt @@ -1,29 +1,11 @@ OpenTelemetry.Batch.Batch(T! item) -> void -OpenTelemetry.Metrics.Exemplar -OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double -OpenTelemetry.Metrics.Exemplar.Exemplar() -> void -OpenTelemetry.Metrics.Exemplar.FilteredTags.get -> OpenTelemetry.ReadOnlyFilteredTagCollection -OpenTelemetry.Metrics.Exemplar.LongValue.get -> long -OpenTelemetry.Metrics.Exemplar.SpanId.get -> System.Diagnostics.ActivitySpanId -OpenTelemetry.Metrics.Exemplar.Timestamp.get -> System.DateTimeOffset -OpenTelemetry.Metrics.Exemplar.TraceId.get -> System.Diagnostics.ActivityTraceId -OpenTelemetry.Metrics.ExemplarFilterType -OpenTelemetry.Metrics.ExemplarFilterType.AlwaysOff = 0 -> OpenTelemetry.Metrics.ExemplarFilterType -OpenTelemetry.Metrics.ExemplarFilterType.AlwaysOn = 1 -> OpenTelemetry.Metrics.ExemplarFilterType -OpenTelemetry.Metrics.ExemplarFilterType.TraceBased = 2 -> OpenTelemetry.Metrics.ExemplarFilterType -OpenTelemetry.Metrics.MetricPoint.TryGetExemplars(out OpenTelemetry.Metrics.ReadOnlyExemplarCollection exemplars) -> bool -OpenTelemetry.Metrics.ReadOnlyExemplarCollection -OpenTelemetry.Metrics.ReadOnlyExemplarCollection.Enumerator -OpenTelemetry.Metrics.ReadOnlyExemplarCollection.Enumerator.Current.get -> OpenTelemetry.Metrics.Exemplar -OpenTelemetry.Metrics.ReadOnlyExemplarCollection.Enumerator.Enumerator() -> void -OpenTelemetry.Metrics.ReadOnlyExemplarCollection.Enumerator.MoveNext() -> bool -OpenTelemetry.Metrics.ReadOnlyExemplarCollection.GetEnumerator() -> OpenTelemetry.Metrics.ReadOnlyExemplarCollection.Enumerator -OpenTelemetry.Metrics.ReadOnlyExemplarCollection.ReadOnlyExemplarCollection() -> void -OpenTelemetry.ReadOnlyFilteredTagCollection -OpenTelemetry.ReadOnlyFilteredTagCollection.Enumerator -OpenTelemetry.ReadOnlyFilteredTagCollection.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair -OpenTelemetry.ReadOnlyFilteredTagCollection.Enumerator.Enumerator() -> void -OpenTelemetry.ReadOnlyFilteredTagCollection.Enumerator.MoveNext() -> bool -OpenTelemetry.ReadOnlyFilteredTagCollection.GetEnumerator() -> OpenTelemetry.ReadOnlyFilteredTagCollection.Enumerator -OpenTelemetry.ReadOnlyFilteredTagCollection.ReadOnlyFilteredTagCollection() -> void -static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetExemplarFilter(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, OpenTelemetry.Metrics.ExemplarFilterType exemplarFilter) -> OpenTelemetry.Metrics.MeterProviderBuilder! +OpenTelemetry.Metrics.MetricStreamConfiguration.CardinalityLimit.get -> int? +OpenTelemetry.Metrics.MetricStreamConfiguration.CardinalityLimit.set -> void +OpenTelemetry.OpenTelemetrySdk +OpenTelemetry.OpenTelemetrySdk.Dispose() -> void +OpenTelemetry.OpenTelemetrySdk.LoggerProvider.get -> OpenTelemetry.Logs.LoggerProvider! +OpenTelemetry.OpenTelemetrySdk.MeterProvider.get -> OpenTelemetry.Metrics.MeterProvider! +OpenTelemetry.OpenTelemetrySdk.TracerProvider.get -> OpenTelemetry.Trace.TracerProvider! +OpenTelemetry.OpenTelemetrySdkExtensions +static OpenTelemetry.OpenTelemetrySdk.Create(System.Action! configure) -> OpenTelemetry.OpenTelemetrySdk! +static OpenTelemetry.OpenTelemetrySdkExtensions.GetLoggerFactory(this OpenTelemetry.OpenTelemetrySdk! sdk) -> Microsoft.Extensions.Logging.ILoggerFactory! diff --git a/src/OpenTelemetry/BatchExportProcessor.cs b/src/OpenTelemetry/BatchExportProcessor.cs index 8e1e2ed0e5..b377d5e89e 100644 --- a/src/OpenTelemetry/BatchExportProcessor.cs +++ b/src/OpenTelemetry/BatchExportProcessor.cs @@ -20,10 +20,10 @@ public abstract class BatchExportProcessor : BaseExportProcessor internal const int DefaultMaxExportBatchSize = 512; internal readonly int MaxExportBatchSize; + internal readonly int ScheduledDelayMilliseconds; + internal readonly int ExporterTimeoutMilliseconds; private readonly CircularBuffer circularBuffer; - private readonly int scheduledDelayMilliseconds; - private readonly int exporterTimeoutMilliseconds; private readonly Thread exporterThread; private readonly AutoResetEvent exportTrigger = new(false); private readonly ManualResetEvent dataExportedNotification = new(false); @@ -54,8 +54,8 @@ protected BatchExportProcessor( Guard.ThrowIfOutOfRange(exporterTimeoutMilliseconds, min: 0); this.circularBuffer = new CircularBuffer(maxQueueSize); - this.scheduledDelayMilliseconds = scheduledDelayMilliseconds; - this.exporterTimeoutMilliseconds = exporterTimeoutMilliseconds; + this.ScheduledDelayMilliseconds = scheduledDelayMilliseconds; + this.ExporterTimeoutMilliseconds = exporterTimeoutMilliseconds; this.MaxExportBatchSize = maxExportBatchSize; this.exporterThread = new Thread(this.ExporterProc) { @@ -252,7 +252,7 @@ private void ExporterProc() { try { - WaitHandle.WaitAny(triggers, this.scheduledDelayMilliseconds); + WaitHandle.WaitAny(triggers, this.ScheduledDelayMilliseconds); } catch (ObjectDisposedException) { diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index e34a00b7ed..5934a0ea67 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -1,11 +1,93 @@ # Changelog +This file contains individual changes for the OpenTelemetry package. For +highlights and announcements covering all components see: [Release +Notes](../../RELEASENOTES.md). + ## Unreleased +* The experimental APIs previously covered by `OTEL1003` + (`MetricStreamConfiguration.CardinalityLimit`) are now part of the public API + and supported in stable builds. + ([#5926](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5926)) + +* Promoted overflow attribute from experimental to stable and removed the + `OTEL_DOTNET_EXPERIMENTAL_METRICS_EMIT_OVERFLOW_ATTRIBUTE` environment variable. + + **Previous Behavior:** + By default, when the cardinality limit was reached, measurements were dropped, + and an internal log was emitted the first time this occurred. Users could + opt-in to experimental overflow attribute feature with + `OTEL_DOTNET_EXPERIMENTAL_METRICS_EMIT_OVERFLOW_ATTRIBUTE=true`. + With this setting, the SDK would use an overflow attribute + (`otel.metric.overflow = true`) to aggregate measurements instead of dropping + measurements. No internal log was emitted in this case. + + **New Behavior:** + The SDK now always uses the overflow attribute (`otel.metric.overflow = true`) + to aggregate measurements when the cardinality limit is reached. The previous + approach of dropping measurements has been removed. No internal logs are + emitted when the limit is hit. + + The default cardinality limit remains 2000 per metric. To set the cardinality + limit for an individual metric, use the [changing cardinality limit for a + Metric](../../docs/metrics/customizing-the-sdk/README.md#changing-the-cardinality-limit-for-a-metric). + + There is NO ability to revert to old behavior. + ([#5909](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5909)) + * Exposed a `public` constructor on `Batch` which accepts a single instance of `T` to be contained in the batch. ([#5642](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5642)) +## 1.10.0-beta.1 + +Released 2024-Sep-30 + +* Added `OpenTelemetrySdk.Create` API for configuring OpenTelemetry .NET signals + (logging, tracing, and metrics) via a single builder. This new API simplifies + bootstrap and teardown, and supports cross-cutting extensions targeting + `IOpenTelemetryBuilder`. + ([#5325](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5325)) + +* Updated the `Microsoft.Extensions.Logging.Configuration` and + `Microsoft.Extensions.Diagnostics.Abstractions` packages version to + `9.0.0-rc.1.24431.7`. + ([#5853](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5853)) + +* Added support in metrics for histogram bucket boundaries set via the .NET 9 + [InstrumentAdvice<T>](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.instrumentadvice-1) + API. + + Note: With this change explicit bucket histogram boundary resolution will + apply in the following order: + + 1. View API + 2. Advice API + 3. SDK defaults + + See [#5854](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5854) + for details. + +* Added support for collecting metrics emitted via the .NET 9 + [Gauge<T>](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.gauge-1) + API. + ([#5867](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5867)) + +## 1.9.0 + +Released 2024-Jun-14 + +## 1.9.0-rc.1 + +Released 2024-Jun-07 + +* The experimental APIs previously covered by `OTEL1000` + (`LoggerProviderBuilder` `AddProcessor` & `ConfigureResource` extensions, and + `LoggerProvider` `ForceFlush` & `Shutdown` extensions) are now part of the + public API and supported in stable builds. + ([#5648](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5648)) + ## 1.9.0-alpha.1 Released 2024-May-20 @@ -29,8 +111,8 @@ Released 2024-May-20 * The experimental APIs previously covered by `OTEL1002` (`Exemplar`, `ExemplarFilterType`, `MeterProviderBuilder.SetExemplarFilter`, `ReadOnlyExemplarCollection`, `ReadOnlyFilteredTagCollection`, & - `MetricPoint.TryGetExemplars`) will now be part of the public API and - supported in stable builds. + `MetricPoint.TryGetExemplars`) are now part of the public API and supported in + stable builds. ([#5607](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5607)) * Fixed the nullable annotations for the `SamplingResult` constructors diff --git a/src/OpenTelemetry/Internal/CircularBuffer.cs b/src/OpenTelemetry/Internal/CircularBuffer.cs index 74c3c8a6c5..7e6a6a822d 100644 --- a/src/OpenTelemetry/Internal/CircularBuffer.cs +++ b/src/OpenTelemetry/Internal/CircularBuffer.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Diagnostics; using System.Runtime.CompilerServices; namespace OpenTelemetry.Internal; @@ -65,7 +66,7 @@ public int Count /// public bool Add(T value) { - Guard.ThrowIfNull(value); + Debug.Assert(value != null, "value was null"); while (true) { @@ -104,7 +105,7 @@ public bool TryAdd(T value, int maxSpinCount) return this.Add(value); } - Guard.ThrowIfNull(value); + Debug.Assert(value != null, "value was null"); var spinCountDown = maxSpinCount; diff --git a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs index b62f4b1009..88312e3a14 100644 --- a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs +++ b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; -#if NET6_0_OR_GREATER +#if NET using System.Diagnostics.CodeAnalysis; #endif using System.Diagnostics.Tracing; @@ -215,7 +215,7 @@ public void NoDroppedExportProcessorItems(string exportProcessorName, string exp this.WriteEvent(31, exportProcessorName, exporterName); } -#if NET6_0_OR_GREATER +#if NET [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe.")] #endif [Event(32, Message = "'{0}' exporting to '{1}' dropped '{2}' item(s) due to buffer full.", Level = EventLevel.Warning)] @@ -224,7 +224,7 @@ public void ExistsDroppedExportProcessorItems(string exportProcessorName, string this.WriteEvent(32, exportProcessorName, exporterName, droppedCount); } -#if NET6_0_OR_GREATER +#if NET [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe.")] #endif [Event(33, Message = "Measurements from Instrument '{0}', Meter '{1}' will be ignored. Reason: '{2}'. Suggested action: '{3}'", Level = EventLevel.Warning)] @@ -257,7 +257,7 @@ public void ProviderDisposed(string providerName) this.WriteEvent(37, providerName); } -#if NET6_0_OR_GREATER +#if NET [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe.")] #endif [Event(38, Message = "Duplicate Instrument '{0}', Meter '{1}' encountered. Reason: '{2}'. Suggested action: '{3}'", Level = EventLevel.Warning)] @@ -278,7 +278,7 @@ public void MetricReaderEvent(string message) this.WriteEvent(40, message); } -#if NET6_0_OR_GREATER +#if NET [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe.")] #endif [Event(41, Message = "View Configuration ignored for Instrument '{0}', Meter '{1}'. Reason: '{2}'. Measurements from the instrument will use default configuration for Aggregation. Suggested action: '{3}'", Level = EventLevel.Warning)] @@ -287,7 +287,7 @@ public void MetricViewIgnored(string instrumentName, string meterName, string re this.WriteEvent(41, instrumentName, meterName, reason, fix); } -#if NET6_0_OR_GREATER +#if NET [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe.")] #endif [Event(43, Message = "ForceFlush invoked for processor type '{0}' returned result '{1}'.", Level = EventLevel.Verbose)] diff --git a/src/OpenTelemetry/Internal/SelfDiagnosticsConfigParser.cs b/src/OpenTelemetry/Internal/SelfDiagnosticsConfigParser.cs index 3216c050f7..1f23bdee77 100644 --- a/src/OpenTelemetry/Internal/SelfDiagnosticsConfigParser.cs +++ b/src/OpenTelemetry/Internal/SelfDiagnosticsConfigParser.cs @@ -66,8 +66,21 @@ public bool TryGetConfiguration( this.configBuffer = buffer; } - file.Read(buffer, 0, buffer.Length); - string configJson = Encoding.UTF8.GetString(buffer); + int bytesRead = 0; + int totalBytesRead = 0; + + while (totalBytesRead < buffer.Length) + { + bytesRead = file.Read(buffer, totalBytesRead, buffer.Length - totalBytesRead); + if (bytesRead == 0) + { + break; + } + + totalBytesRead += bytesRead; + } + + string configJson = Encoding.UTF8.GetString(buffer, 0, totalBytesRead); if (!TryParseLogDirectory(configJson, out logDirectory)) { diff --git a/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs b/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs index cdfe233fd6..2ace3eb552 100644 --- a/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs +++ b/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs @@ -16,7 +16,7 @@ internal sealed class SelfDiagnosticsEventListener : EventListener // Buffer size of the log line. A UTF-16 encoded character in C# can take up to 4 bytes if encoded in UTF-8. private const int BUFFERSIZE = 4 * 5120; private const string EventSourceNamePrefix = "OpenTelemetry-"; - private readonly object lockObj = new(); + private readonly Lock lockObj = new(); private readonly EventLevel logLevel; private readonly SelfDiagnosticsConfigRefresher configRefresher; private readonly ThreadLocal writeBuffer = new(() => null); diff --git a/src/OpenTelemetry/Logs/Builder/LoggerProviderBuilderExtensions.cs b/src/OpenTelemetry/Logs/Builder/LoggerProviderBuilderExtensions.cs index 3fad4d173d..775e7ecb5a 100644 --- a/src/OpenTelemetry/Logs/Builder/LoggerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry/Logs/Builder/LoggerProviderBuilderExtensions.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NET6_0_OR_GREATER +#if NET using System.Diagnostics.CodeAnalysis; #endif using Microsoft.Extensions.DependencyInjection; @@ -14,30 +14,8 @@ namespace OpenTelemetry.Logs; /// /// Contains extension methods for the class. /// -#if EXPOSE_EXPERIMENTAL_FEATURES -#if NET8_0_OR_GREATER -[Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif -public -#else -internal -#endif - static class LoggerProviderBuilderExtensions +public static class LoggerProviderBuilderExtensions { -#if EXPOSE_EXPERIMENTAL_FEATURES - /// - /// Sets the from which the associated with - /// this provider is built from. - /// - /// - /// - /// Note: Calling will override the currently set . - /// To modify the current call instead. - /// - /// . - /// from which Resource will be built. - /// Returns for chaining. -#else /// /// Sets the from which the associated with /// this provider is built from. @@ -49,7 +27,6 @@ static class LoggerProviderBuilderExtensions /// . /// from which Resource will be built. /// Returns for chaining. -#endif public static LoggerProviderBuilder SetResourceBuilder(this LoggerProviderBuilder loggerProviderBuilder, ResourceBuilder resourceBuilder) { Guard.ThrowIfNull(resourceBuilder); @@ -65,24 +42,13 @@ public static LoggerProviderBuilder SetResourceBuilder(this LoggerProviderBuilde return loggerProviderBuilder; } -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Modify in-place the from which the associated with /// this provider is built from. /// - /// /// . /// An action which modifies the provided in-place. /// Returns for chaining. -#else - /// - /// Modify in-place the from which the associated with - /// this provider is built from. - /// - /// . - /// An action which modifies the provided in-place. - /// Returns for chaining. -#endif public static LoggerProviderBuilder ConfigureResource(this LoggerProviderBuilder loggerProviderBuilder, Action configure) { Guard.ThrowIfNull(configure); @@ -98,22 +64,12 @@ public static LoggerProviderBuilder ConfigureResource(this LoggerProviderBuilder return loggerProviderBuilder; } -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Adds a processor to the provider. /// - /// /// . /// LogRecord processor to add. /// Returns for chaining. -#else - /// - /// Adds a processor to the provider. - /// - /// . - /// LogRecord processor to add. - /// Returns for chaining. -#endif public static LoggerProviderBuilder AddProcessor(this LoggerProviderBuilder loggerProviderBuilder, BaseProcessor processor) { Guard.ThrowIfNull(processor); @@ -129,32 +85,18 @@ public static LoggerProviderBuilder AddProcessor(this LoggerProviderBuilder logg return loggerProviderBuilder; } -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Adds a processor to the provider which will be retrieved using dependency injection. /// /// - /// /// Note: The type specified by will be /// registered as a singleton service into application services. /// /// Processor type. /// . /// The supplied for chaining. -#else - /// - /// Adds a processor to the provider which will be retrieved using dependency injection. - /// - /// - /// Note: The type specified by will be - /// registered as a singleton service into application services. - /// - /// Processor type. - /// . - /// The supplied for chaining. -#endif public static LoggerProviderBuilder AddProcessor< -#if NET6_0_OR_GREATER +#if NET [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T>(this LoggerProviderBuilder loggerProviderBuilder) @@ -173,22 +115,12 @@ public static LoggerProviderBuilder AddProcessor< return loggerProviderBuilder; } -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Adds a processor to the provider which will be retrieved using dependency injection. /// - /// /// . /// The factory that creates the service. /// The supplied for chaining. -#else - /// - /// Adds a processor to the provider which will be retrieved using dependency injection. - /// - /// . - /// The factory that creates the service. - /// The supplied for chaining. -#endif public static LoggerProviderBuilder AddProcessor( this LoggerProviderBuilder loggerProviderBuilder, Func> implementationFactory) @@ -206,20 +138,11 @@ public static LoggerProviderBuilder AddProcessor( return loggerProviderBuilder; } -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Run the given actions to initialize the . /// - /// /// . /// . -#else - /// - /// Run the given actions to initialize the . - /// - /// . - /// . -#endif public static LoggerProvider Build(this LoggerProviderBuilder loggerProviderBuilder) { if (loggerProviderBuilder is LoggerProviderBuilderBase loggerProviderBuilderBase) diff --git a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerOptions.cs b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerOptions.cs index 06b3478e54..921fd948ce 100644 --- a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerOptions.cs +++ b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerOptions.cs @@ -82,6 +82,7 @@ public class OpenTelemetryLoggerOptions /// /// Log processor to add. /// Returns for chaining. + // TODO: [Obsolete("Use LoggerProviderBuilder.AddProcessor instead this method will be removed in a future version.")] public OpenTelemetryLoggerOptions AddProcessor(BaseProcessor processor) { Guard.ThrowIfNull(processor); @@ -96,6 +97,7 @@ public OpenTelemetryLoggerOptions AddProcessor(BaseProcessor processo /// /// The factory that creates the service. /// Returns for chaining. + // TODO: [Obsolete("Use LoggerProviderBuilder.AddProcessor instead this method will be removed in a future version.")] public OpenTelemetryLoggerOptions AddProcessor( Func> implementationFactory) { @@ -112,6 +114,7 @@ public OpenTelemetryLoggerOptions AddProcessor( /// /// from which Resource will be built. /// Returns for chaining. + // TODO: [Obsolete("Use LoggerProviderBuilder.SetResourceBuilder instead this method will be removed in a future version.")] public OpenTelemetryLoggerOptions SetResourceBuilder(ResourceBuilder resourceBuilder) { Guard.ThrowIfNull(resourceBuilder); diff --git a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs index 6fdf924417..e1a68730f2 100644 --- a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs +++ b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs @@ -1,13 +1,10 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NET6_0_OR_GREATER +using System.Diagnostics; +#if NET using System.Diagnostics.CodeAnalysis; #endif -#if EXPOSE_EXPERIMENTAL_FEATURES -using System.ComponentModel; -#endif -using System.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; @@ -39,11 +36,10 @@ public static class OpenTelemetryLoggingExtensions /// /// The to use. /// The supplied for call chaining. -#if EXPOSE_EXPERIMENTAL_FEATURES - // todo: [Obsolete("Call UseOpenTelemetry instead this method will be removed in a future version.")] + /* TODO: // Note: We hide AddOpenTelemetry from IDEs using EditorBrowsable when UseOpenTelemetry is present to reduce confusion. [EditorBrowsable(EditorBrowsableState.Never)] -#endif + [Obsolete("Call UseOpenTelemetry instead this method will be removed in a future version.")] */ public static ILoggingBuilder AddOpenTelemetry( this ILoggingBuilder builder) => AddOpenTelemetryInternal(builder, configureBuilder: null, configureOptions: null); @@ -55,11 +51,10 @@ public static ILoggingBuilder AddOpenTelemetry( /// The to use. /// Optional configuration action. /// The supplied for call chaining. -#if EXPOSE_EXPERIMENTAL_FEATURES - // todo: [Obsolete("Call UseOpenTelemetry instead this method will be removed in a future version.")] + /* TODO: // Note: We hide AddOpenTelemetry from IDEs using EditorBrowsable when UseOpenTelemetry is present to reduce confusion. [EditorBrowsable(EditorBrowsableState.Never)] -#endif + [Obsolete("Call UseOpenTelemetry instead this method will be removed in a future version.")]*/ public static ILoggingBuilder AddOpenTelemetry( this ILoggingBuilder builder, Action? configure) @@ -70,31 +65,20 @@ public static ILoggingBuilder AddOpenTelemetry( /// Adds an OpenTelemetry logger named 'OpenTelemetry' to the . /// /// - /// WARNING: This is an experimental API which might change or be removed in the future. Use at your own risk. /// Note: This is safe to be called multiple times and by library authors. /// Only a single will be created /// for a given . /// /// The to use. /// The supplied for call chaining. -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public #else - /// - /// Adds an OpenTelemetry logger named 'OpenTelemetry' to the . - /// - /// - /// Note: This is safe to be called multiple times and by library authors. - /// Only a single will be created - /// for a given . - /// - /// The to use. - /// The supplied for call chaining. internal #endif - static ILoggingBuilder UseOpenTelemetry( + static ILoggingBuilder UseOpenTelemetry( this ILoggingBuilder builder) => AddOpenTelemetryInternal(builder, configureBuilder: null, configureOptions: null); @@ -104,23 +88,16 @@ static ILoggingBuilder UseOpenTelemetry( /// /// /// The to use. - /// Optional configuration action. + /// configuration action. /// The supplied for call chaining. -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public #else - /// - /// Adds an OpenTelemetry logger named 'OpenTelemetry' to the . - /// - /// - /// The to use. - /// configuration action. - /// The supplied for call chaining. internal #endif - static ILoggingBuilder UseOpenTelemetry( + static ILoggingBuilder UseOpenTelemetry( this ILoggingBuilder builder, Action configure) { @@ -138,22 +115,14 @@ static ILoggingBuilder UseOpenTelemetry( /// Optional configuration action. /// Optional configuration action. /// The supplied for call chaining. -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public #else - /// - /// Adds an OpenTelemetry logger named 'OpenTelemetry' to the . - /// - /// - /// The to use. - /// Optional configuration action. - /// Optional configuration action. - /// The supplied for call chaining. internal #endif - static ILoggingBuilder UseOpenTelemetry( + static ILoggingBuilder UseOpenTelemetry( this ILoggingBuilder builder, Action? configureBuilder, Action? configureOptions) @@ -273,7 +242,7 @@ private static ILoggingBuilder AddOpenTelemetryInternal( // and then there should be a way to do this without any warnings. // The correctness of these suppressions is verified by a test which validates that all properties of OpenTelemetryLoggerOptions // are of a primitive type. -#if NET6_0_OR_GREATER +#if NET [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "OpenTelemetryLoggerOptions contains only primitive properties.")] [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "OpenTelemetryLoggerOptions contains only primitive properties.")] #endif diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs index 58d97c38ed..8009687d55 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 && NET8_0_OR_GREATER +#if EXPOSE_EXPERIMENTAL_FEATURES && NET using System.Diagnostics.CodeAnalysis; #endif using System.Runtime.CompilerServices; @@ -355,7 +355,7 @@ public Exception? Exception /// known at the source. /// /// -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public @@ -377,7 +377,7 @@ public Exception? Exception /// Gets or sets the log . /// /// -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public @@ -405,7 +405,7 @@ public Exception? Exception /// typically the which emitted the however the value may be different if is modified. -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public Logger Logger { get; internal set; } = InstrumentationScopeLogger.Default; diff --git a/src/OpenTelemetry/Logs/LoggerProviderExtensions.cs b/src/OpenTelemetry/Logs/LoggerProviderExtensions.cs index 66dbac5ad4..f5d795fba2 100644 --- a/src/OpenTelemetry/Logs/LoggerProviderExtensions.cs +++ b/src/OpenTelemetry/Logs/LoggerProviderExtensions.cs @@ -1,9 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if EXPOSE_EXPERIMENTAL_FEATURES && NET8_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -#endif using OpenTelemetry.Internal; namespace OpenTelemetry.Logs; @@ -11,22 +8,12 @@ namespace OpenTelemetry.Logs; /// /// Contains extension methods for the class. /// -#if EXPOSE_EXPERIMENTAL_FEATURES -#if NET8_0_OR_GREATER -[Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif -public -#else -internal -#endif - static class LoggerProviderExtensions +public static class LoggerProviderExtensions { -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Add a processor to the . /// /// - /// /// Note: The supplied will be /// automatically disposed when then the is disposed. @@ -34,19 +21,6 @@ static class LoggerProviderExtensions /// instance on which ForceFlush will be called. /// Log processor to add. /// The supplied for chaining. -#else - /// - /// Add a processor to the . - /// - /// - /// Note: The supplied will be - /// automatically disposed when then the is disposed. - /// - /// instance on which ForceFlush will be called. - /// Log processor to add. - /// The supplied for chaining. -#endif public static LoggerProvider AddProcessor(this LoggerProvider provider, BaseProcessor processor) { Guard.ThrowIfNull(provider); @@ -60,27 +34,6 @@ public static LoggerProvider AddProcessor(this LoggerProvider provider, BaseProc return provider; } -#if EXPOSE_EXPERIMENTAL_FEATURES - /// - /// Flushes all the processors registered under , blocks the current thread - /// until flush completed, shutdown signaled or timed out. - /// - /// instance on which ForceFlush will be called. - /// - /// The number (non-negative) of milliseconds to wait, or - /// Timeout.Infinite to wait indefinitely. - /// - /// - /// Returns true when force flush succeeded; otherwise, false. - /// - /// - /// Thrown when the timeoutMilliseconds is smaller than -1. - /// - /// - /// - /// This function guarantees thread-safety. - /// -#else /// /// Flushes all the processors registered under , blocks the current thread /// until flush completed, shutdown signaled or timed out. @@ -99,7 +52,6 @@ public static LoggerProvider AddProcessor(this LoggerProvider provider, BaseProc /// /// This function guarantees thread-safety. /// -#endif public static bool ForceFlush(this LoggerProvider provider, int timeoutMilliseconds = Timeout.Infinite) { Guard.ThrowIfNull(provider); @@ -113,28 +65,6 @@ public static bool ForceFlush(this LoggerProvider provider, int timeoutMilliseco return true; } -#if EXPOSE_EXPERIMENTAL_FEATURES - /// - /// Attempts to shutdown the , blocks the current thread until - /// shutdown completed or timed out. - /// - /// instance on which Shutdown will be called. - /// - /// The number (non-negative) of milliseconds to wait, or - /// Timeout.Infinite to wait indefinitely. - /// - /// - /// Returns true when shutdown succeeded; otherwise, false. - /// - /// - /// Thrown when the timeoutMilliseconds is smaller than -1. - /// - /// - /// - /// This function guarantees thread-safety. Only the first call will - /// win, subsequent calls will be no-op. - /// -#else /// /// Attempts to shutdown the , blocks the current thread until /// shutdown completed or timed out. @@ -154,7 +84,6 @@ public static bool ForceFlush(this LoggerProvider provider, int timeoutMilliseco /// This function guarantees thread-safety. Only the first call will /// win, subsequent calls will be no-op. /// -#endif public static bool Shutdown(this LoggerProvider provider, int timeoutMilliseconds = Timeout.Infinite) { Guard.ThrowIfNull(provider); diff --git a/src/OpenTelemetry/Logs/LoggerProviderSdk.cs b/src/OpenTelemetry/Logs/LoggerProviderSdk.cs index f3fc00fe8a..4287fcad3a 100644 --- a/src/OpenTelemetry/Logs/LoggerProviderSdk.cs +++ b/src/OpenTelemetry/Logs/LoggerProviderSdk.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; -#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET using System.Diagnostics.CodeAnalysis; #endif using System.Text; @@ -194,9 +194,14 @@ public bool ContainsBatchProcessor(BaseProcessor processor) } /// - protected override bool TryCreateLogger( +#if EXPOSE_EXPERIMENTAL_FEATURES + protected +#else + internal +#endif + override bool TryCreateLogger( string? name, -#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET [NotNullWhen(true)] #endif out Logger? logger) diff --git a/src/OpenTelemetry/Logs/BatchExportLogRecordProcessorOptions.cs b/src/OpenTelemetry/Logs/Processor/BatchExportLogRecordProcessorOptions.cs similarity index 100% rename from src/OpenTelemetry/Logs/BatchExportLogRecordProcessorOptions.cs rename to src/OpenTelemetry/Logs/Processor/BatchExportLogRecordProcessorOptions.cs diff --git a/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs b/src/OpenTelemetry/Logs/Processor/BatchLogRecordExportProcessor.cs similarity index 100% rename from src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs rename to src/OpenTelemetry/Logs/Processor/BatchLogRecordExportProcessor.cs diff --git a/src/OpenTelemetry/Logs/LogRecordExportProcessorOptions.cs b/src/OpenTelemetry/Logs/Processor/LogRecordExportProcessorOptions.cs similarity index 100% rename from src/OpenTelemetry/Logs/LogRecordExportProcessorOptions.cs rename to src/OpenTelemetry/Logs/Processor/LogRecordExportProcessorOptions.cs diff --git a/src/OpenTelemetry/Logs/SimpleLogRecordExportProcessor.cs b/src/OpenTelemetry/Logs/Processor/SimpleLogRecordExportProcessor.cs similarity index 100% rename from src/OpenTelemetry/Logs/SimpleLogRecordExportProcessor.cs rename to src/OpenTelemetry/Logs/Processor/SimpleLogRecordExportProcessor.cs diff --git a/src/OpenTelemetry/Metrics/AggregatorStore.cs b/src/OpenTelemetry/Metrics/AggregatorStore.cs index 34a4bb5f2c..b7d560b559 100644 --- a/src/OpenTelemetry/Metrics/AggregatorStore.cs +++ b/src/OpenTelemetry/Metrics/AggregatorStore.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Collections.Concurrent; -#if NET8_0_OR_GREATER +#if NET using System.Collections.Frozen; #endif using System.Diagnostics; @@ -14,7 +14,7 @@ namespace OpenTelemetry.Metrics; internal sealed class AggregatorStore { -#if NET8_0_OR_GREATER +#if NET internal readonly FrozenSet? TagKeysInteresting; #else internal readonly HashSet? TagKeysInteresting; @@ -22,17 +22,15 @@ internal sealed class AggregatorStore internal readonly bool OutputDelta; internal readonly bool OutputDeltaWithUnusedMetricPointReclaimEnabled; internal readonly int NumberOfMetricPoints; - internal readonly bool EmitOverflowAttribute; internal readonly ConcurrentDictionary? TagsToMetricPointIndexDictionaryDelta; internal readonly Func? ExemplarReservoirFactory; internal long DroppedMeasurements = 0; private const ExemplarFilterType DefaultExemplarFilter = ExemplarFilterType.AlwaysOff; - private static readonly string MetricPointCapHitFixMessage = "Consider opting in for the experimental SDK feature to emit all the throttled metrics under the overflow attribute by setting env variable OTEL_DOTNET_EXPERIMENTAL_METRICS_EMIT_OVERFLOW_ATTRIBUTE = true. You could also modify instrumentation to reduce the number of unique key/value pair combinations. Or use Views to drop unwanted tags. Or use MeterProviderBuilder.SetMaxMetricPointsPerMetricStream to set higher limit."; private static readonly Comparison> DimensionComparisonDelegate = (x, y) => x.Key.CompareTo(y.Key); - private readonly object lockZeroTags = new(); - private readonly object lockOverflowTag = new(); + private readonly Lock lockZeroTags = new(); + private readonly Lock lockOverflowTag = new(); private readonly int tagsKeysInterestingCount; // This holds the reclaimed MetricPoints that are available for reuse. @@ -42,7 +40,6 @@ internal sealed class AggregatorStore new(); private readonly string name; - private readonly string metricPointCapHitMessage; private readonly MetricPoint[] metricPoints; private readonly int[] currentMetricPointBatch; private readonly AggregationType aggType; @@ -56,7 +53,6 @@ internal sealed class AggregatorStore private int metricPointIndex = 0; private int batchSize = 0; - private int metricCapHitMessageLogged; private bool zeroTagMetricPointInitialized; private bool overflowTagMetricPointInitialized; @@ -65,7 +61,6 @@ internal AggregatorStore( AggregationType aggType, AggregationTemporality temporality, int cardinalityLimit, - bool emitOverflowAttribute, bool shouldReclaimUnusedMetricPoints, ExemplarFilterType? exemplarFilter = null, Func? exemplarReservoirFactory = null) @@ -77,7 +72,6 @@ internal AggregatorStore( // Previously, these were included within the original cardinalityLimit, but now they are explicitly added to enhance clarity. this.NumberOfMetricPoints = cardinalityLimit + 2; - this.metricPointCapHitMessage = $"Maximum MetricPoints limit reached for this Metric stream. Configured limit: {cardinalityLimit}"; this.metricPoints = new MetricPoint[this.NumberOfMetricPoints]; this.currentMetricPointBatch = new int[this.NumberOfMetricPoints]; this.aggType = aggType; @@ -96,7 +90,7 @@ internal AggregatorStore( { this.updateLongCallback = this.UpdateLongCustomTags; this.updateDoubleCallback = this.UpdateDoubleCustomTags; -#if NET8_0_OR_GREATER +#if NET var hs = FrozenSet.ToFrozenSet(metricStreamIdentity.TagKeys, StringComparer.Ordinal); #else var hs = new HashSet(metricStreamIdentity.TagKeys, StringComparer.Ordinal); @@ -105,8 +99,6 @@ internal AggregatorStore( this.tagsKeysInterestingCount = hs.Count; } - this.EmitOverflowAttribute = emitOverflowAttribute; - this.exemplarFilter = exemplarFilter ?? DefaultExemplarFilter; Debug.Assert( this.exemplarFilter == ExemplarFilterType.AlwaysOff @@ -245,17 +237,14 @@ internal void SnapshotDeltaWithMetricPointReclaim() this.batchSize++; } - if (this.EmitOverflowAttribute) + // TakeSnapshot for the MetricPoint for overflow + ref var metricPointForOverflow = ref this.metricPoints[1]; + if (metricPointForOverflow.MetricPointStatus != MetricPointStatus.NoCollectPending) { - // TakeSnapshot for the MetricPoint for overflow - ref var metricPointForOverflow = ref this.metricPoints[1]; - if (metricPointForOverflow.MetricPointStatus != MetricPointStatus.NoCollectPending) - { - this.TakeMetricPointSnapshot(ref metricPointForOverflow, outputDelta: true); + this.TakeMetricPointSnapshot(ref metricPointForOverflow, outputDelta: true); - this.currentMetricPointBatch[this.batchSize] = 1; - this.batchSize++; - } + this.currentMetricPointBatch[this.batchSize] = 1; + this.batchSize++; } // Index 0 and 1 are reserved for no tags and overflow @@ -994,16 +983,8 @@ private void UpdateLongMetricPoint(int metricPointIndex, long value, ReadOnlySpa if (metricPointIndex < 0) { Interlocked.Increment(ref this.DroppedMeasurements); - - if (this.EmitOverflowAttribute) - { - this.InitializeOverflowTagPointIfNotInitialized(); - this.metricPoints[1].Update(value); - } - else if (Interlocked.CompareExchange(ref this.metricCapHitMessageLogged, 1, 0) == 0) - { - OpenTelemetrySdkEventSource.Log.MeasurementDropped(this.name, this.metricPointCapHitMessage, MetricPointCapHitFixMessage); - } + this.InitializeOverflowTagPointIfNotInitialized(); + this.metricPoints[1].Update(value); return; } @@ -1049,16 +1030,8 @@ private void UpdateDoubleMetricPoint(int metricPointIndex, double value, ReadOnl if (metricPointIndex < 0) { Interlocked.Increment(ref this.DroppedMeasurements); - - if (this.EmitOverflowAttribute) - { - this.InitializeOverflowTagPointIfNotInitialized(); - this.metricPoints[1].Update(value); - } - else if (Interlocked.CompareExchange(ref this.metricCapHitMessageLogged, 1, 0) == 0) - { - OpenTelemetrySdkEventSource.Log.MeasurementDropped(this.name, this.metricPointCapHitMessage, MetricPointCapHitFixMessage); - } + this.InitializeOverflowTagPointIfNotInitialized(); + this.metricPoints[1].Update(value); return; } diff --git a/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs index 4eea35b59c..1a5c1d950c 100644 --- a/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NET6_0_OR_GREATER +#if NET using System.Diagnostics.CodeAnalysis; #endif using System.Diagnostics.Metrics; @@ -50,7 +50,7 @@ public static MeterProviderBuilder AddReader(this MeterProviderBuilder meterProv /// . /// The supplied for chaining. public static MeterProviderBuilder AddReader< -#if NET6_0_OR_GREATER +#if NET [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T>(this MeterProviderBuilder meterProviderBuilder) @@ -238,9 +238,7 @@ public static MeterProviderBuilder SetMaxMetricStreams(this MeterProviderBuilder /// . /// Maximum number of metric points allowed per metric stream. /// The supplied for chaining. -#if EXPOSE_EXPERIMENTAL_FEATURES - [Obsolete("Use MetricStreamConfiguration.CardinalityLimit via the AddView API instead. This method will be removed in a future version.")] -#endif + [Obsolete("Use MetricStreamConfiguration.CardinalityLimit via the AddView API instead. This method is marked as obsolete in version 1.10.0 and will be removed in a future version.")] public static MeterProviderBuilder SetMaxMetricPointsPerMetricStream(this MeterProviderBuilder meterProviderBuilder, int maxMetricPointsPerMetricStream) { Guard.ThrowIfOutOfRange(maxMetricPointsPerMetricStream, min: 1); diff --git a/src/OpenTelemetry/Metrics/Exemplar/Exemplar.cs b/src/OpenTelemetry/Metrics/Exemplar/Exemplar.cs index c53a7abf8b..d1801afdd3 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/Exemplar.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/Exemplar.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NET8_0_OR_GREATER +#if NET using System.Collections.Frozen; #endif using System.Diagnostics; @@ -17,7 +17,7 @@ namespace OpenTelemetry.Metrics; /// public struct Exemplar { -#if NET8_0_OR_GREATER +#if NET internal FrozenSet? ViewDefinedTagKeys; #else internal HashSet? ViewDefinedTagKeys; diff --git a/src/OpenTelemetry/Metrics/Exemplar/ExemplarMeasurement.cs b/src/OpenTelemetry/Metrics/Exemplar/ExemplarMeasurement.cs index eb3882e6e0..70d6ff3e76 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 && NET8_0_OR_GREATER +#if EXPOSE_EXPERIMENTAL_FEATURES && NET using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Internal; #endif @@ -14,7 +14,7 @@ namespace OpenTelemetry.Metrics; /// /// /// Measurement type. -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.ExemplarReservoirExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public diff --git a/src/OpenTelemetry/Metrics/Exemplar/ExemplarReservoir.cs b/src/OpenTelemetry/Metrics/Exemplar/ExemplarReservoir.cs index 4eb800921f..0070074bed 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 && NET8_0_OR_GREATER +#if EXPOSE_EXPERIMENTAL_FEATURES && NET using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Internal; #endif @@ -17,7 +17,7 @@ namespace OpenTelemetry.Metrics; /// Specification: . /// -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.ExemplarReservoirExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public diff --git a/src/OpenTelemetry/Metrics/Exemplar/FixedSizeExemplarReservoir.cs b/src/OpenTelemetry/Metrics/Exemplar/FixedSizeExemplarReservoir.cs index dc929e725e..bca577db0b 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 && NET8_0_OR_GREATER +#if EXPOSE_EXPERIMENTAL_FEATURES && NET using System.Diagnostics.CodeAnalysis; #endif using OpenTelemetry.Internal; @@ -14,7 +14,7 @@ namespace OpenTelemetry.Metrics; /// number of s. /// /// -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.ExemplarReservoirExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public diff --git a/src/OpenTelemetry/Metrics/MeterProviderSdk.cs b/src/OpenTelemetry/Metrics/MeterProviderSdk.cs index 467da3f538..a83666bb4c 100644 --- a/src/OpenTelemetry/Metrics/MeterProviderSdk.cs +++ b/src/OpenTelemetry/Metrics/MeterProviderSdk.cs @@ -13,7 +13,6 @@ namespace OpenTelemetry.Metrics; internal sealed class MeterProviderSdk : MeterProvider { - internal const string EmitOverFlowAttributeConfigKey = "OTEL_DOTNET_EXPERIMENTAL_METRICS_EMIT_OVERFLOW_ATTRIBUTE"; internal const string ReclaimUnusedMetricPointsConfigKey = "OTEL_DOTNET_EXPERIMENTAL_METRICS_RECLAIM_UNUSED_METRIC_POINTS"; internal const string ExemplarFilterConfigKey = "OTEL_METRICS_EXEMPLAR_FILTER"; internal const string ExemplarFilterHistogramsConfigKey = "OTEL_DOTNET_EXPERIMENTAL_METRICS_EXEMPLAR_FILTER_HISTOGRAMS"; @@ -22,7 +21,6 @@ internal sealed class MeterProviderSdk : MeterProvider internal readonly IDisposable? OwnedServiceProvider; internal int ShutdownCount; internal bool Disposed; - internal bool EmitOverflowAttribute; internal bool ReclaimUnusedMetricPoints; internal ExemplarFilterType? ExemplarFilter; internal ExemplarFilterType? ExemplarFilterForHistograms; @@ -30,7 +28,7 @@ internal sealed class MeterProviderSdk : MeterProvider private readonly List instrumentations = new(); private readonly List> viewConfigs; - private readonly object collectLock = new(); + private readonly Lock collectLock = new(); private readonly MeterListener listener; private readonly MetricReader? reader; private readonly CompositeMetricReader? compositeMetricReader; @@ -75,7 +73,7 @@ internal MeterProviderSdk( this.viewConfigs = state.ViewConfigs; OpenTelemetrySdkEventSource.Log.MeterProviderSdkEvent( - $"MeterProvider configuration: {{MetricLimit={state.MetricLimit}, CardinalityLimit={state.CardinalityLimit}, EmitOverflowAttribute={this.EmitOverflowAttribute}, ReclaimUnusedMetricPoints={this.ReclaimUnusedMetricPoints}, ExemplarFilter={this.ExemplarFilter}, ExemplarFilterForHistograms={this.ExemplarFilterForHistograms}}}."); + $"MeterProvider configuration: {{MetricLimit={state.MetricLimit}, CardinalityLimit={state.CardinalityLimit}, ReclaimUnusedMetricPoints={this.ReclaimUnusedMetricPoints}, ExemplarFilter={this.ExemplarFilter}, ExemplarFilterForHistograms={this.ExemplarFilterForHistograms}}}."); foreach (var reader in state.Readers) { @@ -86,7 +84,6 @@ internal MeterProviderSdk( reader.ApplyParentProviderSettings( state.MetricLimit, state.CardinalityLimit, - this.EmitOverflowAttribute, this.ReclaimUnusedMetricPoints, this.ExemplarFilter, this.ExemplarFilterForHistograms); @@ -486,11 +483,6 @@ protected override void Dispose(bool disposing) private void ApplySpecificationConfigurationKeys(IConfiguration configuration) { - if (configuration.TryGetBoolValue(OpenTelemetrySdkEventSource.Log, EmitOverFlowAttributeConfigKey, out this.EmitOverflowAttribute)) - { - OpenTelemetrySdkEventSource.Log.MeterProviderSdkEvent("Overflow attribute feature enabled via configuration."); - } - if (configuration.TryGetBoolValue(OpenTelemetrySdkEventSource.Log, ReclaimUnusedMetricPointsConfigKey, out this.ReclaimUnusedMetricPoints)) { OpenTelemetrySdkEventSource.Log.MeterProviderSdkEvent("Reclaim unused metric point feature enabled via configuration."); diff --git a/src/OpenTelemetry/Metrics/Metric.cs b/src/OpenTelemetry/Metrics/Metric.cs index 7801c2dd45..38abf4cc62 100644 --- a/src/OpenTelemetry/Metrics/Metric.cs +++ b/src/OpenTelemetry/Metrics/Metric.cs @@ -1,6 +1,9 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +#if NET +using System.Collections.Frozen; +#endif using System.Diagnostics.Metrics; namespace OpenTelemetry.Metrics; @@ -18,7 +21,13 @@ public sealed class Metric // Short default histogram bounds. Based on the recommended semantic convention values for http.server.request.duration. internal static readonly double[] DefaultHistogramBoundsShortSeconds = new double[] { 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10 }; - internal static readonly HashSet<(string, string)> DefaultHistogramBoundShortMappings = new() + internal static readonly +#if NET + FrozenSet<(string, string)> +#else + HashSet<(string, string)> +#endif + DefaultHistogramBoundShortMappings = new HashSet<(string, string)> { ("Microsoft.AspNetCore.Hosting", "http.server.request.duration"), ("Microsoft.AspNetCore.RateLimiting", "aspnetcore.rate_limiting.request.time_in_queue"), @@ -30,16 +39,30 @@ public sealed class Metric ("System.Net.Http", "http.client.request.duration"), ("System.Net.Http", "http.client.request.time_in_queue"), ("System.Net.NameResolution", "dns.lookup.duration"), - }; + } +#if NET + .ToFrozenSet() +#endif + ; // Long default histogram bounds. Not based on a standard. May change in the future. internal static readonly double[] DefaultHistogramBoundsLongSeconds = new double[] { 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300 }; - internal static readonly HashSet<(string, string)> DefaultHistogramBoundLongMappings = new() + internal static readonly +#if NET + FrozenSet<(string, string)> +#else + HashSet<(string, string)> +#endif + DefaultHistogramBoundLongMappings = new HashSet<(string, string)> { ("Microsoft.AspNetCore.Http.Connections", "signalr.server.connection.duration"), ("Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration"), ("System.Net.Http", "http.client.connection.duration"), - }; + } +#if NET + .ToFrozenSet() +#endif + ; internal readonly AggregatorStore AggregatorStore; @@ -47,7 +70,6 @@ internal Metric( MetricStreamIdentity instrumentIdentity, AggregationTemporality temporality, int cardinalityLimit, - bool emitOverflowAttribute, bool shouldReclaimUnusedMetricPoints, ExemplarFilterType? exemplarFilter = null, Func? exemplarReservoirFactory = null) @@ -117,6 +139,12 @@ internal Metric( aggType = AggregationType.DoubleGauge; this.MetricType = MetricType.DoubleGauge; } + else if (instrumentIdentity.InstrumentType == typeof(Gauge) + || instrumentIdentity.InstrumentType == typeof(Gauge)) + { + aggType = AggregationType.DoubleGauge; + this.MetricType = MetricType.DoubleGauge; + } else if (instrumentIdentity.InstrumentType == typeof(ObservableGauge) || instrumentIdentity.InstrumentType == typeof(ObservableGauge) || instrumentIdentity.InstrumentType == typeof(ObservableGauge) @@ -125,6 +153,14 @@ internal Metric( aggType = AggregationType.LongGauge; this.MetricType = MetricType.LongGauge; } + else if (instrumentIdentity.InstrumentType == typeof(Gauge) + || instrumentIdentity.InstrumentType == typeof(Gauge) + || instrumentIdentity.InstrumentType == typeof(Gauge) + || instrumentIdentity.InstrumentType == typeof(Gauge)) + { + aggType = AggregationType.LongGauge; + this.MetricType = MetricType.LongGauge; + } else if (instrumentIdentity.IsHistogram) { var explicitBucketBounds = instrumentIdentity.HistogramBucketBounds; @@ -156,7 +192,6 @@ internal Metric( aggType, temporality, cardinalityLimit, - emitOverflowAttribute, shouldReclaimUnusedMetricPoints, exemplarFilter, exemplarReservoirFactory); diff --git a/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogram.cs b/src/OpenTelemetry/Metrics/MetricPoint/Base2ExponentialBucketHistogram.cs similarity index 100% rename from src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogram.cs rename to src/OpenTelemetry/Metrics/MetricPoint/Base2ExponentialBucketHistogram.cs diff --git a/src/OpenTelemetry/Metrics/ExponentialHistogramBuckets.cs b/src/OpenTelemetry/Metrics/MetricPoint/ExponentialHistogramBuckets.cs similarity index 100% rename from src/OpenTelemetry/Metrics/ExponentialHistogramBuckets.cs rename to src/OpenTelemetry/Metrics/MetricPoint/ExponentialHistogramBuckets.cs diff --git a/src/OpenTelemetry/Metrics/ExponentialHistogramData.cs b/src/OpenTelemetry/Metrics/MetricPoint/ExponentialHistogramData.cs similarity index 100% rename from src/OpenTelemetry/Metrics/ExponentialHistogramData.cs rename to src/OpenTelemetry/Metrics/MetricPoint/ExponentialHistogramData.cs diff --git a/src/OpenTelemetry/Metrics/HistogramBucket.cs b/src/OpenTelemetry/Metrics/MetricPoint/HistogramBucket.cs similarity index 100% rename from src/OpenTelemetry/Metrics/HistogramBucket.cs rename to src/OpenTelemetry/Metrics/MetricPoint/HistogramBucket.cs diff --git a/src/OpenTelemetry/Metrics/HistogramBuckets.cs b/src/OpenTelemetry/Metrics/MetricPoint/HistogramBuckets.cs similarity index 100% rename from src/OpenTelemetry/Metrics/HistogramBuckets.cs rename to src/OpenTelemetry/Metrics/MetricPoint/HistogramBuckets.cs diff --git a/src/OpenTelemetry/Metrics/MetricPoint.cs b/src/OpenTelemetry/Metrics/MetricPoint/MetricPoint.cs similarity index 100% rename from src/OpenTelemetry/Metrics/MetricPoint.cs rename to src/OpenTelemetry/Metrics/MetricPoint/MetricPoint.cs diff --git a/src/OpenTelemetry/Metrics/MetricPointOptionalComponents.cs b/src/OpenTelemetry/Metrics/MetricPoint/MetricPointOptionalComponents.cs similarity index 100% rename from src/OpenTelemetry/Metrics/MetricPointOptionalComponents.cs rename to src/OpenTelemetry/Metrics/MetricPoint/MetricPointOptionalComponents.cs diff --git a/src/OpenTelemetry/Metrics/MetricPointStatus.cs b/src/OpenTelemetry/Metrics/MetricPoint/MetricPointStatus.cs similarity index 100% rename from src/OpenTelemetry/Metrics/MetricPointStatus.cs rename to src/OpenTelemetry/Metrics/MetricPoint/MetricPointStatus.cs diff --git a/src/OpenTelemetry/Metrics/MetricPointValueStorage.cs b/src/OpenTelemetry/Metrics/MetricPoint/MetricPointValueStorage.cs similarity index 100% rename from src/OpenTelemetry/Metrics/MetricPointValueStorage.cs rename to src/OpenTelemetry/Metrics/MetricPoint/MetricPointValueStorage.cs diff --git a/src/OpenTelemetry/Metrics/MetricPointsAccessor.cs b/src/OpenTelemetry/Metrics/MetricPoint/MetricPointsAccessor.cs similarity index 100% rename from src/OpenTelemetry/Metrics/MetricPointsAccessor.cs rename to src/OpenTelemetry/Metrics/MetricPoint/MetricPointsAccessor.cs diff --git a/src/OpenTelemetry/Metrics/MetricStreamIdentity.cs b/src/OpenTelemetry/Metrics/MetricStreamIdentity.cs index 712e5eda2a..a9918fb6ea 100644 --- a/src/OpenTelemetry/Metrics/MetricStreamIdentity.cs +++ b/src/OpenTelemetry/Metrics/MetricStreamIdentity.cs @@ -22,12 +22,12 @@ public MetricStreamIdentity(Instrument instrument, MetricStreamConfiguration? me this.ViewId = metricStreamConfiguration?.ViewId; this.MetricStreamName = $"{this.MeterName}.{this.MeterVersion}.{this.InstrumentName}"; this.TagKeys = metricStreamConfiguration?.CopiedTagKeys; - this.HistogramBucketBounds = (metricStreamConfiguration as ExplicitBucketHistogramConfiguration)?.CopiedBoundaries; + this.HistogramBucketBounds = GetExplicitBucketHistogramBounds(instrument, metricStreamConfiguration); this.ExponentialHistogramMaxSize = (metricStreamConfiguration as Base2ExponentialBucketHistogramConfiguration)?.MaxSize ?? 0; this.ExponentialHistogramMaxScale = (metricStreamConfiguration as Base2ExponentialBucketHistogramConfiguration)?.MaxScale ?? 0; this.HistogramRecordMinMax = (metricStreamConfiguration as HistogramConfiguration)?.RecordMinMax ?? true; -#if NET6_0_OR_GREATER +#if NET HashCode hashCode = default; hashCode.Add(this.InstrumentType); hashCode.Add(this.MeterName); @@ -150,6 +150,52 @@ public bool Equals(MetricStreamIdentity other) public override readonly int GetHashCode() => this.hashCode; + private static double[]? GetExplicitBucketHistogramBounds(Instrument instrument, MetricStreamConfiguration? metricStreamConfiguration) + { + if (metricStreamConfiguration is ExplicitBucketHistogramConfiguration explicitBucketHistogramConfiguration + && explicitBucketHistogramConfiguration.CopiedBoundaries != null) + { + return explicitBucketHistogramConfiguration.CopiedBoundaries; + } + + return instrument switch + { + Histogram longHistogram => GetExplicitBucketHistogramBoundsFromAdvice(longHistogram), + Histogram intHistogram => GetExplicitBucketHistogramBoundsFromAdvice(intHistogram), + Histogram shortHistogram => GetExplicitBucketHistogramBoundsFromAdvice(shortHistogram), + Histogram byteHistogram => GetExplicitBucketHistogramBoundsFromAdvice(byteHistogram), + Histogram floatHistogram => GetExplicitBucketHistogramBoundsFromAdvice(floatHistogram), + Histogram doubleHistogram => GetExplicitBucketHistogramBoundsFromAdvice(doubleHistogram), + _ => null, + }; + } + + private static double[]? GetExplicitBucketHistogramBoundsFromAdvice(Histogram histogram) + where T : struct + { + var adviceExplicitBucketBoundaries = histogram.Advice?.HistogramBucketBoundaries; + if (adviceExplicitBucketBoundaries == null) + { + return null; + } + + if (typeof(T) == typeof(double)) + { + return ((IReadOnlyList)adviceExplicitBucketBoundaries).ToArray(); + } + else + { + double[] explicitBucketBoundaries = new double[adviceExplicitBucketBoundaries.Count]; + + for (int i = 0; i < adviceExplicitBucketBoundaries.Count; i++) + { + explicitBucketBoundaries[i] = Convert.ToDouble(adviceExplicitBucketBoundaries[i]); + } + + return explicitBucketBoundaries; + } + } + private static bool HistogramBoundsEqual(double[]? bounds1, double[]? bounds2) { if (ReferenceEquals(bounds1, bounds2)) diff --git a/src/OpenTelemetry/Metrics/BaseExportingMetricReader.cs b/src/OpenTelemetry/Metrics/Reader/BaseExportingMetricReader.cs similarity index 100% rename from src/OpenTelemetry/Metrics/BaseExportingMetricReader.cs rename to src/OpenTelemetry/Metrics/Reader/BaseExportingMetricReader.cs diff --git a/src/OpenTelemetry/Metrics/CompositeMetricReader.cs b/src/OpenTelemetry/Metrics/Reader/CompositeMetricReader.cs similarity index 100% rename from src/OpenTelemetry/Metrics/CompositeMetricReader.cs rename to src/OpenTelemetry/Metrics/Reader/CompositeMetricReader.cs diff --git a/src/OpenTelemetry/Metrics/CompositeMetricReaderExt.cs b/src/OpenTelemetry/Metrics/Reader/CompositeMetricReaderExt.cs similarity index 100% rename from src/OpenTelemetry/Metrics/CompositeMetricReaderExt.cs rename to src/OpenTelemetry/Metrics/Reader/CompositeMetricReaderExt.cs diff --git a/src/OpenTelemetry/Metrics/MetricReader.cs b/src/OpenTelemetry/Metrics/Reader/MetricReader.cs similarity index 98% rename from src/OpenTelemetry/Metrics/MetricReader.cs rename to src/OpenTelemetry/Metrics/Reader/MetricReader.cs index decbf8ad70..52353a1155 100644 --- a/src/OpenTelemetry/Metrics/MetricReader.cs +++ b/src/OpenTelemetry/Metrics/Reader/MetricReader.cs @@ -27,6 +27,7 @@ public abstract partial class MetricReader : IDisposable // Temporality is not defined for gauges, so this does not really affect anything. var type when type == typeof(ObservableGauge<>) => AggregationTemporality.Delta, + var type when type == typeof(Gauge<>) => AggregationTemporality.Delta, var type when type == typeof(UpDownCounter<>) => AggregationTemporality.Cumulative, var type when type == typeof(ObservableUpDownCounter<>) => AggregationTemporality.Cumulative, @@ -36,8 +37,8 @@ public abstract partial class MetricReader : IDisposable }; }; - private readonly object newTaskLock = new(); - private readonly object onCollectLock = new(); + private readonly Lock newTaskLock = new(); + private readonly Lock onCollectLock = new(); private readonly TaskCompletionSource shutdownTcs = new(); private MetricReaderTemporalityPreference temporalityPreference = MetricReaderTemporalityPreferenceUnspecified; private Func temporalityFunc = CumulativeTemporalityPreferenceFunc; diff --git a/src/OpenTelemetry/Metrics/MetricReaderExt.cs b/src/OpenTelemetry/Metrics/Reader/MetricReaderExt.cs similarity index 95% rename from src/OpenTelemetry/Metrics/MetricReaderExt.cs rename to src/OpenTelemetry/Metrics/Reader/MetricReaderExt.cs index a6a0a642d4..194383a37a 100644 --- a/src/OpenTelemetry/Metrics/MetricReaderExt.cs +++ b/src/OpenTelemetry/Metrics/Reader/MetricReaderExt.cs @@ -15,14 +15,13 @@ namespace OpenTelemetry.Metrics; public abstract partial class MetricReader { private readonly HashSet metricStreamNames = new(StringComparer.OrdinalIgnoreCase); - private readonly ConcurrentDictionary instrumentIdentityToMetric = new(); - private readonly object instrumentCreationLock = new(); + private readonly ConcurrentDictionary instrumentIdentityToMetric = new(); + private readonly Lock instrumentCreationLock = new(); private int metricLimit; private int cardinalityLimit; private Metric?[]? metrics; private Metric[]? metricsCurrentBatch; private int metricIndex = -1; - private bool emitOverflowAttribute; private bool reclaimUnusedMetricPoints; private ExemplarFilterType? exemplarFilter; private ExemplarFilterType? exemplarFilterForHistograms; @@ -82,7 +81,6 @@ internal virtual List AddMetricWithNoViews(Instrument instrument) metricStreamIdentity, this.GetAggregationTemporality(metricStreamIdentity.InstrumentType), this.cardinalityLimit, - this.emitOverflowAttribute, this.reclaimUnusedMetricPoints, exemplarFilter); } @@ -164,7 +162,6 @@ internal virtual List AddMetricWithViews(Instrument instrument, List AddMetricWithViews(Instrument instrument, List this.instrumentIdentityToMetric.TryGetValue(metricStreamIdentity, out existingMetric) - && existingMetric.Active; + && existingMetric != null && existingMetric.Active; private void CreateOrUpdateMetricStreamRegistration(in MetricStreamIdentity metricStreamIdentity) { @@ -268,8 +263,12 @@ private void RemoveMetric(ref Metric? metric) OpenTelemetrySdkEventSource.Log.MetricInstrumentRemoved(metric!.Name, metric.MeterName); - var result = this.instrumentIdentityToMetric.TryRemove(metric.InstrumentIdentity, out var _); - Debug.Assert(result, "result was false"); + // Note: This is using TryUpdate and NOT TryRemove because there is a + // race condition. If a metric is deactivated and then reactivated in + // the same collection cycle + // instrumentIdentityToMetric[metric.InstrumentIdentity] may already + // point to the new activated metric and not the old deactivated one. + this.instrumentIdentityToMetric.TryUpdate(metric.InstrumentIdentity, null, metric); // Note: metric is a reference to the array storage so // this clears the metric out of the array. diff --git a/src/OpenTelemetry/Metrics/MetricReaderOptions.cs b/src/OpenTelemetry/Metrics/Reader/MetricReaderOptions.cs similarity index 100% rename from src/OpenTelemetry/Metrics/MetricReaderOptions.cs rename to src/OpenTelemetry/Metrics/Reader/MetricReaderOptions.cs diff --git a/src/OpenTelemetry/Metrics/MetricReaderTemporalityPreference.cs b/src/OpenTelemetry/Metrics/Reader/MetricReaderTemporalityPreference.cs similarity index 100% rename from src/OpenTelemetry/Metrics/MetricReaderTemporalityPreference.cs rename to src/OpenTelemetry/Metrics/Reader/MetricReaderTemporalityPreference.cs diff --git a/src/OpenTelemetry/Metrics/PeriodicExportingMetricReader.cs b/src/OpenTelemetry/Metrics/Reader/PeriodicExportingMetricReader.cs similarity index 100% rename from src/OpenTelemetry/Metrics/PeriodicExportingMetricReader.cs rename to src/OpenTelemetry/Metrics/Reader/PeriodicExportingMetricReader.cs diff --git a/src/OpenTelemetry/Metrics/PeriodicExportingMetricReaderOptions.cs b/src/OpenTelemetry/Metrics/Reader/PeriodicExportingMetricReaderOptions.cs similarity index 100% rename from src/OpenTelemetry/Metrics/PeriodicExportingMetricReaderOptions.cs rename to src/OpenTelemetry/Metrics/Reader/PeriodicExportingMetricReaderOptions.cs diff --git a/src/OpenTelemetry/Metrics/StringArrayEqualityComparer.cs b/src/OpenTelemetry/Metrics/StringArrayEqualityComparer.cs index 696cd40c23..0418c236e1 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 NET6_0_OR_GREATER +#if NET 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 1cc4c6bc15..ad166ebcca 100644 --- a/src/OpenTelemetry/Metrics/Tags.cs +++ b/src/OpenTelemetry/Metrics/Tags.cs @@ -3,7 +3,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; -#if NET6_0_OR_GREATER +#if NET using System.Runtime.InteropServices; #endif @@ -44,7 +44,7 @@ public readonly bool Equals(Tags other) return false; } -#if NET6_0_OR_GREATER +#if NET // Note: This loop uses unsafe code (pointers) to elide bounds checks on // two arrays we know to be of equal length. if (length > 0) @@ -104,7 +104,7 @@ private static int ComputeHashCode(KeyValuePair[] keyValuePairs { Debug.Assert(keyValuePairs != null, "keyValuePairs was null"); -#if NET6_0_OR_GREATER +#if NET HashCode hashCode = default; for (int i = 0; i < keyValuePairs.Length; i++) diff --git a/src/OpenTelemetry/Metrics/ThreadStaticStorage.cs b/src/OpenTelemetry/Metrics/ThreadStaticStorage.cs index 35f9be5b5d..a66460bfb7 100644 --- a/src/OpenTelemetry/Metrics/ThreadStaticStorage.cs +++ b/src/OpenTelemetry/Metrics/ThreadStaticStorage.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NET8_0_OR_GREATER +#if NET using System.Collections.Frozen; #endif using System.Diagnostics; @@ -57,7 +57,7 @@ internal void SplitToKeysAndValues( internal void SplitToKeysAndValues( ReadOnlySpan> tags, int tagLength, -#if NET8_0_OR_GREATER +#if NET FrozenSet tagKeysInteresting, #else HashSet tagKeysInteresting, diff --git a/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogramConfiguration.cs b/src/OpenTelemetry/Metrics/View/Base2ExponentialBucketHistogramConfiguration.cs similarity index 100% rename from src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogramConfiguration.cs rename to src/OpenTelemetry/Metrics/View/Base2ExponentialBucketHistogramConfiguration.cs diff --git a/src/OpenTelemetry/Metrics/ExplicitBucketHistogramConfiguration.cs b/src/OpenTelemetry/Metrics/View/ExplicitBucketHistogramConfiguration.cs similarity index 100% rename from src/OpenTelemetry/Metrics/ExplicitBucketHistogramConfiguration.cs rename to src/OpenTelemetry/Metrics/View/ExplicitBucketHistogramConfiguration.cs diff --git a/src/OpenTelemetry/Metrics/HistogramConfiguration.cs b/src/OpenTelemetry/Metrics/View/HistogramConfiguration.cs similarity index 100% rename from src/OpenTelemetry/Metrics/HistogramConfiguration.cs rename to src/OpenTelemetry/Metrics/View/HistogramConfiguration.cs diff --git a/src/OpenTelemetry/Metrics/MetricStreamConfiguration.cs b/src/OpenTelemetry/Metrics/View/MetricStreamConfiguration.cs similarity index 91% rename from src/OpenTelemetry/Metrics/MetricStreamConfiguration.cs rename to src/OpenTelemetry/Metrics/View/MetricStreamConfiguration.cs index 578049f329..cf5e06661a 100644 --- a/src/OpenTelemetry/Metrics/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 && NET8_0_OR_GREATER +#if EXPOSE_EXPERIMENTAL_FEATURES && NET using System.Diagnostics.CodeAnalysis; #endif using OpenTelemetry.Internal; @@ -98,14 +98,11 @@ public string[]? TagKeys } } -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Gets or sets a positive integer value defining the maximum number of /// data points allowed for the metric managed by the view. /// /// - /// WARNING: This is an experimental API which might change or - /// be removed in the future. Use at your own risk. /// Spec reference: Cardinality /// limits. @@ -116,14 +113,7 @@ public string[]? TagKeys /// If not set the default /// MeterProvider cardinality limit of 2000 will apply. /// -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.CardinalityLimitExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else - internal -#endif - int? CardinalityLimit + public int? CardinalityLimit { get => this.cardinalityLimit; set @@ -151,7 +141,7 @@ public string[]? TagKeys /// Specification: . /// -#if NET8_0_OR_GREATER +#if NET [Experimental(DiagnosticDefinitions.ExemplarReservoirExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public Func? ExemplarReservoirFactory { get; set; } diff --git a/src/OpenTelemetry/OpenTelemetryBuilderSdkExtensions.cs b/src/OpenTelemetry/OpenTelemetryBuilderSdkExtensions.cs index 4ae87d3407..c499ef0263 100644 --- a/src/OpenTelemetry/OpenTelemetryBuilderSdkExtensions.cs +++ b/src/OpenTelemetry/OpenTelemetryBuilderSdkExtensions.cs @@ -1,9 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if EXPOSE_EXPERIMENTAL_FEATURES && NET8_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -#endif using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.Metrics; using Microsoft.Extensions.Logging; @@ -129,14 +126,11 @@ public static IOpenTelemetryBuilder WithTracing( return builder; } -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Adds logging services into the builder. /// /// . /// - /// WARNING: This is an experimental API which might change or - /// be removed in the future. Use at your own risk. /// Notes: /// /// This is safe to be called multiple times and by library authors. @@ -149,48 +143,9 @@ public static IOpenTelemetryBuilder WithTracing( /// /// The supplied for chaining /// calls. -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else - /// - /// Adds logging services into the builder. - /// - /// . - /// - /// Notes: - /// - /// This is safe to be called multiple times and by library authors. - /// Only a single will be created for a given - /// . - /// This method automatically registers an named 'OpenTelemetry' into the . - /// - /// - /// The supplied for chaining - /// calls. - internal -#endif - static IOpenTelemetryBuilder WithLogging(this IOpenTelemetryBuilder builder) + public static IOpenTelemetryBuilder WithLogging(this IOpenTelemetryBuilder builder) => WithLogging(builder, configureBuilder: null, configureOptions: null); -#if EXPOSE_EXPERIMENTAL_FEATURES - /// - /// Adds logging services into the builder. - /// - /// - /// . - /// - /// configuration callback. - /// The supplied for chaining - /// calls. -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else /// /// Adds logging services into the builder. /// @@ -200,18 +155,15 @@ static IOpenTelemetryBuilder WithLogging(this IOpenTelemetryBuilder builder) /// configuration callback. /// The supplied for chaining /// calls. - internal -#endif - static IOpenTelemetryBuilder WithLogging( - this IOpenTelemetryBuilder builder, - Action configure) + public static IOpenTelemetryBuilder WithLogging( + this IOpenTelemetryBuilder builder, + Action configure) { Guard.ThrowIfNull(configure); return WithLogging(builder, configureBuilder: configure, configureOptions: null); } -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Adds logging services into the builder. /// @@ -220,31 +172,16 @@ static IOpenTelemetryBuilder WithLogging( /// Optional configuration callback. /// Optional configuration callback. + /// cref="OpenTelemetryLoggerOptions"/> configuration callback. are used by the named 'OpenTelemetry' automatically registered + /// by this method. /// The supplied for chaining /// calls. -#if NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif - public -#else - /// - /// Adds logging services into the builder. - /// - /// - /// . - /// Optional configuration callback. - /// Optional configuration callback. - /// The supplied for chaining - /// calls. - internal -#endif - static IOpenTelemetryBuilder WithLogging( - this IOpenTelemetryBuilder builder, - Action? configureBuilder, - Action? configureOptions) + public static IOpenTelemetryBuilder WithLogging( + this IOpenTelemetryBuilder builder, + Action? configureBuilder, + Action? configureOptions) { builder.Services.AddLogging( logging => logging.UseOpenTelemetry(configureBuilder, configureOptions)); diff --git a/src/OpenTelemetry/OpenTelemetrySdk.cs b/src/OpenTelemetry/OpenTelemetrySdk.cs new file mode 100644 index 0000000000..7020d05ce1 --- /dev/null +++ b/src/OpenTelemetry/OpenTelemetrySdk.cs @@ -0,0 +1,124 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Diagnostics; +using Microsoft.Extensions.DependencyInjection; +using OpenTelemetry.Internal; +using OpenTelemetry.Logs; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace OpenTelemetry; + +/// +/// Contains methods for configuring the OpenTelemetry SDK and accessing +/// logging, metrics, and tracing providers. +/// +public sealed class OpenTelemetrySdk : IDisposable +{ + private readonly ServiceProvider serviceProvider; + + private OpenTelemetrySdk( + Action configure) + { + Debug.Assert(configure != null, "configure was null"); + + var services = new ServiceCollection(); + + var builder = new OpenTelemetrySdkBuilder(services); + + configure!(builder); + + this.serviceProvider = services.BuildServiceProvider(); + + this.LoggerProvider = (LoggerProvider?)this.serviceProvider.GetService(typeof(LoggerProvider)) + ?? new NoopLoggerProvider(); + this.MeterProvider = (MeterProvider?)this.serviceProvider.GetService(typeof(MeterProvider)) + ?? new NoopMeterProvider(); + this.TracerProvider = (TracerProvider?)this.serviceProvider.GetService(typeof(TracerProvider)) + ?? new NoopTracerProvider(); + } + + /// + /// Gets the . + /// + /// + /// Note: The default will be a no-op instance. + /// Call to + /// enable logging. + /// + public LoggerProvider LoggerProvider { get; } + + /// + /// Gets the . + /// + /// + /// Note: The default will be a no-op instance. + /// Call + /// to enable metrics. + /// + public MeterProvider MeterProvider { get; } + + /// + /// Gets the . + /// + /// + /// Note: The default will be a no-op instance. + /// Call + /// to enable tracing. + /// + public TracerProvider TracerProvider { get; } + + /// + /// Gets the containing SDK services. + /// + internal IServiceProvider Services => this.serviceProvider; + + /// + /// Create an instance. + /// + /// configuration delegate. + /// Created . + public static OpenTelemetrySdk Create( + Action configure) + { + Guard.ThrowIfNull(configure); + + return new(configure); + } + + /// + public void Dispose() + { + this.serviceProvider.Dispose(); + } + + internal sealed class NoopLoggerProvider : LoggerProvider + { + } + + internal sealed class NoopMeterProvider : MeterProvider + { + } + + internal sealed class NoopTracerProvider : TracerProvider + { + } + + private sealed class OpenTelemetrySdkBuilder : IOpenTelemetryBuilder + { + public OpenTelemetrySdkBuilder(IServiceCollection services) + { + Debug.Assert(services != null, "services was null"); + + services!.AddOpenTelemetrySharedProviderBuilderServices(); + + this.Services = services!; + } + + public IServiceCollection Services { get; } + } +} diff --git a/src/OpenTelemetry/OpenTelemetrySdkExtensions.cs b/src/OpenTelemetry/OpenTelemetrySdkExtensions.cs new file mode 100644 index 0000000000..f1f01e0f20 --- /dev/null +++ b/src/OpenTelemetry/OpenTelemetrySdkExtensions.cs @@ -0,0 +1,36 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using OpenTelemetry.Internal; + +namespace OpenTelemetry; + +/// +/// Contains methods for extending the class. +/// +public static class OpenTelemetrySdkExtensions +{ + private static readonly NullLoggerFactory NoopLoggerFactory = new(); + + /// + /// Gets the contained in an instance. + /// + /// + /// Note: The default will be a no-op instance. + /// Call + /// to enable logging. + /// + /// . + /// . + public static ILoggerFactory GetLoggerFactory(this OpenTelemetrySdk sdk) + { + Guard.ThrowIfNull(sdk); + + return (ILoggerFactory?)sdk.Services.GetService(typeof(ILoggerFactory)) + ?? NoopLoggerFactory; + } +} diff --git a/src/OpenTelemetry/README.md b/src/OpenTelemetry/README.md index 8e444a6f16..e05a403f42 100644 --- a/src/OpenTelemetry/README.md +++ b/src/OpenTelemetry/README.md @@ -3,16 +3,19 @@ [![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.svg)](https://www.nuget.org/packages/OpenTelemetry) [![NuGet](https://img.shields.io/nuget/dt/OpenTelemetry.svg)](https://www.nuget.org/packages/OpenTelemetry) +
+Table of Contents + * [Installation](#installation) * [Introduction](#introduction) -* [Getting started with Logging](#getting-started-with-logging) -* [Getting started with Metrics](#getting-started-with-metrics) -* [Getting started with Tracing](#getting-started-with-tracing) * [Troubleshooting](#troubleshooting) - * [Configuration Parameters](#configuration-parameters) - * [Remarks](#remarks) + * [Self-diagnostics](#self-diagnostics) + * [Configuration Parameters](#configuration-parameters) + * [Remarks](#remarks) * [References](#references) +
+ ## Installation ```shell @@ -22,61 +25,25 @@ dotnet add package OpenTelemetry ## Introduction OpenTelemetry SDK is a reference implementation of the OpenTelemetry API. It -implements the Tracing API, the Metrics API, and the Context API. Once a valid -SDK is installed and configured, all the OpenTelemetry API methods, which were -no-ops without an SDK, will start emitting telemetry. -This SDK also supports [ILogger](https://docs.microsoft.com/dotnet/api/microsoft.extensions.logging.ilogger) -integration. - -The SDK deals with concerns such as sampling, processing pipeline, exporting -telemetry to a particular backend etc. In most cases, users indirectly install -and enable the SDK, when they install a particular exporter. - -## Getting started with Logging - -If you are new to logging, it is recommended to first follow the [getting -started in 5 minutes - Console -Application](../../docs/logs/getting-started-console/README.md) guide to get up -and running. - -While [OpenTelemetry -logging](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/README.md) -specification is an experimental signal, `ILogger` is the de-facto logging API -provided by the .NET runtime and is a stable API recommended for production use. -This repo ships an OpenTelemetry -[provider](https://docs.microsoft.com/dotnet/core/extensions/logging-providers), -which provides the ability to enrich logs emitted with `ILogger` with -`ActivityContext`, and export them to multiple destinations, similar to tracing. -`ILogger` based API will become the OpenTelemetry .NET implementation of -OpenTelemetry logging. - -## Getting started with Metrics - -If you are new to -[metrics](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md), -it is recommended to first follow the [getting started in 5 minutes - ASP.NET -Core Application](../../docs/metrics/getting-started-aspnetcore/README.md) guide -or the [getting started in 5 minutes - Console -Application](../../docs/metrics/getting-started-console/README.md) guide to get up -and running. - -For a more detailed explanation of SDK metric features see [Customizing -OpenTelemetry .NET SDK for -Metrics](../../docs/metrics/customizing-the-sdk/README.md). - -## Getting started with Tracing - -If you are new to -[traces](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md), -it is recommended to first follow the [getting started in 5 minutes - ASP.NET -Core Application](../../docs/trace/getting-started-aspnetcore/README.md) guide -or the [getting started in 5 minutes - Console -Application](../../docs/trace/getting-started-console/README.md) guide to get up -and running. - -For a more detailed explanation of SDK tracing features see [Customizing -OpenTelemetry .NET SDK for -Tracing](../../docs/trace/customizing-the-sdk/README.md). +implements the Logging API, Metrics API, Tracing API, Resource API, and the +Context API. Once a valid SDK is installed and configured all the OpenTelemetry +API methods, which were no-ops without an SDK, will start emitting telemetry. +This SDK also ships with +[ILogger](https://learn.microsoft.com/dotnet/core/extensions/logging) +integration to automatically capture and enrich logs emitted using +`Microsoft.Extensions.Logging`. + +The SDK deals with concerns such as sampling, processing pipelines (exporting +telemetry to a particular backend, etc.), metrics aggregation, and other +concerns outlined in the [OpenTelemetry +Specification](https://github.com/open-telemetry/opentelemetry-specification). +In most cases, users indirectly install and enable the SDK when they install an +exporter. + +To learn how to set up and configure the OpenTelemetry SDK see: [Getting +started](../../README.md#getting-started). For additional details about +initialization patterns see: [Initialize the +SDK](../../docs/README.md#initialize-the-sdk). ## Troubleshooting @@ -90,9 +57,9 @@ While it is possible to view these logs using tools such as [PerfView](https://github.com/microsoft/perfview), [dotnet-trace](https://docs.microsoft.com/dotnet/core/diagnostics/dotnet-trace) etc., this SDK also ships a [self-diagnostics](#self-diagnostics) feature, which -helps troubleshooting. +helps with troubleshooting. -## Self-diagnostics +### Self-diagnostics OpenTelemetry SDK ships with built-in self-diagnostics feature. This feature, when enabled, will listen to internal logs generated by all OpenTelemetry @@ -130,7 +97,7 @@ Internally, it looks for the configuration file located in and then [AppContext.BaseDirectory](https://docs.microsoft.com/dotnet/api/system.appcontext.basedirectory). You can also find the exact directory by calling these methods from your code. -### Configuration Parameters +#### Configuration Parameters 1. `LogDirectory` is the directory where the output log file will be stored. It can be an absolute path or a relative path to the current directory. @@ -150,7 +117,7 @@ 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. -### Remarks +#### Remarks A `FileSize`-KiB log file named as `ExecutableName.ProcessId.log` (e.g. `foobar.exe.12345.log`) will be generated at the specified directory @@ -172,6 +139,7 @@ start from beginning and overwrite existing text. ## References -* [OpenTelemetry Project](https://opentelemetry.io/) +* [OpenTelemetry Logging SDK specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/sdk.md) +* [OpenTelemetry Metrics SDK specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md) * [OpenTelemetry Tracing SDK specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md) -* [OpenTelemetry Logging specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/README.md) +* [OpenTelemetry Resource SDK specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md) diff --git a/src/OpenTelemetry/ReadOnlyFilteredTagCollection.cs b/src/OpenTelemetry/ReadOnlyFilteredTagCollection.cs index 8b8d7d12b3..6369018ca0 100644 --- a/src/OpenTelemetry/ReadOnlyFilteredTagCollection.cs +++ b/src/OpenTelemetry/ReadOnlyFilteredTagCollection.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NET8_0_OR_GREATER +#if NET using System.Collections.Frozen; #endif using System.Diagnostics; @@ -16,7 +16,7 @@ namespace OpenTelemetry; // prevent accidental boxing. public readonly struct ReadOnlyFilteredTagCollection { -#if NET8_0_OR_GREATER +#if NET private readonly FrozenSet? excludedKeys; #else private readonly HashSet? excludedKeys; @@ -25,7 +25,7 @@ public readonly struct ReadOnlyFilteredTagCollection private readonly int count; internal ReadOnlyFilteredTagCollection( -#if NET8_0_OR_GREATER +#if NET FrozenSet? excludedKeys, #else HashSet? excludedKeys, diff --git a/src/OpenTelemetry/Sdk.cs b/src/OpenTelemetry/Sdk.cs index 9dd0f2776e..2cb4e0b6c9 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 && NET8_0_OR_GREATER +#if EXPOSE_EXPERIMENTAL_FEATURES && NET using System.Diagnostics.CodeAnalysis; #endif using OpenTelemetry.Context.Propagation; @@ -89,8 +89,8 @@ 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 NET8_0_OR_GREATER - [Experimental(DiagnosticDefinitions.LoggerProviderExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] +#if NET + [Experimental(DiagnosticDefinitions.LogsBridgeExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public #else diff --git a/src/OpenTelemetry/SimpleExportProcessor.cs b/src/OpenTelemetry/SimpleExportProcessor.cs index 10951cd853..95387ef997 100644 --- a/src/OpenTelemetry/SimpleExportProcessor.cs +++ b/src/OpenTelemetry/SimpleExportProcessor.cs @@ -12,7 +12,7 @@ namespace OpenTelemetry; public abstract class SimpleExportProcessor : BaseExportProcessor where T : class { - private readonly object syncObject = new(); + private readonly Lock syncObject = new(); /// /// Initializes a new instance of the class. diff --git a/src/OpenTelemetry/Trace/Builder/TracerProviderBuilderExtensions.cs b/src/OpenTelemetry/Trace/Builder/TracerProviderBuilderExtensions.cs index a4c39becf1..c76befd9b7 100644 --- a/src/OpenTelemetry/Trace/Builder/TracerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry/Trace/Builder/TracerProviderBuilderExtensions.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; -#if NET6_0_OR_GREATER +#if NET using System.Diagnostics.CodeAnalysis; #endif using Microsoft.Extensions.DependencyInjection; @@ -27,7 +27,7 @@ public static class TracerProviderBuilderExtensions /// /// This method is not supported in native AOT or Mono Runtime as of .NET 8. /// -#if NET7_0_OR_GREATER +#if NET [RequiresDynamicCode("The code for detecting exception and setting error status might not be available.")] #endif public static TracerProviderBuilder SetErrorStatusOnException(this TracerProviderBuilder tracerProviderBuilder, bool enabled = true) @@ -75,7 +75,7 @@ public static TracerProviderBuilder SetSampler(this TracerProviderBuilder tracer /// . /// The supplied for chaining. public static TracerProviderBuilder SetSampler< -#if NET6_0_OR_GREATER +#if NET [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T>(this TracerProviderBuilder tracerProviderBuilder) @@ -195,7 +195,7 @@ public static TracerProviderBuilder AddProcessor(this TracerProviderBuilder trac /// . /// The supplied for chaining. public static TracerProviderBuilder AddProcessor< -#if NET6_0_OR_GREATER +#if NET [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T>(this TracerProviderBuilder tracerProviderBuilder) diff --git a/src/OpenTelemetry/Trace/ActivityExportProcessorOptions.cs b/src/OpenTelemetry/Trace/Processor/ActivityExportProcessorOptions.cs similarity index 100% rename from src/OpenTelemetry/Trace/ActivityExportProcessorOptions.cs rename to src/OpenTelemetry/Trace/Processor/ActivityExportProcessorOptions.cs diff --git a/src/OpenTelemetry/Trace/BatchActivityExportProcessor.cs b/src/OpenTelemetry/Trace/Processor/BatchActivityExportProcessor.cs similarity index 100% rename from src/OpenTelemetry/Trace/BatchActivityExportProcessor.cs rename to src/OpenTelemetry/Trace/Processor/BatchActivityExportProcessor.cs diff --git a/src/OpenTelemetry/Trace/BatchExportActivityProcessorOptions.cs b/src/OpenTelemetry/Trace/Processor/BatchExportActivityProcessorOptions.cs similarity index 100% rename from src/OpenTelemetry/Trace/BatchExportActivityProcessorOptions.cs rename to src/OpenTelemetry/Trace/Processor/BatchExportActivityProcessorOptions.cs diff --git a/src/OpenTelemetry/Trace/ExceptionProcessor.cs b/src/OpenTelemetry/Trace/Processor/ExceptionProcessor.cs similarity index 88% rename from src/OpenTelemetry/Trace/ExceptionProcessor.cs rename to src/OpenTelemetry/Trace/Processor/ExceptionProcessor.cs index f0084d36cf..f384551940 100644 --- a/src/OpenTelemetry/Trace/ExceptionProcessor.cs +++ b/src/OpenTelemetry/Trace/Processor/ExceptionProcessor.cs @@ -3,7 +3,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; -#if !NET6_0_OR_GREATER && !NETFRAMEWORK +#if !NET && !NETFRAMEWORK using System.Linq.Expressions; using System.Reflection; #endif @@ -18,7 +18,7 @@ internal sealed class ExceptionProcessor : BaseProcessor public ExceptionProcessor() { -#if NET6_0_OR_GREATER || NETFRAMEWORK +#if NET || NETFRAMEWORK this.fnGetExceptionPointers = Marshal.GetExceptionPointers; #else // When running on netstandard or similar the Marshal class is not a part of the netstandard API @@ -62,11 +62,9 @@ public override void OnEnd(Activity activity) if (snapshot != pointers) { - // TODO: Remove this when SetStatus is deprecated +#pragma warning disable activity.SetStatus(Status.Error); - - // For processors/exporters checking `Status` property. - activity.SetStatus(ActivityStatusCode.Error); +#pragma warning restore } } } diff --git a/src/OpenTelemetry/Trace/SimpleActivityExportProcessor.cs b/src/OpenTelemetry/Trace/Processor/SimpleActivityExportProcessor.cs similarity index 100% rename from src/OpenTelemetry/Trace/SimpleActivityExportProcessor.cs rename to src/OpenTelemetry/Trace/Processor/SimpleActivityExportProcessor.cs diff --git a/src/OpenTelemetry/Trace/AlwaysOffSampler.cs b/src/OpenTelemetry/Trace/Sampler/AlwaysOffSampler.cs similarity index 100% rename from src/OpenTelemetry/Trace/AlwaysOffSampler.cs rename to src/OpenTelemetry/Trace/Sampler/AlwaysOffSampler.cs diff --git a/src/OpenTelemetry/Trace/AlwaysOnSampler.cs b/src/OpenTelemetry/Trace/Sampler/AlwaysOnSampler.cs similarity index 100% rename from src/OpenTelemetry/Trace/AlwaysOnSampler.cs rename to src/OpenTelemetry/Trace/Sampler/AlwaysOnSampler.cs diff --git a/src/OpenTelemetry/Trace/ParentBasedSampler.cs b/src/OpenTelemetry/Trace/Sampler/ParentBasedSampler.cs similarity index 100% rename from src/OpenTelemetry/Trace/ParentBasedSampler.cs rename to src/OpenTelemetry/Trace/Sampler/ParentBasedSampler.cs diff --git a/src/OpenTelemetry/Trace/Sampler.cs b/src/OpenTelemetry/Trace/Sampler/Sampler.cs similarity index 100% rename from src/OpenTelemetry/Trace/Sampler.cs rename to src/OpenTelemetry/Trace/Sampler/Sampler.cs diff --git a/src/OpenTelemetry/Trace/SamplingDecision.cs b/src/OpenTelemetry/Trace/Sampler/SamplingDecision.cs similarity index 100% rename from src/OpenTelemetry/Trace/SamplingDecision.cs rename to src/OpenTelemetry/Trace/Sampler/SamplingDecision.cs diff --git a/src/OpenTelemetry/Trace/SamplingParameters.cs b/src/OpenTelemetry/Trace/Sampler/SamplingParameters.cs similarity index 100% rename from src/OpenTelemetry/Trace/SamplingParameters.cs rename to src/OpenTelemetry/Trace/Sampler/SamplingParameters.cs diff --git a/src/OpenTelemetry/Trace/SamplingResult.cs b/src/OpenTelemetry/Trace/Sampler/SamplingResult.cs similarity index 99% rename from src/OpenTelemetry/Trace/SamplingResult.cs rename to src/OpenTelemetry/Trace/Sampler/SamplingResult.cs index 96a2fcbeeb..40cac1c201 100644 --- a/src/OpenTelemetry/Trace/SamplingResult.cs +++ b/src/OpenTelemetry/Trace/Sampler/SamplingResult.cs @@ -102,7 +102,7 @@ public override bool Equals(object? obj) /// public override int GetHashCode() { -#if NET6_0_OR_GREATER +#if NET HashCode hashCode = default; hashCode.Add(this.Decision); hashCode.Add(this.Attributes); diff --git a/src/OpenTelemetry/Trace/TraceIdRatioBasedSampler.cs b/src/OpenTelemetry/Trace/Sampler/TraceIdRatioBasedSampler.cs similarity index 100% rename from src/OpenTelemetry/Trace/TraceIdRatioBasedSampler.cs rename to src/OpenTelemetry/Trace/Sampler/TraceIdRatioBasedSampler.cs diff --git a/src/OpenTelemetry/Trace/TracerProviderSdk.cs b/src/OpenTelemetry/Trace/TracerProviderSdk.cs index 2da7f0ea4d..8fd333b195 100644 --- a/src/OpenTelemetry/Trace/TracerProviderSdk.cs +++ b/src/OpenTelemetry/Trace/TracerProviderSdk.cs @@ -111,7 +111,7 @@ internal TracerProviderSdk( OpenTelemetrySdkEventSource.Log.TracerProviderSdkEvent($"Instrumentations added = \"{instrumentationFactoriesAdded}\"."); } - var listener = new ActivityListener(); + var activityListener = new ActivityListener(); if (this.supportLegacyActivity) { @@ -125,7 +125,7 @@ internal TracerProviderSdk( legacyActivityPredicate = activity => state.LegacyActivityOperationNames.Contains(activity.OperationName); } - listener.ActivityStarted = activity => + activityListener.ActivityStarted = activity => { OpenTelemetrySdkEventSource.Log.ActivityStarted(activity); @@ -163,7 +163,7 @@ internal TracerProviderSdk( } }; - listener.ActivityStopped = activity => + activityListener.ActivityStopped = activity => { OpenTelemetrySdkEventSource.Log.ActivityStopped(activity); @@ -194,7 +194,7 @@ internal TracerProviderSdk( } else { - listener.ActivityStarted = activity => + activityListener.ActivityStarted = activity => { OpenTelemetrySdkEventSource.Log.ActivityStarted(activity); @@ -204,7 +204,7 @@ internal TracerProviderSdk( } }; - listener.ActivityStopped = activity => + activityListener.ActivityStopped = activity => { OpenTelemetrySdkEventSource.Log.ActivityStopped(activity); @@ -230,20 +230,20 @@ internal TracerProviderSdk( if (this.sampler is AlwaysOnSampler) { - listener.Sample = (ref ActivityCreationOptions options) => + activityListener.Sample = (ref ActivityCreationOptions options) => !Sdk.SuppressInstrumentation ? ActivitySamplingResult.AllDataAndRecorded : ActivitySamplingResult.None; this.getRequestedDataAction = this.RunGetRequestedDataAlwaysOnSampler; } else if (this.sampler is AlwaysOffSampler) { - listener.Sample = (ref ActivityCreationOptions options) => + activityListener.Sample = (ref ActivityCreationOptions options) => !Sdk.SuppressInstrumentation ? PropagateOrIgnoreData(options.Parent) : ActivitySamplingResult.None; this.getRequestedDataAction = this.RunGetRequestedDataAlwaysOffSampler; } else { // This delegate informs ActivitySource about sampling decision when the parent context is an ActivityContext. - listener.Sample = (ref ActivityCreationOptions options) => + activityListener.Sample = (ref ActivityCreationOptions options) => !Sdk.SuppressInstrumentation ? ComputeActivitySamplingResult(ref options, this.sampler) : ActivitySamplingResult.None; this.getRequestedDataAction = this.RunGetRequestedDataOtherSampler; } @@ -260,7 +260,7 @@ internal TracerProviderSdk( // Function which takes ActivitySource and returns true/false to indicate if it should be subscribed to // or not. - listener.ShouldListenTo = (activitySource) => + activityListener.ShouldListenTo = activitySource => this.supportLegacyActivity ? string.IsNullOrEmpty(activitySource.Name) || regex.IsMatch(activitySource.Name) : regex.IsMatch(activitySource.Name); @@ -276,19 +276,19 @@ internal TracerProviderSdk( // Function which takes ActivitySource and returns true/false to indicate if it should be subscribed to // or not. - listener.ShouldListenTo = (activitySource) => activitySources.Contains(activitySource.Name); + activityListener.ShouldListenTo = activitySource => activitySources.Contains(activitySource.Name); } } else { if (this.supportLegacyActivity) { - listener.ShouldListenTo = (activitySource) => string.IsNullOrEmpty(activitySource.Name); + activityListener.ShouldListenTo = activitySource => string.IsNullOrEmpty(activitySource.Name); } } - ActivitySource.AddActivityListener(listener); - this.listener = listener; + ActivitySource.AddActivityListener(activityListener); + this.listener = activityListener; OpenTelemetrySdkEventSource.Log.TracerProviderSdkEvent("TracerProvider built successfully."); } diff --git a/src/Shared/ActivityHelperExtensions.cs b/src/Shared/ActivityHelperExtensions.cs index b237ccd545..4fd1f517da 100644 --- a/src/Shared/ActivityHelperExtensions.cs +++ b/src/Shared/ActivityHelperExtensions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using System.Runtime.CompilerServices; using OpenTelemetry.Internal; @@ -24,6 +22,7 @@ internal static class ActivityHelperExtensions /// Status description. /// if was found on the supplied Activity. [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Obsolete] public static bool TryGetStatus(this Activity activity, out StatusCode statusCode, out string? statusDescription) { Debug.Assert(activity != null, "Activity should not be null"); diff --git a/src/Shared/AssemblyVersionExtensions.cs b/src/Shared/AssemblyVersionExtensions.cs index 029a3c2451..67926d99c0 100644 --- a/src/Shared/AssemblyVersionExtensions.cs +++ b/src/Shared/AssemblyVersionExtensions.cs @@ -1,9 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace OpenTelemetry.Internal; @@ -11,6 +10,33 @@ namespace OpenTelemetry.Internal; internal static class AssemblyVersionExtensions { public static string GetPackageVersion(this Assembly assembly) + { + Debug.Assert(assembly != null, "assembly was null"); + + var informationalVersion = assembly.GetCustomAttribute()?.InformationalVersion; + + Debug.Assert(!string.IsNullOrEmpty(informationalVersion), "AssemblyInformationalVersionAttribute was not found in assembly"); + + return ParsePackageVersion(informationalVersion!); + } + + public static bool TryGetPackageVersion(this Assembly assembly, [NotNullWhen(true)] out string? packageVersion) + { + Debug.Assert(assembly != null, "assembly was null"); + + var informationalVersion = assembly.GetCustomAttribute()?.InformationalVersion; + + if (string.IsNullOrEmpty(informationalVersion)) + { + packageVersion = null; + return false; + } + + packageVersion = ParsePackageVersion(informationalVersion!); + return true; + } + + private static string ParsePackageVersion(string informationalVersion) { // MinVer https://github.com/adamralph/minver?tab=readme-ov-file#version-numbers // together with Microsoft.SourceLink.GitHub https://github.com/dotnet/sourcelink @@ -20,9 +46,6 @@ public static string GetPackageVersion(this Assembly assembly) // 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 informationalVersion = assembly.GetCustomAttribute()?.InformationalVersion; - Debug.Assert(!string.IsNullOrEmpty(informationalVersion), "AssemblyInformationalVersionAttribute was not found in assembly"); - var indexOfPlusSign = informationalVersion!.IndexOf('+'); return indexOfPlusSign > 0 ? informationalVersion.Substring(0, indexOfPlusSign) diff --git a/src/Shared/Configuration/IConfigurationExtensionsLogger.cs b/src/Shared/Configuration/IConfigurationExtensionsLogger.cs index 0835cf6ff8..196a485bf3 100644 --- a/src/Shared/Configuration/IConfigurationExtensionsLogger.cs +++ b/src/Shared/Configuration/IConfigurationExtensionsLogger.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace Microsoft.Extensions.Configuration; internal interface IConfigurationExtensionsLogger diff --git a/src/Shared/Configuration/OpenTelemetryConfigurationExtensions.cs b/src/Shared/Configuration/OpenTelemetryConfigurationExtensions.cs index d591ae2207..c713fd166d 100644 --- a/src/Shared/Configuration/OpenTelemetryConfigurationExtensions.cs +++ b/src/Shared/Configuration/OpenTelemetryConfigurationExtensions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; #if !NETFRAMEWORK && !NETSTANDARD2_0 using System.Diagnostics.CodeAnalysis; diff --git a/src/Shared/DiagnosticDefinitions.cs b/src/Shared/DiagnosticDefinitions.cs index 94bf1c5f52..f6b449352f 100644 --- a/src/Shared/DiagnosticDefinitions.cs +++ b/src/Shared/DiagnosticDefinitions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Internal; internal static class DiagnosticDefinitions @@ -11,10 +9,10 @@ internal static class DiagnosticDefinitions public const string LoggerProviderExperimentalApi = "OTEL1000"; public const string LogsBridgeExperimentalApi = "OTEL1001"; - public const string CardinalityLimitExperimentalApi = "OTEL1003"; public const string ExemplarReservoirExperimentalApi = "OTEL1004"; /* Definitions which have been released stable: public const string ExemplarExperimentalApi = "OTEL1002"; + public const string CardinalityLimitExperimentalApi = "OTEL1003"; */ } diff --git a/src/Shared/ExceptionExtensions.cs b/src/Shared/ExceptionExtensions.cs index 9070b59c20..5071a8feeb 100644 --- a/src/Shared/ExceptionExtensions.cs +++ b/src/Shared/ExceptionExtensions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Globalization; namespace OpenTelemetry.Internal; diff --git a/src/Shared/Guard.cs b/src/Shared/Guard.cs index e768d67604..dbbe87e0e8 100644 --- a/src/Shared/Guard.cs +++ b/src/Shared/Guard.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -12,24 +10,24 @@ #pragma warning disable SA1403 // File may only contain a single namespace #pragma warning disable SA1649 // File name should match first type name -#if !NET6_0_OR_GREATER +#if !NET namespace System.Runtime.CompilerServices { /// Allows capturing of the expressions passed to a method. [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] internal sealed class CallerArgumentExpressionAttribute : Attribute { - public CallerArgumentExpressionAttribute(string parameterName) + public CallerArgumentExpressionAttribute(string? parameterName) { this.ParameterName = parameterName; } - public string ParameterName { get; } + public string? ParameterName { get; } } } #endif -#if !NET6_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER +#if !NET && !NETSTANDARD2_1_OR_GREATER namespace System.Diagnostics.CodeAnalysis { /// Specifies that an output is not even if @@ -57,7 +55,7 @@ internal static class Guard /// The parameter name to use in the thrown exception. [DebuggerHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] object? value, [CallerArgumentExpression("value")] string? paramName = null) + public static void ThrowIfNull([NotNull] object? value, [CallerArgumentExpression(nameof(value))] string? paramName = null) { if (value is null) { @@ -72,7 +70,7 @@ public static void ThrowIfNull([NotNull] object? value, [CallerArgumentExpressio /// The parameter name to use in the thrown exception. [DebuggerHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNullOrEmpty([NotNull] string? value, [CallerArgumentExpression("value")] string? paramName = null) + public static void ThrowIfNullOrEmpty([NotNull] string? value, [CallerArgumentExpression(nameof(value))] string? paramName = null) #pragma warning disable CS8777 // Parameter must have a non-null value when exiting. { if (string.IsNullOrEmpty(value)) @@ -89,7 +87,7 @@ public static void ThrowIfNullOrEmpty([NotNull] string? value, [CallerArgumentEx /// The parameter name to use in the thrown exception. [DebuggerHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNullOrWhitespace([NotNull] string? value, [CallerArgumentExpression("value")] string? paramName = null) + public static void ThrowIfNullOrWhitespace([NotNull] string? value, [CallerArgumentExpression(nameof(value))] string? paramName = null) #pragma warning disable CS8777 // Parameter must have a non-null value when exiting. { if (string.IsNullOrWhiteSpace(value)) @@ -107,7 +105,7 @@ public static void ThrowIfNullOrWhitespace([NotNull] string? value, [CallerArgum /// The parameter name to use in the thrown exception. [DebuggerHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfZero(int value, string message = "Must not be zero", [CallerArgumentExpression("value")] string? paramName = null) + public static void ThrowIfZero(int value, string message = "Must not be zero", [CallerArgumentExpression(nameof(value))] string? paramName = null) { if (value == 0) { @@ -122,7 +120,7 @@ public static void ThrowIfZero(int value, string message = "Must not be zero", [ /// The parameter name to use in the thrown exception. [DebuggerHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfInvalidTimeout(int value, [CallerArgumentExpression("value")] string? paramName = null) + public static void ThrowIfInvalidTimeout(int value, [CallerArgumentExpression(nameof(value))] string? paramName = null) { ThrowIfOutOfRange(value, paramName, min: Timeout.Infinite, message: $"Must be non-negative or '{nameof(Timeout)}.{nameof(Timeout.Infinite)}'"); } @@ -139,7 +137,7 @@ public static void ThrowIfInvalidTimeout(int value, [CallerArgumentExpression("v /// An optional custom message to use in the thrown exception. [DebuggerHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfOutOfRange(int value, [CallerArgumentExpression("value")] string? paramName = null, int min = int.MinValue, int max = int.MaxValue, string? minName = null, string? maxName = null, string? message = null) + public static void ThrowIfOutOfRange(int value, [CallerArgumentExpression(nameof(value))] string? paramName = null, int min = int.MinValue, int max = int.MaxValue, string? minName = null, string? maxName = null, string? message = null) { Range(value, paramName, min, max, minName, maxName, message); } @@ -156,7 +154,7 @@ public static void ThrowIfOutOfRange(int value, [CallerArgumentExpression("value /// An optional custom message to use in the thrown exception. [DebuggerHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfOutOfRange(double value, [CallerArgumentExpression("value")] string? paramName = null, double min = double.MinValue, double max = double.MaxValue, string? minName = null, string? maxName = null, string? message = null) + public static void ThrowIfOutOfRange(double value, [CallerArgumentExpression(nameof(value))] string? paramName = null, double min = double.MinValue, double max = double.MaxValue, string? minName = null, string? maxName = null, string? message = null) { Range(value, paramName, min, max, minName, maxName, message); } @@ -170,7 +168,7 @@ public static void ThrowIfOutOfRange(double value, [CallerArgumentExpression("va /// The value casted to the specified type. [DebuggerHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T ThrowIfNotOfType([NotNull] object? value, [CallerArgumentExpression("value")] string? paramName = null) + public static T ThrowIfNotOfType([NotNull] object? value, [CallerArgumentExpression(nameof(value))] string? paramName = null) { if (value is not T result) { diff --git a/src/Shared/MathHelper.cs b/src/Shared/MathHelper.cs index 6bf5b3ac6c..21367918d5 100644 --- a/src/Shared/MathHelper.cs +++ b/src/Shared/MathHelper.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; -#if NET6_0_OR_GREATER +#if NET using System.Numerics; #endif using System.Runtime.CompilerServices; @@ -73,7 +73,7 @@ public static int LeadingZero32(int value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LeadingZero64(long value) { -#if NET6_0_OR_GREATER +#if NET return BitOperations.LeadingZeroCount((ulong)value); #else unchecked @@ -123,7 +123,7 @@ public static long PositiveModulo64(long value, long divisor) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsFinite(double value) { -#if NET6_0_OR_GREATER +#if NET return double.IsFinite(value); #else return !double.IsInfinity(value) && !double.IsNaN(value); diff --git a/src/Shared/Metrics/Base2ExponentialBucketHistogramHelper.cs b/src/Shared/Metrics/Base2ExponentialBucketHistogramHelper.cs index 096eb51828..27f498c0bd 100644 --- a/src/Shared/Metrics/Base2ExponentialBucketHistogramHelper.cs +++ b/src/Shared/Metrics/Base2ExponentialBucketHistogramHelper.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Metrics; /// @@ -23,7 +21,7 @@ public static double CalculateLowerBoundary(int index, int scale) { if (scale > 0) { -#if NET6_0_OR_GREATER +#if NET var inverseFactor = Math.ScaleB(Ln2, -scale); #else var inverseFactor = ScaleB(Ln2, -scale); @@ -49,7 +47,7 @@ public static double CalculateLowerBoundary(int index, int scale) return double.Epsilon; } -#if NET6_0_OR_GREATER +#if NET return Math.ScaleB(1, n); #else return ScaleB(1, n); @@ -57,7 +55,7 @@ public static double CalculateLowerBoundary(int index, int scale) } } -#if !NET6_0_OR_GREATER +#if !NET // Math.ScaleB was introduced in .NET Core 3.0. // This implementation is from: // https://github.com/dotnet/runtime/blob/v7.0.0/src/libraries/System.Private.CoreLib/src/System/Math.cs#L1494 diff --git a/src/Shared/Options/DelegatingOptionsFactory.cs b/src/Shared/Options/DelegatingOptionsFactory.cs index 1b8fe62188..995b69ae49 100644 --- a/src/Shared/Options/DelegatingOptionsFactory.cs +++ b/src/Shared/Options/DelegatingOptionsFactory.cs @@ -13,10 +13,8 @@ we take a dependency on Microsoft.Extensions.Options v5.0.0 (or greater), much example of how that works. */ -#nullable enable - using System.Diagnostics; -#if NET6_0_OR_GREATER +#if NET using System.Diagnostics.CodeAnalysis; #endif using Microsoft.Extensions.Configuration; @@ -27,7 +25,7 @@ namespace Microsoft.Extensions.Options; /// Implementation of . /// /// The type of options being requested. -#if NET6_0_OR_GREATER +#if NET internal sealed class DelegatingOptionsFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions> : #else internal sealed class DelegatingOptionsFactory : diff --git a/src/Shared/Options/DelegatingOptionsFactoryServiceCollectionExtensions.cs b/src/Shared/Options/DelegatingOptionsFactoryServiceCollectionExtensions.cs index 69c7b6c3b6..81e9ac0a66 100644 --- a/src/Shared/Options/DelegatingOptionsFactoryServiceCollectionExtensions.cs +++ b/src/Shared/Options/DelegatingOptionsFactoryServiceCollectionExtensions.cs @@ -1,10 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; -#if NET6_0_OR_GREATER +#if NET using System.Diagnostics.CodeAnalysis; #endif using Microsoft.Extensions.Configuration; @@ -15,7 +13,7 @@ namespace Microsoft.Extensions.DependencyInjection; internal static class DelegatingOptionsFactoryServiceCollectionExtensions { -#if NET6_0_OR_GREATER +#if NET public static IServiceCollection RegisterOptionsFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( #else public static IServiceCollection RegisterOptionsFactory( @@ -40,7 +38,7 @@ public static IServiceCollection RegisterOptionsFactory( return services!; } -#if NET6_0_OR_GREATER +#if NET public static IServiceCollection RegisterOptionsFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( #else public static IServiceCollection RegisterOptionsFactory( @@ -65,7 +63,7 @@ public static IServiceCollection RegisterOptionsFactory( return services!; } -#if NET6_0_OR_GREATER +#if NET public static IServiceCollection DisableOptionsReloading<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( #else public static IServiceCollection DisableOptionsReloading( diff --git a/src/Shared/Options/SingletonOptionsManager.cs b/src/Shared/Options/SingletonOptionsManager.cs index c1807183e3..d3b1928d55 100644 --- a/src/Shared/Options/SingletonOptionsManager.cs +++ b/src/Shared/Options/SingletonOptionsManager.cs @@ -1,15 +1,13 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - -#if NET6_0_OR_GREATER +#if NET using System.Diagnostics.CodeAnalysis; #endif namespace Microsoft.Extensions.Options; -#if NET6_0_OR_GREATER +#if NET internal sealed class SingletonOptionsManager<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions> : IOptionsMonitor, IOptionsSnapshot #else internal sealed class SingletonOptionsManager : IOptionsMonitor, IOptionsSnapshot diff --git a/src/Shared/PeerServiceResolver.cs b/src/Shared/PeerServiceResolver.cs index d4c0b8fcd1..21201b3836 100644 --- a/src/Shared/PeerServiceResolver.cs +++ b/src/Shared/PeerServiceResolver.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Runtime.CompilerServices; using OpenTelemetry.Trace; diff --git a/src/Shared/PeriodicExportingMetricReaderHelper.cs b/src/Shared/PeriodicExportingMetricReaderHelper.cs index d24c74c8f9..e7dc244a94 100644 --- a/src/Shared/PeriodicExportingMetricReaderHelper.cs +++ b/src/Shared/PeriodicExportingMetricReaderHelper.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Metrics; internal static class PeriodicExportingMetricReaderHelper diff --git a/src/Shared/PooledList.cs b/src/Shared/PooledList.cs index 043cd0a452..7027dc4e4b 100644 --- a/src/Shared/PooledList.cs +++ b/src/Shared/PooledList.cs @@ -3,12 +3,13 @@ using System.Buffers; using System.Collections; +using System.Diagnostics.CodeAnalysis; namespace OpenTelemetry.Internal; internal readonly struct PooledList : IEnumerable, ICollection { - private static int lastAllocatedSize = 64; + public static int LastAllocatedSize = 64; private readonly T[] buffer; @@ -33,7 +34,7 @@ public ref T this[int index] public static PooledList Create() { - return new PooledList(ArrayPool.Shared.Rent(lastAllocatedSize), 0); + return new PooledList(ArrayPool.Shared.Rent(LastAllocatedSize), 0); } public static void Add(ref PooledList list, T item) @@ -44,10 +45,10 @@ public static void Add(ref PooledList list, T item) if (list.Count >= buffer.Length) { - lastAllocatedSize = buffer.Length * 2; + LastAllocatedSize = buffer.Length * 2; var previousBuffer = buffer; - buffer = ArrayPool.Shared.Rent(lastAllocatedSize); + buffer = ArrayPool.Shared.Rent(LastAllocatedSize); var span = previousBuffer.AsSpan(); span.CopyTo(buffer); @@ -97,6 +98,7 @@ public struct Enumerator : IEnumerator, IEnumerator private readonly T[] buffer; private readonly int count; private int index; + [AllowNull] private T current; public Enumerator(in PooledList list) @@ -107,9 +109,9 @@ public Enumerator(in PooledList list) this.current = default; } - public T Current { get => this.current; } + public T Current => this.current; - object IEnumerator.Current { get => this.Current; } + object? IEnumerator.Current => this.Current; public void Dispose() { diff --git a/src/Shared/ResourceSemanticConventions.cs b/src/Shared/ResourceSemanticConventions.cs index a3f93c02fa..aa5dae4005 100644 --- a/src/Shared/ResourceSemanticConventions.cs +++ b/src/Shared/ResourceSemanticConventions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Resources; internal static class ResourceSemanticConventions diff --git a/src/Shared/SemanticConventions.cs b/src/Shared/SemanticConventions.cs index 2624acd29c..f84bd6de49 100644 --- a/src/Shared/SemanticConventions.cs +++ b/src/Shared/SemanticConventions.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Trace; /// diff --git a/src/Shared/Shims/Lock.cs b/src/Shared/Shims/Lock.cs new file mode 100644 index 0000000000..8b82deb4d5 --- /dev/null +++ b/src/Shared/Shims/Lock.cs @@ -0,0 +1,18 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#if !NET9_0_OR_GREATER +namespace OpenTelemetry; + +// Note: .NET9 added the System.Threading.Lock class. The goal here is when +// compiling against .NET9+ code should use System.Threading.Lock class for +// better perf. Legacy code can use this class which will perform a classic +// monitor-based lock against a reference of this class. This type is not in the +// System.Threading namespace so that the compiler doesn't get confused when it +// sees it used. It is in OpenTelemetry namespace and not OpenTelemetry.Internal +// namespace so that code should be able to use it without the presence of a +// dedicated "using OpenTelemetry.Internal" just for the shim. +internal sealed class Lock +{ +} +#endif diff --git a/src/Shared/Shims/NullableAttributes.cs b/src/Shared/Shims/NullableAttributes.cs index 72d877e3da..3126b55762 100644 --- a/src/Shared/Shims/NullableAttributes.cs +++ b/src/Shared/Shims/NullableAttributes.cs @@ -6,9 +6,10 @@ #pragma warning disable SA1649 // File name should match first type name #pragma warning disable SA1402 // File may only contain a single type -#if NETFRAMEWORK || NETSTANDARD2_0 +#if NETFRAMEWORK || NETSTANDARD2_0_OR_GREATER namespace System.Diagnostics.CodeAnalysis { +#if NETFRAMEWORK || NETSTANDARD2_0 /// Specifies that null is allowed as an input even if the corresponding type disallows it. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] internal sealed class AllowNullAttribute : Attribute @@ -42,5 +43,26 @@ internal sealed class NotNullIfNotNullAttribute : Attribute public string ParameterName { get; } } +#endif + + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] + internal sealed class MemberNotNullWhenAttribute : Attribute + { + public MemberNotNullWhenAttribute(bool returnValue, string member) + { + ReturnValue = returnValue; + Members = new[] { member }; + } + + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } + + public bool ReturnValue { get; } + + public string[] Members { get; } + } } #endif diff --git a/src/Shared/SpanAttributeConstants.cs b/src/Shared/SpanAttributeConstants.cs index dcd7e5b8ef..a3467bdf4a 100644 --- a/src/Shared/SpanAttributeConstants.cs +++ b/src/Shared/SpanAttributeConstants.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Trace; /// diff --git a/src/Shared/StatusHelper.cs b/src/Shared/StatusHelper.cs index 5d9245010c..00e5a587f9 100644 --- a/src/Shared/StatusHelper.cs +++ b/src/Shared/StatusHelper.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Runtime.CompilerServices; using OpenTelemetry.Trace; diff --git a/src/Shared/TagWriter/ArrayTagWriter.cs b/src/Shared/TagWriter/ArrayTagWriter.cs index ac5bba8bf0..c8859acbb2 100644 --- a/src/Shared/TagWriter/ArrayTagWriter.cs +++ b/src/Shared/TagWriter/ArrayTagWriter.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - namespace OpenTelemetry.Internal; internal abstract class ArrayTagWriter diff --git a/src/Shared/TagWriter/JsonStringArrayTagWriter.cs b/src/Shared/TagWriter/JsonStringArrayTagWriter.cs index 8dd2c086d7..8eefc4b4f9 100644 --- a/src/Shared/TagWriter/JsonStringArrayTagWriter.cs +++ b/src/Shared/TagWriter/JsonStringArrayTagWriter.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using System.Text.Json; diff --git a/src/Shared/TagWriter/TagWriter.cs b/src/Shared/TagWriter/TagWriter.cs index fb56903363..941fa6d6f0 100644 --- a/src/Shared/TagWriter/TagWriter.cs +++ b/src/Shared/TagWriter/TagWriter.cs @@ -1,9 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; +using System.Globalization; namespace OpenTelemetry.Internal; @@ -26,24 +25,33 @@ public bool TryWriteTag( KeyValuePair tag, int? tagValueMaxLength = null) { - if (tag.Value == null) + return this.TryWriteTag(ref state, tag.Key, tag.Value, tagValueMaxLength); + } + + public bool TryWriteTag( + ref TTagState state, + string key, + object? value, + int? tagValueMaxLength = null) + { + if (value == null) { return false; } - switch (tag.Value) + switch (value) { case char c: - this.WriteCharTag(ref state, tag.Key, c); + this.WriteCharTag(ref state, key, c); break; case string s: this.WriteStringTag( ref state, - tag.Key, + key, TruncateString(s.AsSpan(), tagValueMaxLength)); break; case bool b: - this.WriteBooleanTag(ref state, tag.Key, b); + this.WriteBooleanTag(ref state, key, b); break; case byte: case sbyte: @@ -52,23 +60,23 @@ public bool TryWriteTag( case int: case uint: case long: - this.WriteIntegralTag(ref state, tag.Key, Convert.ToInt64(tag.Value)); + this.WriteIntegralTag(ref state, key, Convert.ToInt64(value)); break; case float: case double: - this.WriteFloatingPointTag(ref state, tag.Key, Convert.ToDouble(tag.Value)); + this.WriteFloatingPointTag(ref state, key, Convert.ToDouble(value)); break; case Array array: try { - this.WriteArrayTagInternal(ref state, tag.Key, array, tagValueMaxLength); + this.WriteArrayTagInternal(ref state, key, array, tagValueMaxLength); } catch { // If an exception is thrown when calling ToString // on any element of the array, then the entire array value // is ignored. - return this.LogUnsupportedTagTypeAndReturnFalse(tag.Key, tag.Value); + return this.LogUnsupportedTagTypeAndReturnFalse(key, value); } break; @@ -82,21 +90,21 @@ public bool TryWriteTag( default: try { - var stringValue = Convert.ToString(tag.Value/*TODO: , CultureInfo.InvariantCulture*/); + var stringValue = Convert.ToString(value, CultureInfo.InvariantCulture); if (stringValue == null) { - return this.LogUnsupportedTagTypeAndReturnFalse(tag.Key, tag.Value); + return this.LogUnsupportedTagTypeAndReturnFalse(key, value); } this.WriteStringTag( ref state, - tag.Key, + key, TruncateString(stringValue.AsSpan(), tagValueMaxLength)); } catch { // If ToString throws an exception then the tag is ignored. - return this.LogUnsupportedTagTypeAndReturnFalse(tag.Key, tag.Value); + return this.LogUnsupportedTagTypeAndReturnFalse(key, value); } break; @@ -247,7 +255,7 @@ private void WriteToArrayTypeChecked(ref TArrayState arrayState, Array array, in // case ulong: May throw an exception on overflow. // case decimal: Converting to double produces rounding errors. default: - var stringValue = Convert.ToString(item/*TODO: , CultureInfo.InvariantCulture*/); + var stringValue = Convert.ToString(item, CultureInfo.InvariantCulture); if (stringValue == null) { this.arrayWriter.WriteNullValue(ref arrayState); diff --git a/src/Shared/ThreadSafeRandom.cs b/src/Shared/ThreadSafeRandom.cs index 1ba1b4d91c..10cd995809 100644 --- a/src/Shared/ThreadSafeRandom.cs +++ b/src/Shared/ThreadSafeRandom.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Internal; // Note: Inspired by https://devblogs.microsoft.com/pfxteam/getting-random-numbers-in-a-thread-safe-way/ internal static class ThreadSafeRandom { -#if NET6_0_OR_GREATER +#if NET public static int Next(int min, int max) { return Random.Shared.Next(min, max); diff --git a/test/Benchmarks/Benchmarks.csproj b/test/Benchmarks/Benchmarks.csproj index 915a68730e..d3b3660126 100644 --- a/test/Benchmarks/Benchmarks.csproj +++ b/test/Benchmarks/Benchmarks.csproj @@ -3,9 +3,6 @@ Exe $(TargetFrameworksForTests) - - - disable diff --git a/test/Benchmarks/Context/Propagation/TraceContextPropagatorBenchmarks.cs b/test/Benchmarks/Context/Propagation/TraceContextPropagatorBenchmarks.cs new file mode 100644 index 0000000000..fdae9b0c4f --- /dev/null +++ b/test/Benchmarks/Context/Propagation/TraceContextPropagatorBenchmarks.cs @@ -0,0 +1,70 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using BenchmarkDotNet.Attributes; +using OpenTelemetry.Context.Propagation; + +namespace Benchmarks.Context.Propagation; + +public class TraceContextPropagatorBenchmarks +{ + private const string TraceParent = "traceparent"; + private const string TraceState = "tracestate"; + private const string TraceId = "0af7651916cd43dd8448eb211c80319c"; + private const string SpanId = "b9c7c989f97918e1"; + + private static readonly Random Random = new(455946); + private static readonly TraceContextPropagator TraceContextPropagator = new(); + + private static readonly Func, string, IEnumerable> Getter = (headers, name) => + { + if (headers.TryGetValue(name, out var value)) + { + return [value]; + } + + return []; + }; + + [Params(true, false)] + public bool LongListMember { get; set; } + + [Params(0, 4, 32)] + public int MembersCount { get; set; } + + public Dictionary? Headers { get; private set; } + + [GlobalSetup] + public void Setup() + { + var length = this.LongListMember ? 256 : 20; + + var value = new string('a', length); + + Span keyBuffer = stackalloc char[length - 2]; + + string traceState = string.Empty; + for (var i = 0; i < this.MembersCount; i++) + { + // We want a unique key for each member + for (var j = 0; j < length - 2; j++) + { + keyBuffer[j] = (char)('a' + Random.Next(0, 26)); + } + + var key = keyBuffer.ToString(); + + var listMember = $"{key}{i:00}={value}"; + traceState += i < this.MembersCount - 1 ? $"{listMember}," : listMember; + } + + this.Headers = new Dictionary + { + { TraceParent, $"00-{TraceId}-{SpanId}-01" }, + { TraceState, traceState }, + }; + } + + [Benchmark(Baseline = true)] + public void Extract() => _ = TraceContextPropagator!.Extract(default, this.Headers!, Getter); +} diff --git a/test/Benchmarks/EventSourceBenchmarks.cs b/test/Benchmarks/EventSourceBenchmarks.cs index f830436863..ca8b4ffcd7 100644 --- a/test/Benchmarks/EventSourceBenchmarks.cs +++ b/test/Benchmarks/EventSourceBenchmarks.cs @@ -17,7 +17,7 @@ public void EventWithIdAllocation() activity.Start(); activity.Stop(); - OpenTelemetrySdkEventSource.Log.ActivityStarted(activity.OperationName, activity.Id); + OpenTelemetrySdkEventSource.Log.ActivityStarted(activity.OperationName, activity.Id!); } [Benchmark] diff --git a/test/Benchmarks/Exporter/OtlpGrpcExporterBenchmarks.cs b/test/Benchmarks/Exporter/OtlpGrpcExporterBenchmarks.cs index 2a9dd7cebb..5f41ac0a3f 100644 --- a/test/Benchmarks/Exporter/OtlpGrpcExporterBenchmarks.cs +++ b/test/Benchmarks/Exporter/OtlpGrpcExporterBenchmarks.cs @@ -18,9 +18,9 @@ namespace Benchmarks.Exporter; public class OtlpGrpcExporterBenchmarks { - private OtlpTraceExporter exporter; - private Activity activity; - private CircularBuffer activityBatch; + private OtlpTraceExporter? exporter; + private Activity? activity; + private CircularBuffer? activityBatch; [Params(1, 10, 100)] public int NumberOfBatches { get; set; } @@ -45,8 +45,8 @@ public void GlobalSetup() [GlobalCleanup] public void GlobalCleanup() { - this.exporter.Shutdown(); - this.exporter.Dispose(); + this.exporter?.Shutdown(); + this.exporter?.Dispose(); } [Benchmark] @@ -56,10 +56,10 @@ public void OtlpExporter_Batching() { for (int c = 0; c < this.NumberOfSpans; c++) { - this.activityBatch.Add(this.activity); + this.activityBatch!.Add(this.activity!); } - this.exporter.Export(new Batch(this.activityBatch, this.NumberOfSpans)); + this.exporter!.Export(new Batch(this.activityBatch!, this.NumberOfSpans)); } } } diff --git a/test/Benchmarks/Exporter/OtlpHttpExporterBenchmarks.cs b/test/Benchmarks/Exporter/OtlpHttpExporterBenchmarks.cs index d595eefd9f..1d6c0ad5c1 100644 --- a/test/Benchmarks/Exporter/OtlpHttpExporterBenchmarks.cs +++ b/test/Benchmarks/Exporter/OtlpHttpExporterBenchmarks.cs @@ -20,12 +20,12 @@ namespace Benchmarks.Exporter; public class OtlpHttpExporterBenchmarks { private readonly byte[] buffer = new byte[1024 * 1024]; - private IDisposable server; - private string serverHost; + private IDisposable? server; + private string? serverHost; private int serverPort; - private OtlpTraceExporter exporter; - private Activity activity; - private CircularBuffer activityBatch; + private OtlpTraceExporter? exporter; + private Activity? activity; + private CircularBuffer? activityBatch; [Params(1, 10, 100)] public int NumberOfBatches { get; set; } @@ -73,9 +73,9 @@ public void GlobalSetup() [GlobalCleanup] public void GlobalCleanup() { - this.exporter.Shutdown(); - this.exporter.Dispose(); - this.server.Dispose(); + this.exporter?.Shutdown(); + this.exporter?.Dispose(); + this.server?.Dispose(); } [Benchmark] @@ -85,10 +85,10 @@ public void OtlpExporter_Batching() { for (int c = 0; c < this.NumberOfSpans; c++) { - this.activityBatch.Add(this.activity); + this.activityBatch!.Add(this.activity!); } - this.exporter.Export(new Batch(this.activityBatch, this.NumberOfSpans)); + this.exporter!.Export(new Batch(this.activityBatch!, this.NumberOfSpans)); } } } diff --git a/test/Benchmarks/Exporter/OtlpLogExporterBenchmarks.cs b/test/Benchmarks/Exporter/OtlpLogExporterBenchmarks.cs index 17bcf3dda4..1ec5f4770d 100644 --- a/test/Benchmarks/Exporter/OtlpLogExporterBenchmarks.cs +++ b/test/Benchmarks/Exporter/OtlpLogExporterBenchmarks.cs @@ -36,13 +36,13 @@ namespace Benchmarks.Exporter; public class OtlpLogExporterBenchmarks { - private OtlpLogExporter exporter; - private LogRecord logRecord; - private CircularBuffer logRecordBatch; + private OtlpLogExporter? exporter; + private LogRecord? logRecord; + private CircularBuffer? logRecordBatch; - private IHost host; - private IDisposable server; - private string serverHost; + private IHost? host; + private IDisposable? server; + private string? serverHost; private int serverPort; [GlobalSetup(Target = nameof(OtlpLogExporter_Grpc))] @@ -103,29 +103,29 @@ public void GlobalSetupHttp() [GlobalCleanup(Target = nameof(OtlpLogExporter_Grpc))] public void GlobalCleanupGrpc() { - this.exporter.Shutdown(); - this.exporter.Dispose(); - this.host.Dispose(); + this.exporter?.Shutdown(); + this.exporter?.Dispose(); + this.host?.Dispose(); } [GlobalCleanup(Target = nameof(OtlpLogExporter_Http))] public void GlobalCleanupHttp() { - this.exporter.Shutdown(); - this.exporter.Dispose(); - this.server.Dispose(); + this.exporter?.Shutdown(); + this.exporter?.Dispose(); + this.server?.Dispose(); } [Benchmark] public void OtlpLogExporter_Http() { - this.exporter.Export(new Batch(this.logRecordBatch, 1)); + this.exporter!.Export(new Batch(this.logRecordBatch!, 1)); } [Benchmark] public void OtlpLogExporter_Grpc() { - this.exporter.Export(new Batch(this.logRecordBatch, 1)); + this.exporter!.Export(new Batch(this.logRecordBatch!, 1)); } private sealed class MockLogService : OtlpCollector.LogsService.LogsServiceBase diff --git a/test/Benchmarks/Exporter/OtlpTraceExporterBenchmarks.cs b/test/Benchmarks/Exporter/OtlpTraceExporterBenchmarks.cs index 392e0612c0..5f338fa618 100644 --- a/test/Benchmarks/Exporter/OtlpTraceExporterBenchmarks.cs +++ b/test/Benchmarks/Exporter/OtlpTraceExporterBenchmarks.cs @@ -36,13 +36,13 @@ namespace Benchmarks.Exporter; public class OtlpTraceExporterBenchmarks { - private OtlpTraceExporter exporter; - private Activity activity; - private CircularBuffer activityBatch; + private OtlpTraceExporter? exporter; + private Activity? activity; + private CircularBuffer? activityBatch; - private IHost host; - private IDisposable server; - private string serverHost; + private IHost? host; + private IDisposable? server; + private string? serverHost; private int serverPort; [GlobalSetup(Target = nameof(OtlpTraceExporter_Grpc))] @@ -103,31 +103,31 @@ public void GlobalSetupHttp() [GlobalCleanup(Target = nameof(OtlpTraceExporter_Grpc))] public void GlobalCleanupGrpc() { - this.exporter.Shutdown(); - this.exporter.Dispose(); - this.activity.Dispose(); - this.host.Dispose(); + this.exporter?.Shutdown(); + this.exporter?.Dispose(); + this.activity?.Dispose(); + this.host?.Dispose(); } [GlobalCleanup(Target = nameof(OtlpTraceExporter_Http))] public void GlobalCleanupHttp() { - this.exporter.Shutdown(); - this.exporter.Dispose(); - this.server.Dispose(); - this.activity.Dispose(); + this.exporter?.Shutdown(); + this.exporter?.Dispose(); + this.server?.Dispose(); + this.activity?.Dispose(); } [Benchmark] public void OtlpTraceExporter_Http() { - this.exporter.Export(new Batch(this.activityBatch, 1)); + this.exporter!.Export(new Batch(this.activityBatch!, 1)); } [Benchmark] public void OtlpTraceExporter_Grpc() { - this.exporter.Export(new Batch(this.activityBatch, 1)); + this.exporter!.Export(new Batch(this.activityBatch!, 1)); } private sealed class MockTraceService : OtlpCollector.TraceService.TraceServiceBase diff --git a/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs b/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs index caeda40f44..79f5dde574 100644 --- a/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs +++ b/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs @@ -14,8 +14,8 @@ public class PrometheusSerializerBenchmarks { private readonly List metrics = new(); private readonly byte[] buffer = new byte[85000]; - private Meter meter; - private MeterProvider meterProvider; + private Meter? meter; + private MeterProvider? meterProvider; private Dictionary cache = new Dictionary(); [Params(1, 1000, 10000)] @@ -45,7 +45,7 @@ public void GlobalSetup() public void GlobalCleanup() { this.meter?.Dispose(); - this.meterProvider.Dispose(); + this.meterProvider?.Dispose(); } // TODO: this has a dependency on https://github.com/open-telemetry/opentelemetry-dotnet/issues/2361 diff --git a/test/Benchmarks/Exporter/ZipkinExporterBenchmarks.cs b/test/Benchmarks/Exporter/ZipkinExporterBenchmarks.cs index fb0f802b55..983b6dce2c 100644 --- a/test/Benchmarks/Exporter/ZipkinExporterBenchmarks.cs +++ b/test/Benchmarks/Exporter/ZipkinExporterBenchmarks.cs @@ -19,10 +19,10 @@ namespace Benchmarks.Exporter; public class ZipkinExporterBenchmarks { private readonly byte[] buffer = new byte[4096]; - private Activity activity; - private CircularBuffer activityBatch; - private IDisposable server; - private string serverHost; + private Activity? activity; + private CircularBuffer? activityBatch; + private IDisposable? server; + private string? serverHost; private int serverPort; [Params(1, 10, 100)] @@ -60,7 +60,7 @@ public void GlobalSetup() [GlobalCleanup] public void GlobalCleanup() { - this.server.Dispose(); + this.server?.Dispose(); } [Benchmark] @@ -76,10 +76,10 @@ public void ZipkinExporter_Batching() { for (int c = 0; c < this.NumberOfSpans; c++) { - this.activityBatch.Add(this.activity); + this.activityBatch!.Add(this.activity!); } - exporter.Export(new Batch(this.activityBatch, this.NumberOfSpans)); + exporter.Export(new Batch(this.activityBatch!, this.NumberOfSpans)); } exporter.Shutdown(); diff --git a/test/Benchmarks/Helper/ActivityHelper.cs b/test/Benchmarks/Helper/ActivityHelper.cs index 33f55dafe5..1c5e5e2ea7 100644 --- a/test/Benchmarks/Helper/ActivityHelper.cs +++ b/test/Benchmarks/Helper/ActivityHelper.cs @@ -31,14 +31,14 @@ public static Activity CreateTestActivity() new ActivityEvent( "Event1", eventTimestamp, - new ActivityTagsCollection(new Dictionary + new ActivityTagsCollection(new Dictionary { { "key", "value" }, })), new ActivityEvent( "Event2", eventTimestamp, - new ActivityTagsCollection(new Dictionary + new ActivityTagsCollection(new Dictionary { { "key", "value" }, })), @@ -48,7 +48,7 @@ public static Activity CreateTestActivity() using var activitySource = new ActivitySource(nameof(CreateTestActivity)); - var tags = attributes.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.ToString())); + var tags = attributes.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.ToString())); var links = new[] { new ActivityLink(new ActivityContext( @@ -75,10 +75,10 @@ public static Activity CreateTestActivity() foreach (var evnt in events) { - activity.AddEvent(evnt); + activity!.AddEvent(evnt); } - activity.SetEndTime(endTimestamp); + activity!.SetEndTime(endTimestamp); activity.Stop(); return activity; diff --git a/test/Benchmarks/Helper/LogRecordHelper.cs b/test/Benchmarks/Helper/LogRecordHelper.cs index 0cfd5efde2..f08e62527f 100644 --- a/test/Benchmarks/Helper/LogRecordHelper.cs +++ b/test/Benchmarks/Helper/LogRecordHelper.cs @@ -12,9 +12,9 @@ internal static LogRecord CreateTestLogRecord() { var items = new List(1); using var factory = LoggerFactory.Create(builder => builder - .AddOpenTelemetry(loggerOptions => + .UseOpenTelemetry(logging => { - loggerOptions.AddInMemoryExporter(items); + logging.AddInMemoryExporter(items); })); var logger = factory.CreateLogger("TestLogger"); diff --git a/test/Benchmarks/Logs/Food.cs b/test/Benchmarks/Logs/Food.cs deleted file mode 100644 index b3a9096cc8..0000000000 --- a/test/Benchmarks/Logs/Food.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using Microsoft.Extensions.Logging; - -namespace Benchmarks.Logs; - -public static partial class Food -{ - [LoggerMessage(Level = LogLevel.Information, Message = "Hello from {food} {price}.")] - public static partial void SayHello(this ILogger logger, string food, double price); -} diff --git a/test/Benchmarks/Logs/LogBenchmarks.cs b/test/Benchmarks/Logs/LogBenchmarks.cs index ed1d637e7b..915c115852 100644 --- a/test/Benchmarks/Logs/LogBenchmarks.cs +++ b/test/Benchmarks/Logs/LogBenchmarks.cs @@ -7,23 +7,24 @@ using OpenTelemetry.Logs; /* -BenchmarkDotNet v0.13.10, Windows 11 (10.0.22621.3007/22H2/2022Update/SunValley2) +BenchmarkDotNet v0.13.10, Windows 11 (10.0.22631.3880/23H2/2023Update/SunValley3) 11th Gen Intel Core i7-1185G7 3.00GHz, 1 CPU, 8 logical and 4 physical cores -.NET SDK 8.0.101 - [Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - - -| Method | Mean | Error | StdDev | Gen0 | Allocated | -|------------------------------ |-----------:|----------:|----------:|-------:|----------:| -| NoListenerStringInterpolation | 124.458 ns | 2.5188 ns | 2.2329 ns | 0.0114 | 72 B | -| NoListenerExtensionMethod | 36.326 ns | 0.2916 ns | 0.2435 ns | 0.0102 | 64 B | -| NoListener | 1.375 ns | 0.0586 ns | 0.0896 ns | - | - | -| UnnecessaryIsEnabledCheck | 1.332 ns | 0.0225 ns | 0.0188 ns | - | - | -| CreateLoggerRepeatedly | 48.295 ns | 0.5951 ns | 0.4970 ns | 0.0038 | 24 B | -| OneProcessor | 98.133 ns | 1.8805 ns | 1.5703 ns | 0.0063 | 40 B | -| TwoProcessors | 105.414 ns | 0.4610 ns | 0.3850 ns | 0.0063 | 40 B | -| ThreeProcessors | 102.023 ns | 1.4187 ns | 1.1847 ns | 0.0063 | 40 B | +.NET SDK 8.0.107 + [Host] : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2 + DefaultJob : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2 + + +| Method | Mean | Error | StdDev | Median | Gen0 | Gen1 | Allocated | +|------------------------------ |-----------:|----------:|-----------:|-----------:|-------:|-------:|----------:| +| NoListenerStringInterpolation | 135.503 ns | 2.7458 ns | 4.5114 ns | 135.391 ns | 0.0114 | - | 72 B | +| NoListenerExtensionMethod | 40.218 ns | 0.8249 ns | 2.2581 ns | 39.809 ns | 0.0102 | - | 64 B | +| NoListener | 1.930 ns | 0.0626 ns | 0.1264 ns | 1.889 ns | - | - | - | +| UnnecessaryIsEnabledCheck | 1.531 ns | 0.0542 ns | 0.1267 ns | 1.518 ns | - | - | - | +| CreateLoggerRepeatedly | 53.797 ns | 1.0927 ns | 1.7331 ns | 53.401 ns | 0.0038 | - | 24 B | +| OneProcessor | 111.558 ns | 2.9821 ns | 8.5082 ns | 109.311 ns | 0.0063 | - | 40 B | +| BatchProcessor | 263.650 ns | 5.2908 ns | 14.1223 ns | 258.984 ns | 0.0200 | 0.0043 | 128 B | +| TwoProcessors | 108.701 ns | 2.1964 ns | 4.3355 ns | 108.025 ns | 0.0063 | - | 40 B | +| ThreeProcessors | 105.099 ns | 1.8106 ns | 2.1554 ns | 105.796 ns | 0.0063 | - | 40 B | */ namespace Benchmarks.Logs; @@ -35,11 +36,13 @@ public class LogBenchmarks private readonly ILogger loggerWithNoListener; private readonly ILogger loggerWithOneProcessor; + private readonly ILogger loggerWithBatchProcessor; private readonly ILogger loggerWithTwoProcessors; private readonly ILogger loggerWithThreeProcessors; private readonly ILoggerFactory loggerFactoryWithNoListener; private readonly ILoggerFactory loggerFactoryWithOneProcessor; + private readonly ILoggerFactory loggerFactoryWithBatchProcessor; private readonly ILoggerFactory loggerFactoryWithTwoProcessor; private readonly ILoggerFactory loggerFactoryWithThreeProcessor; @@ -50,25 +53,32 @@ public LogBenchmarks() this.loggerFactoryWithOneProcessor = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => options - .AddProcessor(new DummyLogProcessor())); + builder.UseOpenTelemetry(logging => logging + .AddProcessor(new NoopLogProcessor())); }); this.loggerWithOneProcessor = this.loggerFactoryWithOneProcessor.CreateLogger(); + this.loggerFactoryWithBatchProcessor = LoggerFactory.Create(builder => + { + builder.UseOpenTelemetry(logging => logging + .AddProcessor(new BatchLogRecordExportProcessor(new NoopExporter()))); + }); + this.loggerWithBatchProcessor = this.loggerFactoryWithBatchProcessor.CreateLogger(); + this.loggerFactoryWithTwoProcessor = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => options - .AddProcessor(new DummyLogProcessor()) - .AddProcessor(new DummyLogProcessor())); + builder.UseOpenTelemetry(logging => logging + .AddProcessor(new NoopLogProcessor()) + .AddProcessor(new NoopLogProcessor())); }); this.loggerWithTwoProcessors = this.loggerFactoryWithTwoProcessor.CreateLogger(); this.loggerFactoryWithThreeProcessor = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => options - .AddProcessor(new DummyLogProcessor()) - .AddProcessor(new DummyLogProcessor()) - .AddProcessor(new DummyLogProcessor())); + builder.UseOpenTelemetry(logging => logging + .AddProcessor(new NoopLogProcessor()) + .AddProcessor(new NoopLogProcessor()) + .AddProcessor(new NoopLogProcessor())); }); this.loggerWithThreeProcessors = this.loggerFactoryWithThreeProcessor.CreateLogger(); } @@ -97,7 +107,12 @@ public void NoListenerExtensionMethod() [Benchmark] public void NoListener() { - this.loggerWithNoListener.SayHello(FoodName, FoodPrice); + this.loggerWithNoListener.FoodRecallNotice( + brandName: "Contoso", + productDescription: "Salads", + productType: "Food & Beverages", + recallReasonDescription: "due to a possible health risk from Listeria monocytogenes", + companyName: "Contoso Fresh Vegetables, Inc."); } [Benchmark] @@ -105,7 +120,12 @@ public void UnnecessaryIsEnabledCheck() { if (this.loggerWithNoListener.IsEnabled(LogLevel.Information)) { - this.loggerWithNoListener.SayHello(FoodName, FoodPrice); + this.loggerWithNoListener.FoodRecallNotice( + brandName: "Contoso", + productDescription: "Salads", + productType: "Food & Beverages", + recallReasonDescription: "due to a possible health risk from Listeria monocytogenes", + companyName: "Contoso Fresh Vegetables, Inc."); } } @@ -113,28 +133,67 @@ public void UnnecessaryIsEnabledCheck() public void CreateLoggerRepeatedly() { var logger = this.loggerFactoryWithNoListener.CreateLogger(); - logger.SayHello(FoodName, FoodPrice); + logger.FoodRecallNotice( + brandName: "Contoso", + productDescription: "Salads", + productType: "Food & Beverages", + recallReasonDescription: "due to a possible health risk from Listeria monocytogenes", + companyName: "Contoso Fresh Vegetables, Inc."); } [Benchmark] public void OneProcessor() { - this.loggerWithOneProcessor.SayHello(FoodName, FoodPrice); + this.loggerWithOneProcessor.FoodRecallNotice( + brandName: "Contoso", + productDescription: "Salads", + productType: "Food & Beverages", + recallReasonDescription: "due to a possible health risk from Listeria monocytogenes", + companyName: "Contoso Fresh Vegetables, Inc."); + } + + [Benchmark] + public void BatchProcessor() + { + this.loggerWithBatchProcessor.FoodRecallNotice( + brandName: "Contoso", + productDescription: "Salads", + productType: "Food & Beverages", + recallReasonDescription: "due to a possible health risk from Listeria monocytogenes", + companyName: "Contoso Fresh Vegetables, Inc."); } [Benchmark] public void TwoProcessors() { - this.loggerWithTwoProcessors.SayHello(FoodName, FoodPrice); + this.loggerWithTwoProcessors.FoodRecallNotice( + brandName: "Contoso", + productDescription: "Salads", + productType: "Food & Beverages", + recallReasonDescription: "due to a possible health risk from Listeria monocytogenes", + companyName: "Contoso Fresh Vegetables, Inc."); } [Benchmark] public void ThreeProcessors() { - this.loggerWithThreeProcessors.SayHello(FoodName, FoodPrice); + this.loggerWithThreeProcessors.FoodRecallNotice( + brandName: "Contoso", + productDescription: "Salads", + productType: "Food & Beverages", + recallReasonDescription: "due to a possible health risk from Listeria monocytogenes", + companyName: "Contoso Fresh Vegetables, Inc."); + } + + internal class NoopLogProcessor : BaseProcessor + { } - internal class DummyLogProcessor : BaseProcessor + internal class NoopExporter : BaseExporter { + public override ExportResult Export(in Batch batch) + { + return ExportResult.Success; + } } } diff --git a/test/Benchmarks/Logs/LogScopeBenchmarks.cs b/test/Benchmarks/Logs/LogScopeBenchmarks.cs index affa9f5f66..b8bedab231 100644 --- a/test/Benchmarks/Logs/LogScopeBenchmarks.cs +++ b/test/Benchmarks/Logs/LogScopeBenchmarks.cs @@ -75,6 +75,6 @@ public LogScopeBenchmarks() [Benchmark] public void ForEachScope() { - this.logRecord.ForEachScope(this.callback, null); + this.logRecord.ForEachScope(this.callback!, null); } } diff --git a/test/Benchmarks/Logs/LoggerExtensions.cs b/test/Benchmarks/Logs/LoggerExtensions.cs new file mode 100644 index 0000000000..47cd596243 --- /dev/null +++ b/test/Benchmarks/Logs/LoggerExtensions.cs @@ -0,0 +1,18 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.Logging; + +namespace Benchmarks.Logs; + +internal static partial class LoggerExtensions +{ + [LoggerMessage(LogLevel.Critical, "A `{productType}` recall notice was published for `{brandName} {productDescription}` produced by `{companyName}` ({recallReasonDescription}).")] + public static partial void FoodRecallNotice( + this ILogger logger, + string brandName, + string productDescription, + string productType, + string recallReasonDescription, + string companyName); +} diff --git a/test/Benchmarks/Metrics/Base2ExponentialHistogramBenchmarks.cs b/test/Benchmarks/Metrics/Base2ExponentialHistogramBenchmarks.cs index aec8ba6fce..43a2dfdc4f 100644 --- a/test/Benchmarks/Metrics/Base2ExponentialHistogramBenchmarks.cs +++ b/test/Benchmarks/Metrics/Base2ExponentialHistogramBenchmarks.cs @@ -31,10 +31,10 @@ public class Base2ExponentialHistogramBenchmarks { 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 Histogram histogram; - private MeterProvider meterProvider; - private Meter meter; + private readonly string[] dimensionValues = ["DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10"]; + private Histogram? histogram; + private MeterProvider? meterProvider; + private Meter? meter; [GlobalSetup] public void Setup() @@ -58,29 +58,29 @@ public void Setup() public void Cleanup() { this.meter?.Dispose(); - this.meterProvider.Dispose(); + this.meterProvider?.Dispose(); } [Benchmark] public void HistogramHotPath() { - this.histogram.Record(this.random.Next(MaxValue)); + this.histogram!.Record(this.random.Next(MaxValue)); } [Benchmark] public void HistogramWith1LabelHotPath() { - var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 2)]); - this.histogram.Record(this.random.Next(MaxValue), tag1); + var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 2)]); + this.histogram!.Record(this.random.Next(MaxValue), tag1); } [Benchmark] public void HistogramWith3LabelsHotPath() { - 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)]); - this.histogram.Record(this.random.Next(MaxValue), tag1, tag2, tag3); + 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)]); + this.histogram!.Record(this.random.Next(MaxValue), tag1, tag2, tag3); } [Benchmark] @@ -94,7 +94,7 @@ public void HistogramWith5LabelsHotPath() { "DimName4", this.dimensionValues[this.random.Next(0, 5)] }, { "DimName5", this.dimensionValues[this.random.Next(0, 10)] }, }; - this.histogram.Record(this.random.Next(MaxValue), tags); + this.histogram!.Record(this.random.Next(MaxValue), tags); } [Benchmark] @@ -110,6 +110,6 @@ public void HistogramWith7LabelsHotPath() { "DimName6", this.dimensionValues[this.random.Next(0, 2)] }, { "DimName7", this.dimensionValues[this.random.Next(0, 1)] }, }; - this.histogram.Record(this.random.Next(MaxValue), tags); + this.histogram!.Record(this.random.Next(MaxValue), tags); } } diff --git a/test/Benchmarks/Metrics/Base2ExponentialHistogramMapToIndexBenchmarks.cs b/test/Benchmarks/Metrics/Base2ExponentialHistogramMapToIndexBenchmarks.cs index a7cc5edb5c..5207716c42 100644 --- a/test/Benchmarks/Metrics/Base2ExponentialHistogramMapToIndexBenchmarks.cs +++ b/test/Benchmarks/Metrics/Base2ExponentialHistogramMapToIndexBenchmarks.cs @@ -25,7 +25,7 @@ public class Base2ExponentialHistogramMapToIndexBenchmarks { private const int MaxValue = 10000; private readonly Random random = new(); - private Base2ExponentialBucketHistogram exponentialHistogram; + private Base2ExponentialBucketHistogram? exponentialHistogram; [Params(-11, 3, 20)] public int Scale { get; set; } @@ -39,6 +39,6 @@ public void Setup() [Benchmark] public void MapToIndex() { - this.exponentialHistogram.MapToIndex(this.random.Next(MaxValue)); + this.exponentialHistogram!.MapToIndex(this.random.Next(MaxValue)); } } diff --git a/test/Benchmarks/Metrics/Base2ExponentialHistogramScaleBenchmarks.cs b/test/Benchmarks/Metrics/Base2ExponentialHistogramScaleBenchmarks.cs index d11d8743eb..4c7d01f925 100644 --- a/test/Benchmarks/Metrics/Base2ExponentialHistogramScaleBenchmarks.cs +++ b/test/Benchmarks/Metrics/Base2ExponentialHistogramScaleBenchmarks.cs @@ -28,9 +28,9 @@ public class Base2ExponentialHistogramScaleBenchmarks { private const int MaxValue = 10000; private readonly Random random = new(); - private Histogram histogram; - private MeterProvider meterProvider; - private Meter meter; + private Histogram? histogram; + private MeterProvider? meterProvider; + private Meter? meter; // This is a simple benchmark that records values in the range [0, 10000]. // The reason the following scales are benchmarked are as follows: @@ -66,12 +66,12 @@ public void Setup() public void Cleanup() { this.meter?.Dispose(); - this.meterProvider.Dispose(); + this.meterProvider?.Dispose(); } [Benchmark] public void HistogramHotPath() { - this.histogram.Record(this.random.Next(MaxValue)); + this.histogram!.Record(this.random.Next(MaxValue)); } } diff --git a/test/Benchmarks/Metrics/ExemplarBenchmarks.cs b/test/Benchmarks/Metrics/ExemplarBenchmarks.cs index ac1d347fba..d9369d26e3 100644 --- a/test/Benchmarks/Metrics/ExemplarBenchmarks.cs +++ b/test/Benchmarks/Metrics/ExemplarBenchmarks.cs @@ -42,12 +42,12 @@ public class ExemplarBenchmarks { 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 Histogram histogramWithoutTagReduction; - private Histogram histogramWithTagReduction; - private Counter counterWithoutTagReduction; - private Counter counterWithTagReduction; - private MeterProvider meterProvider; - private Meter meter; + private Histogram? histogramWithoutTagReduction; + private Histogram? histogramWithTagReduction; + private Counter? counterWithoutTagReduction; + private Counter? counterWithTagReduction; + private MeterProvider? meterProvider; + private Meter? meter; [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "Test only.")] public enum ExemplarConfigurationType @@ -104,7 +104,7 @@ public void Setup() }) .Build(); - ExemplarReservoir CreateExemplarReservoir() + ExemplarReservoir? CreateExemplarReservoir() { return this.ExemplarConfiguration == ExemplarConfigurationType.AlwaysOnWithHighValueSampling ? new HighValueExemplarReservoir(800D) @@ -116,13 +116,13 @@ ExemplarReservoir CreateExemplarReservoir() public void Cleanup() { this.meter?.Dispose(); - this.meterProvider.Dispose(); + this.meterProvider?.Dispose(); } [Benchmark] public void HistogramNoTagReduction() { - var random = ThreadLocalRandom.Value; + var random = ThreadLocalRandom.Value!; var tags = new TagList { { "DimName1", this.dimensionValues[random.Next(0, 2)] }, @@ -132,13 +132,13 @@ public void HistogramNoTagReduction() { "DimName5", this.dimensionValues[random.Next(0, 10)] }, }; - this.histogramWithoutTagReduction.Record(random.NextDouble() * 1000D, tags); + this.histogramWithoutTagReduction!.Record(random.NextDouble() * 1000D, tags); } [Benchmark] public void HistogramWithTagReduction() { - var random = ThreadLocalRandom.Value; + var random = ThreadLocalRandom.Value!; var tags = new TagList { { "DimName1", this.dimensionValues[random.Next(0, 2)] }, @@ -148,13 +148,13 @@ public void HistogramWithTagReduction() { "DimName5", this.dimensionValues[random.Next(0, 10)] }, }; - this.histogramWithTagReduction.Record(random.NextDouble() * 1000D, tags); + this.histogramWithTagReduction!.Record(random.NextDouble() * 1000D, tags); } [Benchmark] public void CounterNoTagReduction() { - var random = ThreadLocalRandom.Value; + var random = ThreadLocalRandom.Value!; var tags = new TagList { { "DimName1", this.dimensionValues[random.Next(0, 2)] }, @@ -164,13 +164,13 @@ public void CounterNoTagReduction() { "DimName5", this.dimensionValues[random.Next(0, 10)] }, }; - this.counterWithoutTagReduction.Add(random.Next(1000), tags); + this.counterWithoutTagReduction!.Add(random.Next(1000), tags); } [Benchmark] public void CounterWithTagReduction() { - var random = ThreadLocalRandom.Value; + var random = ThreadLocalRandom.Value!; var tags = new TagList { { "DimName1", this.dimensionValues[random.Next(0, 2)] }, @@ -180,7 +180,7 @@ public void CounterWithTagReduction() { "DimName5", this.dimensionValues[random.Next(0, 10)] }, }; - this.counterWithTagReduction.Add(random.Next(1000), tags); + this.counterWithTagReduction!.Add(random.Next(1000), tags); } private sealed class HighValueExemplarReservoir : FixedSizeExemplarReservoir diff --git a/test/Benchmarks/Metrics/HistogramBenchmarks.cs b/test/Benchmarks/Metrics/HistogramBenchmarks.cs index 17bbad18c6..e30ae2ffad 100644 --- a/test/Benchmarks/Metrics/HistogramBenchmarks.cs +++ b/test/Benchmarks/Metrics/HistogramBenchmarks.cs @@ -47,10 +47,10 @@ public class HistogramBenchmarks 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 Histogram histogram; - private MeterProvider meterProvider; - private Meter meter; - private double[] bounds; + private Histogram? histogram; + private MeterProvider? meterProvider; + private Meter? meter; + private double[]? bounds; // Note: Values related to `HistogramBuckets.DefaultHistogramCountForBinarySearch` [Params(10, 49, 50, 1000)] @@ -85,29 +85,29 @@ public void Setup() public void Cleanup() { this.meter?.Dispose(); - this.meterProvider.Dispose(); + this.meterProvider?.Dispose(); } [Benchmark] public void HistogramHotPath() { - this.histogram.Record(this.random.Next(MaxValue)); + this.histogram!.Record(this.random.Next(MaxValue)); } [Benchmark] public void HistogramWith1LabelHotPath() { - var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 2)]); - this.histogram.Record(this.random.Next(MaxValue), tag1); + var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 2)]); + this.histogram!.Record(this.random.Next(MaxValue), tag1); } [Benchmark] public void HistogramWith3LabelsHotPath() { - 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)]); - this.histogram.Record(this.random.Next(MaxValue), tag1, tag2, tag3); + 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)]); + this.histogram!.Record(this.random.Next(MaxValue), tag1, tag2, tag3); } [Benchmark] @@ -121,7 +121,7 @@ public void HistogramWith5LabelsHotPath() { "DimName4", this.dimensionValues[this.random.Next(0, 5)] }, { "DimName5", this.dimensionValues[this.random.Next(0, 10)] }, }; - this.histogram.Record(this.random.Next(MaxValue), tags); + this.histogram!.Record(this.random.Next(MaxValue), tags); } [Benchmark] @@ -137,6 +137,6 @@ public void HistogramWith7LabelsHotPath() { "DimName6", this.dimensionValues[this.random.Next(0, 2)] }, { "DimName7", this.dimensionValues[this.random.Next(0, 1)] }, }; - this.histogram.Record(this.random.Next(MaxValue), tags); + this.histogram!.Record(this.random.Next(MaxValue), tags); } } diff --git a/test/Benchmarks/Metrics/MetricCollectBenchmarks.cs b/test/Benchmarks/Metrics/MetricCollectBenchmarks.cs index c0e0bdebb4..ac3f0cf20c 100644 --- a/test/Benchmarks/Metrics/MetricCollectBenchmarks.cs +++ b/test/Benchmarks/Metrics/MetricCollectBenchmarks.cs @@ -29,12 +29,12 @@ public class MetricCollectBenchmarks // TODO: Confirm if this needs to be thread-safe private readonly Random random = new(); - private Counter counter; - private MeterProvider provider; - private Meter meter; - private CancellationTokenSource token; - private BaseExportingMetricReader reader; - private Task writeMetricTask; + private Counter? counter; + private MeterProvider? provider; + private Meter? meter; + private CancellationTokenSource? token; + private BaseExportingMetricReader? reader; + private Task? writeMetricTask; [Params(false, true)] public bool UseWithRef { get; set; } @@ -86,9 +86,9 @@ void ProcessExport(Batch batch) { while (!this.token.IsCancellationRequested) { - 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)]); + 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)]); this.counter.Add(100.00, tag1, tag2, tag3); } }); @@ -98,16 +98,16 @@ void ProcessExport(Batch batch) [GlobalCleanup] public void Cleanup() { - this.token.Cancel(); - this.token.Dispose(); - this.writeMetricTask.Wait(); - this.meter.Dispose(); - this.provider.Dispose(); + this.token?.Cancel(); + this.token?.Dispose(); + this.writeMetricTask?.Wait(); + this.meter?.Dispose(); + this.provider?.Dispose(); } [Benchmark] public void Collect() { - this.reader.Collect(); + this.reader!.Collect(); } } diff --git a/test/Benchmarks/Metrics/MetricsBenchmarks.cs b/test/Benchmarks/Metrics/MetricsBenchmarks.cs index e63aeb8757..117bf59a5e 100644 --- a/test/Benchmarks/Metrics/MetricsBenchmarks.cs +++ b/test/Benchmarks/Metrics/MetricsBenchmarks.cs @@ -60,9 +60,9 @@ public class MetricsBenchmarks { private readonly Random random = new(); private readonly string[] dimensionValues = new string[] { "DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10" }; - private Counter counter; - private MeterProvider meterProvider; - private Meter meter; + private Counter? counter; + private MeterProvider? meterProvider; + private Meter? meter; [Params(MetricReaderTemporalityPreference.Cumulative, MetricReaderTemporalityPreference.Delta)] public MetricReaderTemporalityPreference AggregationTemporality { get; set; } @@ -89,83 +89,83 @@ public void Setup() public void Cleanup() { this.meter?.Dispose(); - this.meterProvider.Dispose(); + this.meterProvider?.Dispose(); } [Benchmark] public void CounterHotPath() { - this.counter.Add(100); + this.counter!.Add(100); } [Benchmark] public void CounterWith1LabelsHotPath() { - var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 10)]); - this.counter.Add(100, tag1); + var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 10)]); + this.counter!.Add(100, tag1); } [Benchmark] public void CounterWith2LabelsHotPath() { - var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 10)]); - var tag2 = new KeyValuePair("DimName2", this.dimensionValues[this.random.Next(0, 10)]); - this.counter.Add(100, tag1, tag2); + var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 10)]); + var tag2 = new KeyValuePair("DimName2", this.dimensionValues[this.random.Next(0, 10)]); + this.counter!.Add(100, tag1, tag2); } [Benchmark] public void CounterWith3LabelsHotPath() { - 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)]); - this.counter.Add(100, tag1, tag2, tag3); + 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)]); + this.counter!.Add(100, tag1, tag2, tag3); } [Benchmark] public void CounterWith4LabelsHotPath() { - var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 2)]); - var tag2 = new KeyValuePair("DimName2", this.dimensionValues[this.random.Next(0, 5)]); - var tag3 = new KeyValuePair("DimName3", this.dimensionValues[this.random.Next(0, 10)]); - var tag4 = new KeyValuePair("DimName4", this.dimensionValues[this.random.Next(0, 10)]); - this.counter.Add(100, tag1, tag2, tag3, tag4); + var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 2)]); + var tag2 = new KeyValuePair("DimName2", this.dimensionValues[this.random.Next(0, 5)]); + var tag3 = new KeyValuePair("DimName3", this.dimensionValues[this.random.Next(0, 10)]); + var tag4 = new KeyValuePair("DimName4", this.dimensionValues[this.random.Next(0, 10)]); + this.counter!.Add(100, tag1, tag2, tag3, tag4); } [Benchmark] public void CounterWith5LabelsHotPath() { - var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 2)]); - var tag2 = new KeyValuePair("DimName2", this.dimensionValues[this.random.Next(0, 2)]); - var tag3 = new KeyValuePair("DimName3", this.dimensionValues[this.random.Next(0, 5)]); - var tag4 = new KeyValuePair("DimName4", this.dimensionValues[this.random.Next(0, 5)]); - var tag5 = new KeyValuePair("DimName5", this.dimensionValues[this.random.Next(0, 10)]); - this.counter.Add(100, tag1, tag2, tag3, tag4, tag5); + var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 2)]); + var tag2 = new KeyValuePair("DimName2", this.dimensionValues[this.random.Next(0, 2)]); + var tag3 = new KeyValuePair("DimName3", this.dimensionValues[this.random.Next(0, 5)]); + var tag4 = new KeyValuePair("DimName4", this.dimensionValues[this.random.Next(0, 5)]); + var tag5 = new KeyValuePair("DimName5", this.dimensionValues[this.random.Next(0, 10)]); + this.counter!.Add(100, tag1, tag2, tag3, tag4, tag5); } [Benchmark] public void CounterWith6LabelsHotPath() { - var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 2)]); - var tag2 = new KeyValuePair("DimName2", this.dimensionValues[this.random.Next(0, 2)]); - var tag3 = new KeyValuePair("DimName3", this.dimensionValues[this.random.Next(0, 2)]); - var tag4 = new KeyValuePair("DimName4", this.dimensionValues[this.random.Next(0, 5)]); - var tag5 = new KeyValuePair("DimName5", this.dimensionValues[this.random.Next(0, 5)]); - var tag6 = new KeyValuePair("DimName6", this.dimensionValues[this.random.Next(0, 5)]); - this.counter.Add(100, tag1, tag2, tag3, tag4, tag5, tag6); + var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 2)]); + var tag2 = new KeyValuePair("DimName2", this.dimensionValues[this.random.Next(0, 2)]); + var tag3 = new KeyValuePair("DimName3", this.dimensionValues[this.random.Next(0, 2)]); + var tag4 = new KeyValuePair("DimName4", this.dimensionValues[this.random.Next(0, 5)]); + var tag5 = new KeyValuePair("DimName5", this.dimensionValues[this.random.Next(0, 5)]); + var tag6 = new KeyValuePair("DimName6", this.dimensionValues[this.random.Next(0, 5)]); + this.counter!.Add(100, tag1, tag2, tag3, tag4, tag5, tag6); } [Benchmark] public void CounterWith7LabelsHotPath() { - var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 1)]); - var tag2 = new KeyValuePair("DimName2", this.dimensionValues[this.random.Next(0, 2)]); - var tag3 = new KeyValuePair("DimName3", this.dimensionValues[this.random.Next(0, 2)]); - var tag4 = new KeyValuePair("DimName4", this.dimensionValues[this.random.Next(0, 2)]); - var tag5 = new KeyValuePair("DimName5", this.dimensionValues[this.random.Next(0, 5)]); - var tag6 = new KeyValuePair("DimName6", this.dimensionValues[this.random.Next(0, 5)]); - var tag7 = new KeyValuePair("DimName7", this.dimensionValues[this.random.Next(0, 5)]); - this.counter.Add(100, tag1, tag2, tag3, tag4, tag5, tag6, tag7); + var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 1)]); + var tag2 = new KeyValuePair("DimName2", this.dimensionValues[this.random.Next(0, 2)]); + var tag3 = new KeyValuePair("DimName3", this.dimensionValues[this.random.Next(0, 2)]); + var tag4 = new KeyValuePair("DimName4", this.dimensionValues[this.random.Next(0, 2)]); + var tag5 = new KeyValuePair("DimName5", this.dimensionValues[this.random.Next(0, 5)]); + var tag6 = new KeyValuePair("DimName6", this.dimensionValues[this.random.Next(0, 5)]); + var tag7 = new KeyValuePair("DimName7", this.dimensionValues[this.random.Next(0, 5)]); + this.counter!.Add(100, tag1, tag2, tag3, tag4, tag5, tag6, tag7); } [Benchmark] @@ -175,7 +175,7 @@ public void CounterWith1LabelsHotPathUsingTagList() { { "DimName1", this.dimensionValues[this.random.Next(0, 10)] }, }; - this.counter.Add(100, tags); + this.counter!.Add(100, tags); } [Benchmark] @@ -186,7 +186,7 @@ public void CounterWith2LabelsHotPathUsingTagList() { "DimName1", this.dimensionValues[this.random.Next(0, 10)] }, { "DimName2", this.dimensionValues[this.random.Next(0, 10)] }, }; - this.counter.Add(100, tags); + this.counter!.Add(100, tags); } [Benchmark] @@ -198,7 +198,7 @@ public void CounterWith3LabelsHotPathUsingTagList() { "DimName2", this.dimensionValues[this.random.Next(0, 10)] }, { "DimName3", this.dimensionValues[this.random.Next(0, 10)] }, }; - this.counter.Add(100, tags); + this.counter!.Add(100, tags); } [Benchmark] @@ -211,7 +211,7 @@ public void CounterWith4LabelsHotPathUsingTagList() { "DimName3", this.dimensionValues[this.random.Next(0, 10)] }, { "DimName4", this.dimensionValues[this.random.Next(0, 10)] }, }; - this.counter.Add(100, tags); + this.counter!.Add(100, tags); } [Benchmark] @@ -225,7 +225,7 @@ public void CounterWith5LabelsHotPathUsingTagList() { "DimName4", this.dimensionValues[this.random.Next(0, 5)] }, { "DimName5", this.dimensionValues[this.random.Next(0, 10)] }, }; - this.counter.Add(100, tags); + this.counter!.Add(100, tags); } [Benchmark] @@ -240,7 +240,7 @@ public void CounterWith6LabelsHotPathUsingTagList() { "DimName5", this.dimensionValues[this.random.Next(0, 5)] }, { "DimName6", this.dimensionValues[this.random.Next(0, 5)] }, }; - this.counter.Add(100, tags); + this.counter!.Add(100, tags); } [Benchmark] @@ -256,7 +256,7 @@ public void CounterWith7LabelsHotPathUsingTagList() { "DimName6", this.dimensionValues[this.random.Next(0, 5)] }, { "DimName7", this.dimensionValues[this.random.Next(0, 5)] }, }; - this.counter.Add(100, tags); + this.counter!.Add(100, tags); } [Benchmark] @@ -273,7 +273,7 @@ public void CounterWith8LabelsHotPathUsingTagList() { "DimName7", this.dimensionValues[this.random.Next(0, 5)] }, { "DimName8", this.dimensionValues[this.random.Next(0, 5)] }, }; - this.counter.Add(100, tags); + this.counter!.Add(100, tags); } [Benchmark] @@ -291,6 +291,6 @@ public void CounterWith9LabelsHotPathUsingTagList() { "DimName8", this.dimensionValues[this.random.Next(0, 5)] }, { "DimName9", this.dimensionValues[this.random.Next(0, 5)] }, }; - this.counter.Add(100, tags); + this.counter!.Add(100, tags); } } diff --git a/test/Benchmarks/Metrics/MetricsViewBenchmarks.cs b/test/Benchmarks/Metrics/MetricsViewBenchmarks.cs index 359313ac9b..ce9cb40b6c 100644 --- a/test/Benchmarks/Metrics/MetricsViewBenchmarks.cs +++ b/test/Benchmarks/Metrics/MetricsViewBenchmarks.cs @@ -32,10 +32,10 @@ public class MetricsViewBenchmarks 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 List metrics; - private Counter counter; - private MeterProvider meterProvider; - private Meter meter; + private List? metrics; + private Counter? counter; + private MeterProvider? meterProvider; + private Meter? meter; public enum ViewConfiguration { @@ -130,13 +130,13 @@ public void Setup() public void Cleanup() { this.meter?.Dispose(); - this.meterProvider.Dispose(); + this.meterProvider?.Dispose(); } [Benchmark] public void CounterHotPath() { - var random = ThreadLocalRandom.Value; + var random = ThreadLocalRandom.Value!; var tags = new TagList { { "DimName1", DimensionValues[random.Next(0, 2)] }, diff --git a/test/Benchmarks/TestTraceServiceClient.cs b/test/Benchmarks/TestTraceServiceClient.cs index 968ddd7571..fc60f90278 100644 --- a/test/Benchmarks/TestTraceServiceClient.cs +++ b/test/Benchmarks/TestTraceServiceClient.cs @@ -10,7 +10,7 @@ namespace Benchmarks; internal class TestTraceServiceClient : TraceService.TraceServiceClient { - public override ExportTraceServiceResponse Export(ExportTraceServiceRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) + public override ExportTraceServiceResponse Export(ExportTraceServiceRequest request, Metadata? headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) { return new ExportTraceServiceResponse(); } diff --git a/test/Benchmarks/Trace/ActivityCreationBenchmarks.cs b/test/Benchmarks/Trace/ActivityCreationBenchmarks.cs index 447e550bd8..2ec79e4ef3 100644 --- a/test/Benchmarks/Trace/ActivityCreationBenchmarks.cs +++ b/test/Benchmarks/Trace/ActivityCreationBenchmarks.cs @@ -29,7 +29,7 @@ public class ActivityCreationBenchmarks { private readonly ActivitySource benchmarkSource = new("Benchmark"); private readonly ActivityContext parentCtx = new(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None); - private TracerProvider tracerProvider; + private TracerProvider? tracerProvider; [GlobalSetup] public void GlobalSetup() @@ -43,8 +43,8 @@ public void GlobalSetup() [GlobalCleanup] public void GlobalCleanup() { - this.tracerProvider.Dispose(); - this.benchmarkSource.Dispose(); + this.tracerProvider?.Dispose(); + this.benchmarkSource?.Dispose(); } [Benchmark] diff --git a/test/Benchmarks/Trace/SamplerBenchmarks.cs b/test/Benchmarks/Trace/SamplerBenchmarks.cs index 440536f281..a835daea37 100644 --- a/test/Benchmarks/Trace/SamplerBenchmarks.cs +++ b/test/Benchmarks/Trace/SamplerBenchmarks.cs @@ -92,7 +92,7 @@ public void SamplerAppendingTraceState() internal class TestSampler : Sampler { - public Func SamplingAction { get; set; } + public Func? SamplingAction { get; set; } public override SamplingResult ShouldSample(in SamplingParameters samplingParameters) { diff --git a/test/Benchmarks/Trace/SpanCreationBenchmarks.cs b/test/Benchmarks/Trace/SpanCreationBenchmarks.cs index 9ca218f2dc..4aacae1586 100644 --- a/test/Benchmarks/Trace/SpanCreationBenchmarks.cs +++ b/test/Benchmarks/Trace/SpanCreationBenchmarks.cs @@ -32,11 +32,11 @@ namespace Benchmarks.Trace; public class SpanCreationBenchmarks { - private Tracer alwaysSampleTracer; - private Tracer neverSampleTracer; - private Tracer noopTracer; - private TracerProvider tracerProviderAlwaysOnSample; - private TracerProvider tracerProviderAlwaysOffSample; + private Tracer? alwaysSampleTracer; + private Tracer? neverSampleTracer; + private Tracer? noopTracer; + private TracerProvider? tracerProviderAlwaysOnSample; + private TracerProvider? tracerProviderAlwaysOffSample; [GlobalSetup] public void GlobalSetup() @@ -61,37 +61,37 @@ public void GlobalSetup() [GlobalCleanup] public void GlobalCleanup() { - this.tracerProviderAlwaysOffSample.Dispose(); - this.tracerProviderAlwaysOnSample.Dispose(); + this.tracerProviderAlwaysOffSample?.Dispose(); + this.tracerProviderAlwaysOnSample?.Dispose(); } [Benchmark] - public void CreateSpan_Sampled() => SpanCreationScenarios.CreateSpan(this.alwaysSampleTracer); + public void CreateSpan_Sampled() => SpanCreationScenarios.CreateSpan(this.alwaysSampleTracer!); [Benchmark] - public void CreateSpan_ParentContext() => SpanCreationScenarios.CreateSpan_ParentContext(this.alwaysSampleTracer); + public void CreateSpan_ParentContext() => SpanCreationScenarios.CreateSpan_ParentContext(this.alwaysSampleTracer!); [Benchmark] - public void CreateSpan_Attributes_Sampled() => SpanCreationScenarios.CreateSpan_Attributes(this.alwaysSampleTracer); + public void CreateSpan_Attributes_Sampled() => SpanCreationScenarios.CreateSpan_Attributes(this.alwaysSampleTracer!); [Benchmark] - public void CreateSpan_WithSpan() => SpanCreationScenarios.CreateSpan_Propagate(this.alwaysSampleTracer); + public void CreateSpan_WithSpan() => SpanCreationScenarios.CreateSpan_Propagate(this.alwaysSampleTracer!); [Benchmark] - public void CreateSpan_Active() => SpanCreationScenarios.CreateSpan_Active(this.alwaysSampleTracer); + public void CreateSpan_Active() => SpanCreationScenarios.CreateSpan_Active(this.alwaysSampleTracer!); [Benchmark] - public void CreateSpan_Active_GetCurrent() => SpanCreationScenarios.CreateSpan_Active_GetCurrent(this.alwaysSampleTracer); + public void CreateSpan_Active_GetCurrent() => SpanCreationScenarios.CreateSpan_Active_GetCurrent(this.alwaysSampleTracer!); [Benchmark] - public void CreateSpan_Attributes_NotSampled() => SpanCreationScenarios.CreateSpan_Attributes(this.neverSampleTracer); + public void CreateSpan_Attributes_NotSampled() => SpanCreationScenarios.CreateSpan_Attributes(this.neverSampleTracer!); [Benchmark(Baseline = true)] - public void CreateSpan_Noop() => SpanCreationScenarios.CreateSpan(this.noopTracer); + public void CreateSpan_Noop() => SpanCreationScenarios.CreateSpan(this.noopTracer!); [Benchmark] - public void CreateSpan_Attributes_Noop() => SpanCreationScenarios.CreateSpan_Attributes(this.noopTracer); + public void CreateSpan_Attributes_Noop() => SpanCreationScenarios.CreateSpan_Attributes(this.noopTracer!); [Benchmark] - public void CreateSpan_Propagate_Noop() => SpanCreationScenarios.CreateSpan_Propagate(this.noopTracer); + public void CreateSpan_Propagate_Noop() => SpanCreationScenarios.CreateSpan_Propagate(this.noopTracer!); } diff --git a/test/Directory.Build.targets b/test/Directory.Build.targets index 5a745da336..d1457ef460 100644 --- a/test/Directory.Build.targets +++ b/test/Directory.Build.targets @@ -1,10 +1,15 @@ - - - + + + + + + + + + diff --git a/test/Directory.Packages.props b/test/Directory.Packages.props deleted file mode 100644 index 575224321a..0000000000 --- a/test/Directory.Packages.props +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/test/OpenTelemetry.Api.Tests/BaggageTests.cs b/test/OpenTelemetry.Api.Tests/BaggageTests.cs index e13aa68a50..904cba5896 100644 --- a/test/OpenTelemetry.Api.Tests/BaggageTests.cs +++ b/test/OpenTelemetry.Api.Tests/BaggageTests.cs @@ -27,8 +27,8 @@ public void SetAndGetTest() { var list = new List>(2) { - new KeyValuePair(K1, V1), - new KeyValuePair(K2, V2), + new(K1, V1), + new(K2, V2), }; Baggage.SetBaggage(K1, V1); @@ -44,7 +44,7 @@ public void SetAndGetTest() Assert.Null(Baggage.GetBaggage("NO_KEY")); Assert.Equal(V2, Baggage.Current.GetBaggage(K2)); - Assert.Throws(() => Baggage.GetBaggage(null)); + Assert.Throws(() => Baggage.GetBaggage(null!)); } [Fact] @@ -52,12 +52,12 @@ public void SetExistingKeyTest() { var list = new List>(2) { - new KeyValuePair(K1, V1), + new(K1, V1), }; - Baggage.Current.SetBaggage(new KeyValuePair(K1, V1)); + Baggage.Current.SetBaggage(new KeyValuePair(K1, V1)); var baggage = Baggage.SetBaggage(K1, V1); - Baggage.SetBaggage(new Dictionary { [K1] = V1 }, baggage); + Baggage.SetBaggage(new Dictionary { [K1] = V1 }, baggage); Assert.Equal(list, Baggage.GetBaggage()); } @@ -78,7 +78,7 @@ public void SetNullValueTest() Assert.Empty(Baggage.SetBaggage(K1, null).GetBaggage()); Baggage.SetBaggage(K1, V1); - Baggage.SetBaggage(new Dictionary + Baggage.SetBaggage(new Dictionary { [K1] = null, [K2] = V2, @@ -94,7 +94,7 @@ public void RemoveTest() var empty2 = Baggage.RemoveBaggage(K1); Assert.True(empty == empty2); - var baggage = Baggage.SetBaggage(new Dictionary + var baggage = Baggage.SetBaggage(new Dictionary { [K1] = V1, [K2] = V2, @@ -112,7 +112,7 @@ public void RemoveTest() [Fact] public void ClearTest() { - var baggage = Baggage.SetBaggage(new Dictionary + var baggage = Baggage.SetBaggage(new Dictionary { [K1] = V1, [K2] = V2, @@ -151,8 +151,8 @@ public void EnumeratorTest() { var list = new List>(2) { - new KeyValuePair(K1, V1), - new KeyValuePair(K2, V2), + new(K1, V1), + new(K2, V2), }; var baggage = Baggage.SetBaggage(K1, V1); @@ -178,11 +178,11 @@ public void EnumeratorTest() [Fact] public void EqualsTest() { - var bc1 = new Baggage(new Dictionary() { [K1] = V1, [K2] = V2 }); - var bc2 = new Baggage(new Dictionary() { [K1] = V1, [K2] = V2 }); - var bc3 = new Baggage(new Dictionary() { [K2] = V2, [K1] = V1 }); - var bc4 = new Baggage(new Dictionary() { [K1] = V1, [K2] = V1 }); - var bc5 = new Baggage(new Dictionary() { [K1] = V2, [K2] = V1 }); + var bc1 = new Baggage(new Dictionary { [K1] = V1, [K2] = V2 }); + var bc2 = new Baggage(new Dictionary { [K1] = V1, [K2] = V2 }); + var bc3 = new Baggage(new Dictionary { [K2] = V2, [K1] = V1 }); + var bc4 = new Baggage(new Dictionary { [K1] = V1, [K2] = V1 }); + var bc5 = new Baggage(new Dictionary { [K1] = V2, [K2] = V1 }); Assert.True(bc1.Equals(bc2)); @@ -207,7 +207,7 @@ public void CreateBaggageTest() ["key2"] = "value2", ["KEY2"] = "VALUE2", ["KEY3"] = "VALUE3", - ["Key3"] = null, + ["Key3"] = null!, // Note: This causes Key3 to be removed }); Assert.Equal(2, baggage.Count); @@ -232,7 +232,7 @@ public void EqualityTests() baggage = Baggage.SetBaggage(K1, V1); - var baggage2 = Baggage.SetBaggage(null); + var baggage2 = Baggage.SetBaggage(null!); Assert.Equal(baggage, baggage2); diff --git a/test/OpenTelemetry.Api.Tests/Trace/Propagation/B3PropagatorTest.cs b/test/OpenTelemetry.Api.Tests/Context/Propagation/B3PropagatorTest.cs similarity index 99% rename from test/OpenTelemetry.Api.Tests/Trace/Propagation/B3PropagatorTest.cs rename to test/OpenTelemetry.Api.Tests/Context/Propagation/B3PropagatorTest.cs index 8cc20c6ff5..4ff05249c5 100644 --- a/test/OpenTelemetry.Api.Tests/Trace/Propagation/B3PropagatorTest.cs +++ b/test/OpenTelemetry.Api.Tests/Context/Propagation/B3PropagatorTest.cs @@ -24,8 +24,12 @@ public class B3PropagatorTest private static readonly Func, string, IEnumerable> Getter = (d, k) => { - d.TryGetValue(k, out var v); - return new string[] { v }; + if (d.TryGetValue(k, out var v)) + { + return [v]; + } + + return []; }; private readonly B3Propagator b3propagator = new(); diff --git a/test/OpenTelemetry.Api.Tests/Trace/Propagation/BaggagePropagatorTest.cs b/test/OpenTelemetry.Api.Tests/Context/Propagation/BaggagePropagatorTest.cs similarity index 84% rename from test/OpenTelemetry.Api.Tests/Trace/Propagation/BaggagePropagatorTest.cs rename to test/OpenTelemetry.Api.Tests/Context/Propagation/BaggagePropagatorTest.cs index 9d8a897258..6575d2949f 100644 --- a/test/OpenTelemetry.Api.Tests/Trace/Propagation/BaggagePropagatorTest.cs +++ b/test/OpenTelemetry.Api.Tests/Context/Propagation/BaggagePropagatorTest.cs @@ -11,8 +11,12 @@ public class BaggagePropagatorTest private static readonly Func, string, IEnumerable> Getter = (d, k) => { - d.TryGetValue(k, out var v); - return new string[] { v }; + if (d.TryGetValue(k, out var v)) + { + return [v]; + } + + return []; }; private static readonly Func>, string, IEnumerable> GetterList = @@ -38,7 +42,7 @@ public void ValidateFieldsProperty() [Fact] public void ValidateDefaultCarrierExtraction() { - var propagationContext = this.baggage.Extract(default, null, null); + var propagationContext = this.baggage.Extract(default, null!, null!); Assert.Equal(default, propagationContext); } @@ -46,7 +50,7 @@ public void ValidateDefaultCarrierExtraction() public void ValidateDefaultGetterExtraction() { var carrier = new Dictionary(); - var propagationContext = this.baggage.Extract(default, carrier, null); + var propagationContext = this.baggage.Extract(default, carrier, null!); Assert.Equal(default, propagationContext); } @@ -131,9 +135,7 @@ public void ValidateSpecialCharsBaggageExtraction() Assert.Equal("key%28%293", escapedKey); Assert.Equal("value%28%29%21%26%3B%3A", escapedValue); - var initialBaggage = - $"key%201=value%201,{encodedKey}={encodedValue},{escapedKey}={escapedValue},key4=%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~"; - + var initialBaggage = $"key+1=value+1,{encodedKey}={encodedValue},{escapedKey}={escapedValue}"; var carrier = new List> { new KeyValuePair(BaggagePropagator.BaggageHeaderName, initialBaggage), @@ -144,11 +146,11 @@ public void ValidateSpecialCharsBaggageExtraction() Assert.False(propagationContext == default); Assert.True(propagationContext.ActivityContext == default); - Assert.Equal(4, propagationContext.Baggage.Count); + Assert.Equal(3, propagationContext.Baggage.Count); var actualBaggage = propagationContext.Baggage.GetBaggage(); - Assert.Equal(4, actualBaggage.Count); + Assert.Equal(3, actualBaggage.Count); Assert.True(actualBaggage.ContainsKey("key 1")); Assert.Equal("value 1", actualBaggage["key 1"]); @@ -158,10 +160,6 @@ public void ValidateSpecialCharsBaggageExtraction() Assert.True(actualBaggage.ContainsKey("key()3")); Assert.Equal("value()!&;:", actualBaggage["key()3"]); - - // x20-x7E range - Assert.True(actualBaggage.ContainsKey("key4")); - Assert.Equal(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", actualBaggage["key4"]); } [Fact] @@ -201,14 +199,11 @@ public void ValidateSpecialCharsBaggageInjection() { { "key 1", "value 1" }, { "key2", "!x_x,x-x&x(x\");:" }, - - // x20-x7E range - { "key3", " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" }, })); this.baggage.Inject(propagationContext, carrier, Setter); Assert.Single(carrier); - Assert.Equal("key%201=value%201,key2=%21x_x%2Cx-x%26x%28x%22%29%3B%3A,key3=%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~", carrier[BaggagePropagator.BaggageHeaderName]); + Assert.Equal("key+1=value+1,key2=!x_x%2Cx-x%26x(x%22)%3B%3A", carrier[BaggagePropagator.BaggageHeaderName]); } } diff --git a/test/OpenTelemetry.Api.Tests/Trace/Propagation/CompositePropagatorTest.cs b/test/OpenTelemetry.Api.Tests/Context/Propagation/CompositePropagatorTest.cs similarity index 56% rename from test/OpenTelemetry.Api.Tests/Trace/Propagation/CompositePropagatorTest.cs rename to test/OpenTelemetry.Api.Tests/Context/Propagation/CompositePropagatorTest.cs index f4066edcc1..b70565affa 100644 --- a/test/OpenTelemetry.Api.Tests/Trace/Propagation/CompositePropagatorTest.cs +++ b/test/OpenTelemetry.Api.Tests/Context/Propagation/CompositePropagatorTest.cs @@ -31,28 +31,72 @@ public class CompositePropagatorTest private readonly ActivitySpanId spanId = ActivitySpanId.CreateRandom(); [Fact] - public void CompositePropagator_NullTextFormatList() + public void CompositePropagator_NullTextMapPropagators() { - Assert.Throws(() => new CompositeTextMapPropagator(null)); + Assert.Throws(() => new CompositeTextMapPropagator(null!)); + } + + [Fact] + public void CompositePropagator_EmptyTextMapPropagators() + { + var compositePropagator = new CompositeTextMapPropagator([]); + Assert.Empty(compositePropagator.Fields); + } + + [Fact] + public void CompositePropagator_NullTextMapPropagator() + { + var compositePropagator = new CompositeTextMapPropagator([null!]); + Assert.Empty(compositePropagator.Fields); + } + + [Fact] + public void CompositePropagator_NoOpTextMapPropagators() + { + var compositePropagator = new CompositeTextMapPropagator([new NoopTextMapPropagator()]); + Assert.Empty(compositePropagator.Fields); + } + + [Fact] + public void CompositePropagator_SingleTextMapPropagator() + { + var testPropagator = new TestPropagator("custom-traceparent-1", "custom-tracestate-1"); + + var compositePropagator = new CompositeTextMapPropagator([testPropagator]); + + // We expect a new HashSet, with a copy of the values from the propagator. + Assert.Equal(testPropagator.Fields, compositePropagator.Fields); + Assert.NotSame(testPropagator.Fields, compositePropagator.Fields); } [Fact] public void CompositePropagator_TestPropagator() { - var compositePropagator = new CompositeTextMapPropagator(new List - { - new TestPropagator("custom-traceparent-1", "custom-tracestate-1"), - new TestPropagator("custom-traceparent-2", "custom-tracestate-2"), - }); + var testPropagatorA = new TestPropagator("custom-traceparent-1", "custom-tracestate-1"); + var testPropagatorB = new TestPropagator("custom-traceparent-2", "custom-tracestate-2"); + + var compositePropagator = new CompositeTextMapPropagator([testPropagatorA, testPropagatorB,]); var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); - PropagationContext propagationContext = new PropagationContext(activityContext, default); + var propagationContext = new PropagationContext(activityContext, default); var carrier = new Dictionary(); using var activity = new Activity("test"); compositePropagator.Inject(propagationContext, carrier, Setter); Assert.Contains(carrier, kv => kv.Key == "custom-traceparent-1"); Assert.Contains(carrier, kv => kv.Key == "custom-traceparent-2"); + + Assert.Equal(testPropagatorA.Fields.Count + testPropagatorB.Fields.Count, compositePropagator.Fields.Count); + Assert.Subset(compositePropagator.Fields, testPropagatorA.Fields); + Assert.Subset(compositePropagator.Fields, testPropagatorB.Fields); + + Assert.Equal(1, testPropagatorA.InjectCount); + Assert.Equal(1, testPropagatorB.InjectCount); + + compositePropagator.Extract(default, new Dictionary(), Getter); + + Assert.Equal(1, testPropagatorA.ExtractCount); + Assert.Equal(1, testPropagatorB.ExtractCount); } [Fact] @@ -61,20 +105,24 @@ public void CompositePropagator_UsingSameTag() const string header01 = "custom-tracestate-01"; const string header02 = "custom-tracestate-02"; - var compositePropagator = new CompositeTextMapPropagator(new List - { - new TestPropagator("custom-traceparent", header01, true), - new TestPropagator("custom-traceparent", header02), - }); + var testPropagatorA = new TestPropagator("custom-traceparent", header01, true); + var testPropagatorB = new TestPropagator("custom-traceparent", header02); + + var compositePropagator = new CompositeTextMapPropagator([testPropagatorA, testPropagatorB,]); var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); - PropagationContext propagationContext = new PropagationContext(activityContext, default); + var propagationContext = new PropagationContext(activityContext, default); var carrier = new Dictionary(); compositePropagator.Inject(propagationContext, carrier, Setter); Assert.Contains(carrier, kv => kv.Key == "custom-traceparent"); + Assert.Equal(3, compositePropagator.Fields.Count); + + Assert.Equal(1, testPropagatorA.InjectCount); + Assert.Equal(1, testPropagatorB.InjectCount); + // checking if the latest propagator is the one with the data. So, it will replace the previous one. Assert.Equal($"00-{this.traceId}-{this.spanId}-{header02.Split('-').Last()}", carrier["custom-traceparent"]); @@ -85,6 +133,9 @@ public void CompositePropagator_UsingSameTag() // checking if we accessed only two times: header/headerstate options // if that's true, we skipped the first one since we have a logic to for the default result Assert.Equal(2, count); + + Assert.Equal(1, testPropagatorA.ExtractCount); + Assert.Equal(1, testPropagatorB.ExtractCount); } [Fact] @@ -99,13 +150,13 @@ public void CompositePropagator_ActivityContext_Baggage() var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null, isRemote: true); var baggage = new Dictionary { ["key1"] = "value1" }; - PropagationContext propagationContextActivityOnly = new PropagationContext(activityContext, default); - PropagationContext propagationContextBaggageOnly = new PropagationContext(default, new Baggage(baggage)); - PropagationContext propagationContextBoth = new PropagationContext(activityContext, new Baggage(baggage)); + var propagationContextActivityOnly = new PropagationContext(activityContext, default); + var propagationContextBaggageOnly = new PropagationContext(default, new Baggage(baggage)); + var propagationContextBoth = new PropagationContext(activityContext, new Baggage(baggage)); var carrier = new Dictionary(); compositePropagator.Inject(propagationContextActivityOnly, carrier, Setter); - PropagationContext extractedContext = compositePropagator.Extract(default, carrier, Getter); + var extractedContext = compositePropagator.Extract(default, carrier, Getter); Assert.Equal(propagationContextActivityOnly, extractedContext); carrier = new Dictionary(); diff --git a/test/OpenTelemetry.Api.Tests/Trace/Propagation/TestPropagator.cs b/test/OpenTelemetry.Api.Tests/Context/Propagation/TestPropagator.cs similarity index 74% rename from test/OpenTelemetry.Api.Tests/Trace/Propagation/TestPropagator.cs rename to test/OpenTelemetry.Api.Tests/Context/Propagation/TestPropagator.cs index abd4a17b1e..ec791743f5 100644 --- a/test/OpenTelemetry.Api.Tests/Trace/Propagation/TestPropagator.cs +++ b/test/OpenTelemetry.Api.Tests/Context/Propagation/TestPropagator.cs @@ -11,6 +11,9 @@ public class TestPropagator : TextMapPropagator private readonly string stateHeaderName; private readonly bool defaultContext; + private int extractCount = 0; + private int injectCount = 0; + public TestPropagator(string idHeaderName, string stateHeaderName, bool defaultContext = false) { this.idHeaderName = idHeaderName; @@ -18,17 +21,23 @@ public TestPropagator(string idHeaderName, string stateHeaderName, bool defaultC this.defaultContext = defaultContext; } + public int ExtractCount => this.extractCount; + + public int InjectCount => this.injectCount; + public override ISet Fields => new HashSet() { this.idHeaderName, this.stateHeaderName }; - public override PropagationContext Extract(PropagationContext context, T carrier, Func> getter) + public override PropagationContext Extract(PropagationContext context, T carrier, Func?> getter) { + Interlocked.Increment(ref this.extractCount); + if (this.defaultContext) { return context; } - IEnumerable id = getter(carrier, this.idHeaderName); - if (!id.Any()) + var id = getter(carrier, this.idHeaderName); + if (id == null || !id.Any()) { return context; } @@ -39,8 +48,8 @@ public override PropagationContext Extract(PropagationContext context, T carr return context; } - string tracestate = string.Empty; - IEnumerable tracestateCollection = getter(carrier, this.stateHeaderName); + var tracestate = string.Empty; + var tracestateCollection = getter(carrier, this.stateHeaderName); if (tracestateCollection?.Any() ?? false) { TraceContextPropagator.TryExtractTracestate(tracestateCollection.ToArray(), out tracestate); @@ -53,14 +62,16 @@ public override PropagationContext Extract(PropagationContext context, T carr public override void Inject(PropagationContext context, T carrier, Action setter) { - string headerNumber = this.stateHeaderName.Split('-').Last(); + Interlocked.Increment(ref this.injectCount); + + var headerNumber = this.stateHeaderName.Split('-').Last(); var traceparent = string.Concat("00-", context.ActivityContext.TraceId.ToHexString(), "-", context.ActivityContext.SpanId.ToHexString()); traceparent = string.Concat(traceparent, "-", headerNumber); setter(carrier, this.idHeaderName, traceparent); - string tracestateStr = context.ActivityContext.TraceState; + var tracestateStr = context.ActivityContext.TraceState; if (tracestateStr?.Length > 0) { setter(carrier, this.stateHeaderName, tracestateStr); diff --git a/test/OpenTelemetry.Api.Tests/Trace/Propagation/TracestateUtilsTests.cs b/test/OpenTelemetry.Api.Tests/Context/Propagation/TracestateUtilsTests.cs similarity index 94% rename from test/OpenTelemetry.Api.Tests/Trace/Propagation/TracestateUtilsTests.cs rename to test/OpenTelemetry.Api.Tests/Context/Propagation/TracestateUtilsTests.cs index 499fe66cbf..deb9fc9e8f 100644 --- a/test/OpenTelemetry.Api.Tests/Trace/Propagation/TracestateUtilsTests.cs +++ b/test/OpenTelemetry.Api.Tests/Context/Propagation/TracestateUtilsTests.cs @@ -7,9 +7,16 @@ namespace OpenTelemetry.Context.Propagation.Tests; public class TracestateUtilsTests { + [Fact] + public void NullTracestate() + { + var tracestateEntries = new List>(); + Assert.False(TraceStateUtilsNew.AppendTraceState(null!, tracestateEntries)); + Assert.Empty(tracestateEntries); + } + [Theory] [InlineData("")] - [InlineData(null)] [InlineData(" ")] [InlineData("\t")] public void EmptyTracestate(string tracestate) diff --git a/test/OpenTelemetry.Api.Tests/Context/RuntimeContextTest.cs b/test/OpenTelemetry.Api.Tests/Context/RuntimeContextTest.cs index 1d25b4bf54..ff80bdd76d 100644 --- a/test/OpenTelemetry.Api.Tests/Context/RuntimeContextTest.cs +++ b/test/OpenTelemetry.Api.Tests/Context/RuntimeContextTest.cs @@ -16,7 +16,7 @@ public RuntimeContextTest() public static void RegisterSlotWithInvalidNameThrows() { Assert.Throws(() => RuntimeContext.RegisterSlot(string.Empty)); - Assert.Throws(() => RuntimeContext.RegisterSlot(null)); + Assert.Throws(() => RuntimeContext.RegisterSlot(null!)); } [Fact] @@ -31,7 +31,7 @@ public static void RegisterSlotWithSameName() public static void GetSlotWithInvalidNameThrows() { Assert.Throws(() => RuntimeContext.GetSlot(string.Empty)); - Assert.Throws(() => RuntimeContext.GetSlot(null)); + Assert.Throws(() => RuntimeContext.GetSlot(null!)); } [Fact] @@ -58,12 +58,88 @@ public void RegisterAndGetSlot() Assert.Equal(100, expectedSlot.Get()); } + [Fact] + public void ValueTypeSlotNullableTests() + { + var expectedSlot = RuntimeContext.RegisterSlot("testslot_valuetype"); + Assert.NotNull(expectedSlot); + + var slotValueAccessor = expectedSlot as IRuntimeContextSlotValueAccessor; + Assert.NotNull(slotValueAccessor); + + Assert.Equal(0, expectedSlot.Get()); + Assert.Equal(0, slotValueAccessor.Value); + + slotValueAccessor.Value = 100; + + Assert.Equal(100, expectedSlot.Get()); + Assert.Equal(100, slotValueAccessor.Value); + + slotValueAccessor.Value = null; + + Assert.Equal(0, expectedSlot.Get()); + Assert.Equal(0, slotValueAccessor.Value); + + Assert.Throws(() => slotValueAccessor.Value = false); + } + + [Fact] + public void NullableValueTypeSlotNullableTests() + { + var expectedSlot = RuntimeContext.RegisterSlot("testslot_nullablevaluetype"); + Assert.NotNull(expectedSlot); + + var slotValueAccessor = expectedSlot as IRuntimeContextSlotValueAccessor; + Assert.NotNull(slotValueAccessor); + + Assert.Null(expectedSlot.Get()); + Assert.Null(slotValueAccessor.Value); + + slotValueAccessor.Value = 100; + + Assert.Equal(100, expectedSlot.Get()); + Assert.Equal(100, slotValueAccessor.Value); + + slotValueAccessor.Value = null; + + Assert.Null(expectedSlot.Get()); + Assert.Null(slotValueAccessor.Value); + + Assert.Throws(() => slotValueAccessor.Value = false); + } + + [Fact] + public void ReferenceTypeSlotNullableTests() + { + var expectedSlot = RuntimeContext.RegisterSlot("testslot_referencetype"); + Assert.NotNull(expectedSlot); + + var slotValueAccessor = expectedSlot as IRuntimeContextSlotValueAccessor; + Assert.NotNull(slotValueAccessor); + + Assert.Null(expectedSlot.Get()); + Assert.Null(slotValueAccessor.Value); + + slotValueAccessor.Value = this; + + Assert.Equal(this, expectedSlot.Get()); + Assert.Equal(this, slotValueAccessor.Value); + + slotValueAccessor.Value = null; + + Assert.Null(expectedSlot.Get()); + Assert.Null(slotValueAccessor.Value); + + Assert.Throws(() => slotValueAccessor.Value = new object()); + } + #if NETFRAMEWORK [Fact] public void NetFrameworkGetSlotInAnotherAppDomain() { const string slotName = "testSlot"; var slot = RuntimeContext.RegisterSlot(slotName); + Assert.NotNull(slot); slot.Set(100); // Create an object in another AppDomain and try to access the slot diff --git a/test/OpenTelemetry.Api.Tests/Internal/GuardTest.cs b/test/OpenTelemetry.Api.Tests/Internal/GuardTest.cs index 455ba44d4b..a2af242b97 100644 --- a/test/OpenTelemetry.Api.Tests/Internal/GuardTest.cs +++ b/test/OpenTelemetry.Api.Tests/Internal/GuardTest.cs @@ -20,17 +20,17 @@ public void NullTest() Guard.ThrowIfNull("hello"); // Invalid - object potato = null; + object? potato = null; var ex1 = Assert.Throws(() => Guard.ThrowIfNull(potato)); Assert.Contains("Must not be null", ex1.Message); Assert.Equal("potato", ex1.ParamName); - object @event = null; + object? @event = null; var ex2 = Assert.Throws(() => Guard.ThrowIfNull(@event)); Assert.Contains("Must not be null", ex2.Message); Assert.Equal("@event", ex2.ParamName); - Thing thing = null; + Thing? thing = null; var ex3 = Assert.Throws(() => Guard.ThrowIfNull(thing?.Bar)); Assert.Contains("Must not be null", ex3.Message); Assert.Equal("thing?.Bar", ex3.ParamName); @@ -153,12 +153,12 @@ public void ZeroTest() public class Thing { - public string Bar { get; set; } + public string? Bar { get; set; } } -#if !NET6_0_OR_GREATER +#if !NET /// - /// Borrowed from: . + /// Borrowed from: . /// public class CallerArgumentExpressionAttributeTests { @@ -166,7 +166,7 @@ public class CallerArgumentExpressionAttributeTests [InlineData(null)] [InlineData("")] [InlineData("paramName")] - public static void Ctor_ParameterName_Roundtrip(string value) + public static void Ctor_ParameterName_Roundtrip(string? value) { var caea = new CallerArgumentExpressionAttribute(value); Assert.Equal(value, caea.ParameterName); @@ -175,12 +175,13 @@ public static void Ctor_ParameterName_Roundtrip(string value) [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("argument")] string expr = null) => expr; + 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 c09c728cd5..6cc8486a23 100644 --- a/test/OpenTelemetry.Api.Tests/Logs/LogRecordAttributeListTests.cs +++ b/test/OpenTelemetry.Api.Tests/Logs/LogRecordAttributeListTests.cs @@ -29,13 +29,15 @@ public void ReadWriteTest(int numberOfItems) var item = attributes[i]; Assert.Equal($"key{i}", item.Key); + Assert.NotNull(item.Value); Assert.Equal(i, (int)item.Value); } int index = 0; - foreach (KeyValuePair item in attributes) + foreach (KeyValuePair item in attributes) { Assert.Equal($"key{index}", item.Key); + Assert.NotNull(item.Value); Assert.Equal(index, (int)item.Value); index++; } @@ -74,6 +76,7 @@ public void ClearTest(int numberOfItems) var item = attributes[i]; Assert.Equal($"key{i}", item.Key); + Assert.NotNull(item.Value); Assert.Equal(i, (int)item.Value); } @@ -98,7 +101,7 @@ public void ExportTest(int numberOfItems) attributes.Add($"key{i}", i); } - List> storage = null; + List>? storage = null; var exportedAttributes = attributes.Export(ref storage); @@ -122,9 +125,10 @@ public void ExportTest(int numberOfItems) } int index = 0; - foreach (KeyValuePair item in exportedAttributes) + foreach (KeyValuePair item in exportedAttributes) { Assert.Equal($"key{index}", item.Key); + Assert.NotNull(item.Value); Assert.Equal(index, (int)item.Value); index++; } diff --git a/test/OpenTelemetry.Api.Tests/Logs/LogRecordDataTests.cs b/test/OpenTelemetry.Api.Tests/Logs/LogRecordDataTests.cs index 89d32795d6..03d65ab3dd 100644 --- a/test/OpenTelemetry.Api.Tests/Logs/LogRecordDataTests.cs +++ b/test/OpenTelemetry.Api.Tests/Logs/LogRecordDataTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using Xunit; diff --git a/test/OpenTelemetry.Api.Tests/Logs/LogRecordSeverityExtensionsTests.cs b/test/OpenTelemetry.Api.Tests/Logs/LogRecordSeverityExtensionsTests.cs index 9035898cc6..10d2f90896 100644 --- a/test/OpenTelemetry.Api.Tests/Logs/LogRecordSeverityExtensionsTests.cs +++ b/test/OpenTelemetry.Api.Tests/Logs/LogRecordSeverityExtensionsTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using Xunit; namespace OpenTelemetry.Logs.Tests; diff --git a/test/OpenTelemetry.Api.Tests/Logs/LoggerProviderTests.cs b/test/OpenTelemetry.Api.Tests/Logs/LoggerProviderTests.cs index e26443707d..27078c6307 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 -#nullable enable - -#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET using System.Diagnostics.CodeAnalysis; #endif using Xunit; @@ -62,9 +60,13 @@ private sealed class NoopLoggerProvider : LoggerProvider private sealed class TestLoggerProvider : LoggerProvider { +#if OPENTELEMETRY_API_EXPERIMENTAL_FEATURES_EXPOSED protected override bool TryCreateLogger( +#else + internal override bool TryCreateLogger( +#endif string? name, -#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET [NotNullWhen(true)] #endif out Logger? logger) diff --git a/test/OpenTelemetry.Api.Tests/OpenTelemetry.Api.Tests.csproj b/test/OpenTelemetry.Api.Tests/OpenTelemetry.Api.Tests.csproj index b2e0fa8b6d..2e88e5a958 100644 --- a/test/OpenTelemetry.Api.Tests/OpenTelemetry.Api.Tests.csproj +++ b/test/OpenTelemetry.Api.Tests/OpenTelemetry.Api.Tests.csproj @@ -1,14 +1,14 @@ + Unit test project for OpenTelemetry.Api $(TargetFrameworksForTests) $(NoWarn),CS0618 - - - disable + true + @@ -22,9 +22,7 @@ - - runtime; build; native; contentfiles; analyzers - - + + diff --git a/test/OpenTelemetry.Api.Tests/Trace/ActivityExtensionsTest.cs b/test/OpenTelemetry.Api.Tests/Trace/ActivityExtensionsTest.cs index 31fbf80672..5f7a6ff549 100644 --- a/test/OpenTelemetry.Api.Tests/Trace/ActivityExtensionsTest.cs +++ b/test/OpenTelemetry.Api.Tests/Trace/ActivityExtensionsTest.cs @@ -202,7 +202,7 @@ public void GetTagValue() [Theory] [InlineData("Key", "Value", true)] [InlineData("CustomTag", null, false)] - public void TryCheckFirstTag(string tagName, object expectedTagValue, bool expectedResult) + public void TryCheckFirstTag(string tagName, object? expectedTagValue, bool expectedResult) { using var activity = new Activity("Test"); activity.SetTag("Key", "Value"); diff --git a/test/OpenTelemetry.Api.Tests/Trace/SpanAttributesTest.cs b/test/OpenTelemetry.Api.Tests/Trace/SpanAttributesTest.cs index f0c5c7c110..abf35e3935 100644 --- a/test/OpenTelemetry.Api.Tests/Trace/SpanAttributesTest.cs +++ b/test/OpenTelemetry.Api.Tests/Trace/SpanAttributesTest.cs @@ -37,7 +37,7 @@ public void ValidateAddMethods() public void ValidateNullKey() { var spanAttribute = new SpanAttributes(); - Assert.Throws(() => spanAttribute.Add(null, "null key")); + Assert.Throws(() => spanAttribute.Add(null!, "null key")); } [Fact] @@ -53,17 +53,17 @@ public void ValidateSameKey() public void ValidateConstructorWithList() { var spanAttributes = new SpanAttributes( - new List>() - { - new KeyValuePair("Span attribute int", 1), - new KeyValuePair("Span attribute string", "str"), - }); + new List> + { + new("Span attribute int", 1), + new("Span attribute string", "str"), + }); Assert.Equal(2, spanAttributes.Attributes.Count); } [Fact] public void ValidateConstructorWithNullList() { - Assert.Throws(() => new SpanAttributes(null)); + Assert.Throws(() => new SpanAttributes(null!)); } } diff --git a/test/OpenTelemetry.Api.Tests/Trace/TelemetrySpanTest.cs b/test/OpenTelemetry.Api.Tests/Trace/TelemetrySpanTest.cs index 6434569e1d..a8c281deae 100644 --- a/test/OpenTelemetry.Api.Tests/Trace/TelemetrySpanTest.cs +++ b/test/OpenTelemetry.Api.Tests/Trace/TelemetrySpanTest.cs @@ -18,6 +18,7 @@ public void CheckRecordExceptionData() telemetrySpan.RecordException(new ArgumentNullException(message, new Exception("new-exception"))); Assert.Single(activity.Events); + Assert.NotNull(telemetrySpan.Activity); var @event = telemetrySpan.Activity.Events.FirstOrDefault(q => q.Name == SemanticConventions.AttributeExceptionEventName); Assert.Equal(message, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionMessage).Value); Assert.Equal(typeof(ArgumentNullException).Name, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionType).Value); @@ -35,6 +36,7 @@ public void CheckRecordExceptionData2() telemetrySpan.RecordException(type, message, stack); Assert.Single(activity.Events); + Assert.NotNull(telemetrySpan.Activity); var @event = telemetrySpan.Activity.Events.FirstOrDefault(q => q.Name == SemanticConventions.AttributeExceptionEventName); Assert.Equal(message, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionMessage).Value); Assert.Equal(type, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionType).Value); @@ -62,6 +64,7 @@ public void ParentIds() // ParentId should be unset Assert.Equal(default, parentSpan.ParentSpanId); + Assert.NotNull(parentActivity.Id); using var childActivity = new Activity("childOperation").SetParentId(parentActivity.Id); using var childSpan = new TelemetrySpan(childActivity); diff --git a/test/OpenTelemetry.Api.Tests/Trace/TracerTest.cs b/test/OpenTelemetry.Api.Tests/Trace/TracerTest.cs index 7c71f41cda..642795202d 100644 --- a/test/OpenTelemetry.Api.Tests/Trace/TracerTest.cs +++ b/test/OpenTelemetry.Api.Tests/Trace/TracerTest.cs @@ -53,13 +53,16 @@ public void Tracer_StartRootSpan_BadArgs_NullSpanName() .AddSource("tracername") .Build(); - var span1 = this.tracer.StartRootSpan(null); + var span1 = this.tracer.StartRootSpan(null!); + Assert.NotNull(span1.Activity); Assert.True(string.IsNullOrEmpty(span1.Activity.DisplayName)); - var span2 = this.tracer.StartRootSpan(null, SpanKind.Client); + var span2 = this.tracer.StartRootSpan(null!, SpanKind.Client); + Assert.NotNull(span2.Activity); Assert.True(string.IsNullOrEmpty(span2.Activity.DisplayName)); - var span3 = this.tracer.StartRootSpan(null, SpanKind.Client, default); + var span3 = this.tracer.StartRootSpan(null!, SpanKind.Client, default); + Assert.NotNull(span3.Activity); Assert.True(string.IsNullOrEmpty(span3.Activity.DisplayName)); } @@ -109,13 +112,16 @@ public void Tracer_StartSpan_BadArgs_NullSpanName() .AddSource("tracername") .Build(); - var span1 = this.tracer.StartSpan(null); + var span1 = this.tracer.StartSpan(null!); + Assert.NotNull(span1.Activity); Assert.True(string.IsNullOrEmpty(span1.Activity.DisplayName)); - var span2 = this.tracer.StartSpan(null, SpanKind.Client); + var span2 = this.tracer.StartSpan(null!, SpanKind.Client); + Assert.NotNull(span2.Activity); Assert.True(string.IsNullOrEmpty(span2.Activity.DisplayName)); - var span3 = this.tracer.StartSpan(null, SpanKind.Client, null); + var span3 = this.tracer.StartSpan(null!, SpanKind.Client, null); + Assert.NotNull(span3.Activity); Assert.True(string.IsNullOrEmpty(span3.Activity.DisplayName)); } @@ -126,13 +132,16 @@ public void Tracer_StartActiveSpan_BadArgs_NullSpanName() .AddSource("tracername") .Build(); - var span1 = this.tracer.StartActiveSpan(null); + var span1 = this.tracer.StartActiveSpan(null!); + Assert.NotNull(span1.Activity); Assert.True(string.IsNullOrEmpty(span1.Activity.DisplayName)); - var span2 = this.tracer.StartActiveSpan(null, SpanKind.Client); + var span2 = this.tracer.StartActiveSpan(null!, SpanKind.Client); + Assert.NotNull(span2.Activity); Assert.True(string.IsNullOrEmpty(span2.Activity.DisplayName)); - var span3 = this.tracer.StartActiveSpan(null, SpanKind.Client, null); + var span3 = this.tracer.StartActiveSpan(null!, SpanKind.Client, null); + Assert.NotNull(span3.Activity); Assert.True(string.IsNullOrEmpty(span3.Activity.DisplayName)); } @@ -143,10 +152,12 @@ public void Tracer_StartSpan_FromParent_BadArgs_NullSpanName() .AddSource("tracername") .Build(); - var span1 = this.tracer.StartSpan(null, SpanKind.Client, TelemetrySpan.NoopInstance); + var span1 = this.tracer.StartSpan(null!, SpanKind.Client, TelemetrySpan.NoopInstance); + Assert.NotNull(span1.Activity); Assert.True(string.IsNullOrEmpty(span1.Activity.DisplayName)); - var span2 = this.tracer.StartSpan(null, SpanKind.Client, TelemetrySpan.NoopInstance, default); + var span2 = this.tracer.StartSpan(null!, SpanKind.Client, TelemetrySpan.NoopInstance, default); + Assert.NotNull(span2.Activity); Assert.True(string.IsNullOrEmpty(span2.Activity.DisplayName)); } @@ -159,10 +170,12 @@ public void Tracer_StartSpan_FromParentContext_BadArgs_NullSpanName() var blankContext = default(SpanContext); - var span1 = this.tracer.StartSpan(null, SpanKind.Client, blankContext); + var span1 = this.tracer.StartSpan(null!, SpanKind.Client, blankContext); + Assert.NotNull(span1.Activity); Assert.True(string.IsNullOrEmpty(span1.Activity.DisplayName)); - var span2 = this.tracer.StartSpan(null, SpanKind.Client, blankContext, default); + var span2 = this.tracer.StartSpan(null!, SpanKind.Client, blankContext, default); + Assert.NotNull(span2.Activity); Assert.True(string.IsNullOrEmpty(span2.Activity.DisplayName)); } @@ -173,10 +186,12 @@ public void Tracer_StartActiveSpan_FromParent_BadArgs_NullSpanName() .AddSource("tracername") .Build(); - var span1 = this.tracer.StartActiveSpan(null, SpanKind.Client, TelemetrySpan.NoopInstance); + var span1 = this.tracer.StartActiveSpan(null!, SpanKind.Client, TelemetrySpan.NoopInstance); + Assert.NotNull(span1.Activity); Assert.True(string.IsNullOrEmpty(span1.Activity.DisplayName)); - var span2 = this.tracer.StartActiveSpan(null, SpanKind.Client, TelemetrySpan.NoopInstance, default); + var span2 = this.tracer.StartActiveSpan(null!, SpanKind.Client, TelemetrySpan.NoopInstance, default); + Assert.NotNull(span2.Activity); Assert.True(string.IsNullOrEmpty(span2.Activity.DisplayName)); } @@ -189,10 +204,12 @@ public void Tracer_StartActiveSpan_FromParentContext_BadArgs_NullSpanName() var blankContext = default(SpanContext); - var span1 = this.tracer.StartActiveSpan(null, SpanKind.Client, blankContext); + var span1 = this.tracer.StartActiveSpan(null!, SpanKind.Client, blankContext); + Assert.NotNull(span1.Activity); Assert.True(string.IsNullOrEmpty(span1.Activity.DisplayName)); - var span2 = this.tracer.StartActiveSpan(null, SpanKind.Client, blankContext, default); + var span2 = this.tracer.StartActiveSpan(null!, SpanKind.Client, blankContext, default); + Assert.NotNull(span2.Activity); Assert.True(string.IsNullOrEmpty(span2.Activity.DisplayName)); } @@ -204,19 +221,23 @@ public void Tracer_StartActiveSpan_CreatesActiveSpan() .Build(); var span1 = this.tracer.StartActiveSpan("Test"); + Assert.NotNull(span1.Activity); Assert.Equal(span1.Activity.SpanId, Tracer.CurrentSpan.Context.SpanId); var span2 = this.tracer.StartActiveSpan("Test", SpanKind.Client); + Assert.NotNull(span2.Activity); Assert.Equal(span2.Activity.SpanId, Tracer.CurrentSpan.Context.SpanId); var span = this.tracer.StartSpan("foo"); Tracer.WithSpan(span); var span3 = this.tracer.StartActiveSpan("Test", SpanKind.Client, span); + Assert.NotNull(span3.Activity); Assert.Equal(span3.Activity.SpanId, Tracer.CurrentSpan.Context.SpanId); var spanContext = new SpanContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded); var span4 = this.tracer.StartActiveSpan("Test", SpanKind.Client, spanContext); + Assert.NotNull(span4.Activity); Assert.Equal(span4.Activity.SpanId, Tracer.CurrentSpan.Context.SpanId); } @@ -278,21 +299,21 @@ public void CreateSpan_NotSampled() [Fact] public void TracerBecomesNoopWhenParentProviderIsDisposedTest() { - TracerProvider provider = null; - Tracer tracer = null; + TracerProvider? provider; + Tracer? tracer1; using (var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource("mytracer") .Build()) { provider = tracerProvider; - tracer = tracerProvider.GetTracer("mytracer"); + tracer1 = tracerProvider.GetTracer("mytracer"); - var span1 = tracer.StartSpan("foo"); + var span1 = tracer1.StartSpan("foo"); Assert.True(span1.IsRecording); } - var span2 = tracer.StartSpan("foo"); + var span2 = tracer1.StartSpan("foo"); Assert.False(span2.IsRecording); var tracer2 = provider.GetTracer("mytracer"); @@ -349,9 +370,10 @@ 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((object? state) => { var testTracerProvider = state as TestTracerProvider; + Assert.NotNull(testTracerProvider); var id = Interlocked.Increment(ref testTracerProvider.NumberOfThreads); var name = $"Tracer{id}"; diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/BaseOtlpHttpExportClientTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/BaseOtlpHttpExportClientTests.cs index a846af33a4..36d534df9a 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/BaseOtlpHttpExportClientTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/BaseOtlpHttpExportClientTests.cs @@ -17,7 +17,7 @@ public class BaseOtlpHttpExportClientTests [InlineData("https://custom.host", null, "https://custom.host")] [InlineData("http://custom.host:44318/custom/path", null, "http://custom.host:44318/custom/path")] [InlineData("https://custom.host", "http://from.otel.exporter.env.var", "https://custom.host")] - public void ValidateOtlpHttpExportClientEndpoint(string optionEndpoint, string endpointEnvVar, string expectedExporterEndpoint) + public void ValidateOtlpHttpExportClientEndpoint(string? optionEndpoint, string? endpointEnvVar, string expectedExporterEndpoint) { try { diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/ExportClient/OtlpHttpTraceExportClientTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/ExportClient/OtlpHttpTraceExportClientTests.cs index 7593a67287..0b932f87d3 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/ExportClient/OtlpHttpTraceExportClientTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/ExportClient/OtlpHttpTraceExportClientTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; -#if !NET6_0_OR_GREATER +#if !NET using System.Net.Http; #endif using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; @@ -63,8 +63,8 @@ public void NewOtlpHttpTraceExportClient_OtlpExporterOptions_ExporterHasCorrectP public void SendExportRequest_ExportTraceServiceRequest_SendsCorrectHttpRequest(bool includeServiceNameInResource) { // Arrange - var evenTags = new[] { new KeyValuePair("k0", "v0") }; - var oddTags = new[] { new KeyValuePair("k1", "v1") }; + var evenTags = new[] { new KeyValuePair("k0", "v0") }; + var oddTags = new[] { new KeyValuePair("k1", "v1") }; var sources = new[] { new ActivitySource("even", "2.4.6"), @@ -116,7 +116,8 @@ public void SendExportRequest_ExportTraceServiceRequest_SendsCorrectHttpRequest( var activityKind = isEven ? ActivityKind.Client : ActivityKind.Server; var activityTags = isEven ? evenTags : oddTags; - using Activity activity = source.StartActivity($"span-{i}", activityKind, parentContext: default, activityTags); + using Activity? activity = source.StartActivity($"span-{i}", activityKind, parentContext: default, activityTags); + Assert.NotNull(activity); processor.OnEnd(activity); } @@ -141,6 +142,7 @@ void RunTest(Batch batch) Assert.True(result.Success); Assert.NotNull(httpRequest); Assert.Equal(HttpMethod.Post, httpRequest.Method); + Assert.NotNull(httpRequest.RequestUri); Assert.Equal("http://localhost:4317/", httpRequest.RequestUri.AbsoluteUri); Assert.Equal(OtlpExporterOptions.StandardHeaders.Length + 2, httpRequest.Headers.Count()); Assert.Contains(httpRequest.Headers, h => h.Key == header1.Name && h.Value.First() == header1.Value); diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/Serializer/ProtobufSerializerTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/Serializer/ProtobufSerializerTests.cs new file mode 100644 index 0000000000..0ee13a5b83 --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/Serializer/ProtobufSerializerTests.cs @@ -0,0 +1,333 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Text; +using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; +using Xunit; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.Implementation.Serializer; + +public class ProtobufSerializerTests +{ + [Fact] + public void GetTagValue_ReturnsCorrectValue() + { + Assert.Equal(8u, ProtobufSerializer.GetTagValue(1, ProtobufWireType.VARINT)); + Assert.Equal(17u, ProtobufSerializer.GetTagValue(2, ProtobufWireType.I64)); + Assert.Equal(26u, ProtobufSerializer.GetTagValue(3, ProtobufWireType.LEN)); + } + + [Fact] + public void WriteTag_WritesCorrectly() + { + byte[] buffer = new byte[10]; + int position = ProtobufSerializer.WriteTag(buffer, 0, 1, ProtobufWireType.VARINT); + Assert.Equal(1, position); + Assert.Equal(8, buffer[0]); + } + + [Fact] + public void WriteLength_WritesCorrectly() + { + byte[] buffer = new byte[10]; + int position = ProtobufSerializer.WriteLength(buffer, 0, 300); + Assert.Equal(2, position); + Assert.Equal(0xAC, buffer[0]); + Assert.Equal(0x02, buffer[1]); + } + + [Fact] + public void WriteBoolWithTag_WritesCorrectly() + { + byte[] buffer = new byte[10]; + int position = ProtobufSerializer.WriteBoolWithTag(buffer, 0, 1, true); + Assert.Equal(2, position); + Assert.Equal(8, buffer[0]); + Assert.Equal(1, buffer[1]); + } + + [Fact] + public void WriteFixed32WithTag_WritesCorrectly() + { + byte[] buffer = new byte[10]; + int position = ProtobufSerializer.WriteFixed32WithTag(buffer, 0, 1, 0x12345678); + Assert.Equal(5, position); + Assert.Equal(13, buffer[0]); + Assert.Equal(0x78, buffer[1]); + Assert.Equal(0x56, buffer[2]); + Assert.Equal(0x34, buffer[3]); + Assert.Equal(0x12, buffer[4]); + } + + [Fact] + public void WriteFixed64WithTag_WritesCorrectly() + { + byte[] buffer = new byte[10]; + int position = ProtobufSerializer.WriteFixed64WithTag(buffer, 0, 1, 0x123456789ABCDEF0); + Assert.Equal(9, position); + Assert.Equal(9, buffer[0]); // Tag + Assert.Equal(0xF0, buffer[1]); + Assert.Equal(0xDE, buffer[2]); + Assert.Equal(0xBC, buffer[3]); + Assert.Equal(0x9A, buffer[4]); + Assert.Equal(0x78, buffer[5]); + Assert.Equal(0x56, buffer[6]); + Assert.Equal(0x34, buffer[7]); + Assert.Equal(0x12, buffer[8]); + } + + [Fact] + public void WriteStringWithTag_WritesCorrectly() + { + byte[] buffer = new byte[20]; + int position = ProtobufSerializer.WriteStringWithTag(buffer, 0, 1, "Hello"); + Assert.Equal(7, position); + Assert.Equal(10, buffer[0]); + Assert.Equal(5, buffer[1]); + Assert.Equal((byte)'H', buffer[2]); + Assert.Equal((byte)'e', buffer[3]); + Assert.Equal((byte)'l', buffer[4]); + Assert.Equal((byte)'l', buffer[5]); + Assert.Equal((byte)'o', buffer[6]); + } + + [Theory] + [InlineData(300, new byte[] { 0xAC, 0x82, 0x80, 0x00 })] // Normal case with 300 + [InlineData(127, new byte[] { 0xFF, 0x80, 0x80, 0x00 })] // Boundary case: max 1-byte value + [InlineData(128, new byte[] { 0x80, 0x81, 0x80, 0x00 })] // Boundary case: min 2-byte value + [InlineData(16383, new byte[] { 0xFF, 0xFF, 0x80, 0x00 })] // Max 2-byte value + [InlineData(16384, new byte[] { 0x80, 0x80, 0x81, 0x00 })] // Min 3-byte value + [InlineData(2097151, new byte[] { 0xFF, 0xFF, 0xFF, 0x00 })] // Max 3-byte value + [InlineData(2097152, new byte[] { 0x80, 0x80, 0x80, 0x01 })] // Min 4-byte value + [InlineData(268435455, new byte[] { 0xFF, 0xFF, 0xFF, 0x7F })] // Max 4-byte value + public void WriteReservedLength_WritesCorrectly(int length, byte[] expectedBytes) + { + byte[] buffer = new byte[10]; + ProtobufSerializer.WriteReservedLength(buffer, 0, length); + + for (int i = 0; i < expectedBytes.Length; i++) + { + Assert.Equal(expectedBytes[i], buffer[i]); + } + } + + [Fact] + public void WriteTagAndLength_WritesCorrectly() + { + byte[] buffer = new byte[10]; + int position = ProtobufSerializer.WriteTagAndLength(buffer, 0, 300, 1, ProtobufWireType.LEN); + Assert.Equal(3, position); + Assert.Equal(10, buffer[0]); // Tag + Assert.Equal(0xAC, buffer[1]); // Length (300 in varint encoding) + Assert.Equal(0x02, buffer[2]); + } + + [Fact] + public void WriteEnumWithTag_WritesCorrectly() + { + byte[] buffer = new byte[10]; + int position = ProtobufSerializer.WriteEnumWithTag(buffer, 0, 1, 5); + Assert.Equal(2, position); + Assert.Equal(8, buffer[0]); // Tag + Assert.Equal(5, buffer[1]); // Enum value + } + + [Fact] + public void WriteVarInt64_WritesCorrectly() + { + byte[] buffer = new byte[10]; + int position = ProtobufSerializer.WriteVarInt64(buffer, 0, 300); + Assert.Equal(2, position); + Assert.Equal(0xAC, buffer[0]); + Assert.Equal(0x02, buffer[1]); + } + + [Fact] + public void WriteInt64WithTag_WritesCorrectly() + { + byte[] buffer = new byte[10]; + int position = ProtobufSerializer.WriteInt64WithTag(buffer, 0, 1, 300); + Assert.Equal(3, position); + Assert.Equal(8, buffer[0]); // Tag + Assert.Equal(0xAC, buffer[1]); + Assert.Equal(0x02, buffer[2]); + } + + [Fact] + public void WriteDoubleWithTag_WritesCorrectly() + { + byte[] buffer = new byte[10]; + int position = ProtobufSerializer.WriteDoubleWithTag(buffer, 0, 1, 123.456); + Assert.Equal(9, position); + Assert.Equal(9, buffer[0]); // Tag + + // The next 8 bytes represent 123.456 in IEEE 754 double-precision format + Assert.Equal(0x77, buffer[1]); + Assert.Equal(0xBE, buffer[2]); + Assert.Equal(0x9F, buffer[3]); + Assert.Equal(0x1A, buffer[4]); + Assert.Equal(0x2F, buffer[5]); + Assert.Equal(0xDD, buffer[6]); + Assert.Equal(0x5E, buffer[7]); + Assert.Equal(0x40, buffer[8]); + } + + [Fact] + public void WriteVarInt32_WritesCorrectly() + { + byte[] buffer = new byte[10]; + int position = ProtobufSerializer.WriteVarInt32(buffer, 0, 300); + Assert.Equal(2, position); + Assert.Equal(0xAC, buffer[0]); + Assert.Equal(0x02, buffer[1]); + } + + [Fact] + public void WriteVarInt32_MaxValue_WritesCorrectly() + { + byte[] buffer = new byte[10]; + int position = ProtobufSerializer.WriteVarInt32(buffer, 0, uint.MaxValue); + Assert.Equal(5, position); + Assert.Equal(0xFF, buffer[0]); + Assert.Equal(0xFF, buffer[1]); + Assert.Equal(0xFF, buffer[2]); + Assert.Equal(0xFF, buffer[3]); + Assert.Equal(0x0F, buffer[4]); + } + + [Fact] + public void WriteVarInt64_MaxValue_WritesCorrectly() + { + byte[] buffer = new byte[10]; + int position = ProtobufSerializer.WriteVarInt64(buffer, 0, ulong.MaxValue); + Assert.Equal(10, position); + for (int i = 0; i < 9; i++) + { + Assert.Equal(0xFF, buffer[i]); + } + + Assert.Equal(0x01, buffer[9]); + } + + [Fact] + public void WriteStringWithTag_EmptyString_WritesCorrectly() + { + byte[] buffer = new byte[10]; + int position = ProtobufSerializer.WriteStringWithTag(buffer, 0, 1, string.Empty); + Assert.Equal(2, position); + Assert.Equal(10, buffer[0]); // Tag + Assert.Equal(0, buffer[1]); // Length + } + + [Fact] + public void WriteStringWithTag_ASCIIString_WritesCorrectly() + { + byte[] buffer = new byte[20]; + int position = ProtobufSerializer.WriteStringWithTag(buffer, 0, 1, "Hello"); + Assert.Equal(7, position); + Assert.Equal(10, buffer[0]); // Tag + Assert.Equal(5, buffer[1]); // Length + + byte[] expectedContent = Encoding.ASCII.GetBytes("Hello"); + byte[] actualContent = new byte[5]; + Array.Copy(buffer, 2, actualContent, 0, 5); + Assert.True(expectedContent.SequenceEqual(actualContent)); + } + + [Fact] + public void WriteStringWithTag_UnicodeString_WritesCorrectly() + { + byte[] buffer = new byte[20]; + string unicodeString = "\u3053\u3093\u306b\u3061\u306f"; // "Hello" in Japanese + int position = ProtobufSerializer.WriteStringWithTag(buffer, 0, 1, unicodeString); + Assert.Equal(17, position); + Assert.Equal(10, buffer[0]); // Tag + Assert.Equal(15, buffer[1]); // Length (3 bytes per character in UTF-8) + + byte[] expectedContent = Encoding.UTF8.GetBytes(unicodeString); + byte[] actualContent = new byte[15]; + Array.Copy(buffer, 2, actualContent, 0, 15); + Assert.True(expectedContent.SequenceEqual(actualContent)); + } + + [Fact] + public void WriteStringWithTag_LongString_WritesCorrectly() + { + string longString = new string('a', 1000); + byte[] buffer = new byte[1100]; + int position = ProtobufSerializer.WriteStringWithTag(buffer, 0, 1, longString); + Assert.Equal(1003, position); + Assert.Equal(10, buffer[0]); // Tag + Assert.Equal(0xE8, buffer[1]); // Length (1000 in varint encoding) + Assert.Equal(0x07, buffer[2]); + + byte[] expectedContent = Encoding.UTF8.GetBytes(longString); + byte[] actualContent = new byte[1000]; + Array.Copy(buffer, 3, actualContent, 0, 1000); + Assert.True(expectedContent.SequenceEqual(actualContent)); + } + + [Fact] + public void WriteStringWithTag_MixedEncodingString_WritesCorrectly() + { + byte[] buffer = new byte[30]; + string mixedString = "Hello\u4e16\u754c"; // "Hello World" with "World" in Chinese + int position = ProtobufSerializer.WriteStringWithTag(buffer, 0, 1, mixedString); + Assert.Equal(13, position); + Assert.Equal(10, buffer[0]); // Tag + Assert.Equal(11, buffer[1]); // Length (5 for "Hello" + 6 for Chinese "World" in UTF-8) + + byte[] expectedContent = Encoding.UTF8.GetBytes(mixedString); + byte[] actualContent = new byte[11]; + Array.Copy(buffer, 2, actualContent, 0, 11); + Assert.True(expectedContent.SequenceEqual(actualContent)); + } + + [Fact] + public void WriteStringWithTag_StringWithSpecialCharacters_WritesCorrectly() + { + byte[] buffer = new byte[30]; + string specialString = "Hello\n\t\"World\""; + int position = ProtobufSerializer.WriteStringWithTag(buffer, 0, 1, specialString); + Assert.Equal(16, position); + Assert.Equal(10, buffer[0]); // Tag + Assert.Equal(14, buffer[1]); // Length + + byte[] expectedContent = Encoding.UTF8.GetBytes(specialString); + byte[] actualContent = new byte[14]; + Array.Copy(buffer, 2, actualContent, 0, 14); + Assert.True(expectedContent.SequenceEqual(actualContent)); + } + + [Fact] + public void WriteStringWithTag_StringWithNullCharacters_WritesCorrectly() + { + byte[] buffer = new byte[20]; + string stringWithNull = "Hello\0World"; + int position = ProtobufSerializer.WriteStringWithTag(buffer, 0, 1, stringWithNull); + Assert.Equal(13, position); + Assert.Equal(10, buffer[0]); // Tag + Assert.Equal(11, buffer[1]); // Length + + byte[] expectedContent = Encoding.UTF8.GetBytes(stringWithNull); + byte[] actualContent = new byte[11]; + Array.Copy(buffer, 2, actualContent, 0, 11); + Assert.True(expectedContent.SequenceEqual(actualContent)); + } + + [Fact] + public void WriteStringWithTag_SurrogatePairs_WritesCorrectly() + { + byte[] buffer = new byte[20]; + string surrogatePairString = "\uD83D\uDCD6"; // Books emoji + int position = ProtobufSerializer.WriteStringWithTag(buffer, 0, 1, surrogatePairString); + Assert.Equal(6, position); + Assert.Equal(10, buffer[0]); // Tag + Assert.Equal(4, buffer[1]); // Length (4 bytes for the surrogate pair in UTF-8) + + byte[] expectedContent = Encoding.UTF8.GetBytes(surrogatePairString); + byte[] actualContent = new byte[4]; + Array.Copy(buffer, 2, actualContent, 0, 4); + Assert.True(expectedContent.SequenceEqual(actualContent)); + } +} diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/Serializer/ResourceProtoSerializerTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/Serializer/ResourceProtoSerializerTests.cs new file mode 100644 index 0000000000..0deb4c585a --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/Serializer/ResourceProtoSerializerTests.cs @@ -0,0 +1,66 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; +using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; +using OpenTelemetry.Proto.Trace.V1; +using OpenTelemetry.Resources; +using Xunit; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.Implementation.Serializer; + +public class ResourceProtoSerializerTests +{ + [Fact] + public void CreateResource_SupportedAttributeTypes() + { + // Arrange + byte[] buffer = new byte[1024]; + var attributes = new Dictionary + { + { "string", "stringValue" }, + { "bool", true }, + { "double", 0.1d }, + { "long", 1L }, + + // int and float supported by conversion to long and double + { "int", 1 }, + { "short", (short)1 }, + { "float", 0.1f }, + + // natively supported array types + { "string arr", new string[] { "stringValue1", "stringValue2" } }, + { "bool arr", new bool[] { true } }, + { "double arr", new double[] { 0.1d } }, + { "long arr", new long[] { 1L } }, + + // have to convert to other primitive array types + { "int arr", new int[] { 1, 2, 3 } }, + { "short arr", new short[] { (short)1 } }, + { "float arr", new float[] { 0.1f } }, + }; + + // Act + var resource = ResourceBuilder.CreateEmpty().AddAttributes(attributes).Build(); + var writePosition = ProtobufOtlpResourceSerializer.WriteResource(buffer, 0, resource); + var otlpResource = resource.ToOtlpResource(); + var expectedResourceSpans = new ResourceSpans + { + Resource = otlpResource, + }; + + // Deserialize the ResourceSpans and validate the attributes. + ResourceSpans actualResourceSpans; + using (var stream = new MemoryStream(buffer, 0, writePosition)) + { + actualResourceSpans = ResourceSpans.Parser.ParseFrom(stream); + } + + // Assert + Assert.Equal(expectedResourceSpans.Resource.Attributes.Count, actualResourceSpans.Resource.Attributes.Count); + foreach (var actualAttribute in actualResourceSpans.Resource.Attributes) + { + Assert.Contains(actualAttribute, expectedResourceSpans.Resource.Attributes); + } + } +} diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/.gitignore b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/.gitignore index feada15042..b9e60522f6 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/.gitignore +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/.gitignore @@ -1,3 +1,9 @@ # Self-signed cert generated by integration test otel-collector.crt otel-collector.key +otel-client.crt +otel-client.key +otel-untrusted-collector.crt +otel-untrusted-collector.key +certs/* +certs diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/Dockerfile b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/Dockerfile index 691524a9d2..0685ff1181 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/Dockerfile +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/Dockerfile @@ -2,12 +2,12 @@ # This should be run from the root of the repo: # docker build --file test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/Dockerfile -ARG BUILD_SDK_VERSION=8.0 -ARG TEST_SDK_VERSION=8.0 +ARG BUILD_SDK_VERSION=9.0 +ARG TEST_SDK_VERSION=9.0 FROM mcr.microsoft.com/dotnet/sdk:${BUILD_SDK_VERSION} AS build ARG PUBLISH_CONFIGURATION=Release -ARG PUBLISH_FRAMEWORK=net8.0 +ARG PUBLISH_FRAMEWORK=net9.0 WORKDIR /repo COPY . ./ WORKDIR "/repo/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests" diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs index 9fbe4b5703..3e40b9c6d9 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.Diagnostics.Metrics; using System.Diagnostics.Tracing; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Logs; @@ -22,7 +21,7 @@ public sealed class IntegrationTests : IDisposable private const int ExportIntervalMilliseconds = 10000; private static readonly SdkLimitOptions DefaultSdkLimitOptions = new(); private static readonly ExperimentalOptions DefaultExperimentalOptions = new(); - private static readonly string CollectorHostname = SkipUnlessEnvVarFoundTheoryAttribute.GetEnvironmentVariable(CollectorHostnameEnvVarName); + private static readonly string? CollectorHostname = SkipUnlessEnvVarFoundTheoryAttribute.GetEnvironmentVariable(CollectorHostnameEnvVarName); private readonly OpenTelemetryEventListener openTelemetryEventListener; public IntegrationTests(ITestOutputHelper outputHelper) @@ -62,7 +61,7 @@ public void TraceExportResultIsSuccess(OtlpExportProtocol protocol, string endpo }, }; - DelegatingExporter delegatingExporter = null; + DelegatingExporter? delegatingExporter = null; var exportResults = new List(); var activitySourceName = "otlp.collector.test"; @@ -141,7 +140,7 @@ public void MetricExportResultIsSuccess(OtlpExportProtocol protocol, string endp Protocol = protocol, }; - DelegatingExporter delegatingExporter = null; + DelegatingExporter? delegatingExporter = null; var exportResults = new List(); var meterName = "otlp.collector.test"; @@ -221,7 +220,7 @@ public void LogExportResultIsSuccess(OtlpExportProtocol protocol, string endpoin Protocol = protocol, }; - DelegatingExporter delegatingExporter = null; + DelegatingExporter delegatingExporter; var exportResults = new List(); var processorOptions = new LogRecordExportProcessorOptions { @@ -235,7 +234,7 @@ public void LogExportResultIsSuccess(OtlpExportProtocol protocol, string endpoin using var loggerFactory = LoggerFactory.Create(builder => { builder - .AddOpenTelemetry(options => options + .UseOpenTelemetry(logging => logging .AddProcessor(sp => OtlpLogExporterHelperExtensions.BuildOtlpLogExporter( sp, @@ -299,8 +298,8 @@ protected override void OnEventSourceCreated(EventSource eventSource) protected override void OnEventWritten(EventWrittenEventArgs eventData) { - string message; - if (eventData.Message != null && (eventData.Payload?.Count ?? 0) > 0) + string? message; + if (eventData.Message != null && eventData.Payload != null && eventData.Payload.Count > 0) { message = string.Format(eventData.Message, eventData.Payload.ToArray()); } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/create-cert.sh b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/create-cert.sh deleted file mode 100755 index c0821abc46..0000000000 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/create-cert.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -set -e - -# Generate self-signed certificate for the collector -openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \ - -subj "/CN=otel-collector" \ - -keyout /otel-collector.key -out /otel-collector.crt - -# Copy the certificate and private key file to shared volume that the collector -# container and test container can access -cp /otel-collector.crt /otel-collector.key /cfg - -chmod 644 /cfg/otel-collector.key - -# The integration test is run via docker-compose with the --exit-code-from -# option. The --exit-code-from option implies --abort-on-container-exit -# which means when any container exits then all containers are stopped. -# Since the container running this script would be otherwise short-lived -# we sleep here. If the test does not finish within this time then the test -# container will be stopped and have a non-zero exit code. -sleep 300 diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/docker-compose.yml b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/docker-compose.yml index b6317ff518..f281c47dce 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/docker-compose.yml +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/docker-compose.yml @@ -5,11 +5,20 @@ version: '3.7' services: - create-cert: - image: mcr.microsoft.com/dotnet/sdk:7.0 + init-service: + image: otel-test-image + build: + context: . + dockerfile: ./test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/Dockerfile volumes: - ./test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest:/cfg - command: /cfg/create-cert.sh + command: > + sh -c " + mkdir -p /cfg/certs; + cp /test/*.pem /cfg/certs/; + chmod 644 /cfg/certs/*; + sleep 1000; + " otel-collector: image: otel/opentelemetry-collector @@ -17,9 +26,10 @@ services: - ./test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest:/cfg command: --config=/cfg/otel-collector-config.yaml depends_on: - - create-cert + - init-service tests: + image: otel-test-image build: context: . dockerfile: ./test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/Dockerfile @@ -27,7 +37,7 @@ services: - ./test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest:/cfg command: /cfg/run-test.sh environment: - - OTEL_COLLECTOR_HOSTNAME=otel-collector - - OTEL_MOCK_COLLECTOR_HOSTNAME=mock-otel-collector + OTEL_COLLECTOR_HOSTNAME: otel-collector + OTEL_MOCK_COLLECTOR_HOSTNAME: mock-otel-collector depends_on: - otel-collector diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/otel-collector-config.yaml b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/otel-collector-config.yaml index 477de40fe7..9a71c67e4d 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/otel-collector-config.yaml +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/otel-collector-config.yaml @@ -16,26 +16,52 @@ receivers: grpc: endpoint: 0.0.0.0:5317 tls: - cert_file: /cfg/otel-collector.crt - key_file: /cfg/otel-collector.key + cert_file: /cfg/certs/otel-test-server-cert.pem + key_file: /cfg/certs/otel-test-server-key.pem http: endpoint: 0.0.0.0:5318 tls: - cert_file: /cfg/otel-collector.crt - key_file: /cfg/otel-collector.key + cert_file: /cfg/certs/otel-test-server-cert.pem + key_file: /cfg/certs/otel-test-server-key.pem + otlp/untrustedtls: + protocols: + grpc: + endpoint: 0.0.0.0:6317 + tls: + cert_file: /cfg/certs/otel-untrusted-collector-cert.pem + key_file: /cfg/certs/otel-untrusted-collector-key.pem + http: + endpoint: 0.0.0.0:6318 + tls: + cert_file: /cfg/certs/otel-untrusted-collector-cert.pem + key_file: /cfg/certs/otel-untrusted-collector-key.pem + otlp/mtls: + protocols: + grpc: + endpoint: 0.0.0.0:7317 + tls: + cert_file: /cfg/certs/otel-test-server-cert.pem + key_file: /cfg/certs/otel-test-server-key.pem + client_ca_file: /cfg/certs/otel-test-ca-cert.pem + http: + endpoint: 0.0.0.0:7318 + tls: + cert_file: /cfg/certs/otel-test-server-cert.pem + key_file: /cfg/certs/otel-test-server-key.pem + client_ca_file: /cfg/certs/otel-test-ca-cert.pem exporters: - logging: + debug: verbosity: detailed service: pipelines: traces: - receivers: [otlp, otlp/tls] - exporters: [logging] + receivers: [otlp, otlp/tls, otlp/untrustedtls, otlp/mtls] + exporters: [debug] metrics: - receivers: [otlp, otlp/tls] - exporters: [logging] + receivers: [otlp, otlp/tls, otlp/untrustedtls, otlp/mtls] + exporters: [debug] logs: - receivers: [otlp, otlp/tls] - exporters: [logging] + receivers: [otlp, otlp/tls, otlp/untrustedtls, otlp/mtls] + exporters: [debug] diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/run-test.sh b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/run-test.sh index d88a7f1aa5..fbe9f076a9 100755 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/run-test.sh +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/run-test.sh @@ -1,8 +1,8 @@ #!/bin/bash set -e -# Trust the self-signed certificated used by the collector -cp /cfg/otel-collector.crt /usr/local/share/ca-certificates/ +# Trust the self-signed certificate used by the collector +cp /cfg/certs/otel-test-ca-cert.pem /usr/local/share/ca-certificates/otel-test-ca-cert.crt update-ca-certificates --verbose dotnet test OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.dll --TestCaseFilter:CategoryName=CollectorIntegrationTests --logger "console;verbosity=detailed" diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/MockCollectorIntegrationTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/MockCollectorIntegrationTests.cs index a5ce7a0a27..27b64e08f6 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/MockCollectorIntegrationTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/MockCollectorIntegrationTests.cs @@ -3,6 +3,7 @@ #if !NETFRAMEWORK using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Net; using Google.Protobuf; using Grpc.Core; @@ -74,7 +75,7 @@ public async Task TestRecoveryAfterFailedExport() await httpClient.GetAsync($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}"); var exportResults = new List(); - var otlpExporter = new OtlpTraceExporter(new OtlpExporterOptions() { Endpoint = new Uri($"http://localhost:{testGrpcPort}") }); + using var otlpExporter = new OtlpTraceExporter(new OtlpExporterOptions() { Endpoint = new Uri($"http://localhost:{testGrpcPort}") }); var delegatingExporter = new DelegatingExporter { OnExportFunc = (batch) => @@ -94,12 +95,12 @@ public async Task TestRecoveryAfterFailedExport() using var source = new ActivitySource(activitySourceName); - source.StartActivity().Stop(); + source.StartActivity()?.Stop(); Assert.Single(exportResults); Assert.Equal(ExportResult.Failure, exportResults[0]); - source.StartActivity().Stop(); + source.StartActivity()?.Stop(); Assert.Equal(2, exportResults.Count); Assert.Equal(ExportResult.Success, exportResults[1]); @@ -179,10 +180,10 @@ public async Task GrpcRetryTests(bool useRetryTransmissionHandler, ExportResult var exporterOptions = new OtlpExporterOptions() { Endpoint = endpoint, TimeoutMilliseconds = 20000, Protocol = OtlpExportProtocol.Grpc }; var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary { [ExperimentalOptions.OtlpRetryEnvVar] = useRetryTransmissionHandler ? "in_memory" : null }) + .AddInMemoryCollection(new Dictionary { [ExperimentalOptions.OtlpRetryEnvVar] = useRetryTransmissionHandler ? "in_memory" : null }) .Build(); - var otlpExporter = new OtlpTraceExporter(exporterOptions, new SdkLimitOptions(), new ExperimentalOptions(configuration)); + using var otlpExporter = new OtlpTraceExporter(exporterOptions, new SdkLimitOptions(), new ExperimentalOptions(configuration)); var activitySourceName = "otel.grpc.retry.test"; using var source = new ActivitySource(activitySourceName); @@ -192,6 +193,7 @@ public async Task GrpcRetryTests(bool useRetryTransmissionHandler, ExportResult .Build(); using var activity = source.StartActivity("GrpcRetryTest"); + Assert.NotNull(activity); activity.Stop(); using var batch = new Batch([activity], 1); @@ -263,10 +265,10 @@ public async Task HttpRetryTests(bool useRetryTransmissionHandler, ExportResult var exporterOptions = new OtlpExporterOptions() { Endpoint = endpoint, TimeoutMilliseconds = 20000, Protocol = OtlpExportProtocol.HttpProtobuf }; var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary { [ExperimentalOptions.OtlpRetryEnvVar] = useRetryTransmissionHandler ? "in_memory" : null }) + .AddInMemoryCollection(new Dictionary { [ExperimentalOptions.OtlpRetryEnvVar] = useRetryTransmissionHandler ? "in_memory" : null }) .Build(); - var otlpExporter = new OtlpTraceExporter(exporterOptions, new SdkLimitOptions(), new ExperimentalOptions(configuration)); + using var otlpExporter = new OtlpTraceExporter(exporterOptions, new SdkLimitOptions(), new ExperimentalOptions(configuration)); var activitySourceName = "otel.http.retry.test"; using var source = new ActivitySource(activitySourceName); @@ -276,6 +278,7 @@ public async Task HttpRetryTests(bool useRetryTransmissionHandler, ExportResult .Build(); using var activity = source.StartActivity("HttpRetryTest"); + Assert.NotNull(activity); activity.Stop(); using var batch = new Batch([activity], 1); @@ -348,7 +351,7 @@ public async Task HttpPersistentStorageRetryTests(bool usePersistentStorageTrans // TODO: update this to configure via experimental environment variable. OtlpExporterTransmissionHandler transmissionHandler; - MockFileProvider mockProvider = null; + MockFileProvider? mockProvider = null; if (usePersistentStorageTransmissionHandler) { mockProvider = new MockFileProvider(); @@ -368,7 +371,7 @@ public async Task HttpPersistentStorageRetryTests(bool usePersistentStorageTrans transmissionHandler = new OtlpExporterTransmissionHandler(exportClient, exporterOptions.TimeoutMilliseconds); } - var otlpExporter = new OtlpTraceExporter(exporterOptions, new(), new(), transmissionHandler); + using var otlpExporter = new OtlpTraceExporter(exporterOptions, new(), new(), transmissionHandler); var activitySourceName = "otel.http.persistent.storage.retry.test"; using var source = new ActivitySource(activitySourceName); @@ -378,6 +381,7 @@ public async Task HttpPersistentStorageRetryTests(bool usePersistentStorageTrans .Build(); using var activity = source.StartActivity("HttpPersistentStorageRetryTest"); + Assert.NotNull(activity); activity.Stop(); using var batch = new Batch([activity], 1); @@ -387,12 +391,13 @@ public async Task HttpPersistentStorageRetryTests(bool usePersistentStorageTrans if (usePersistentStorageTransmissionHandler) { + Assert.NotNull(mockProvider); if (exportResult == ExportResult.Success) { - Assert.Single(mockProvider.TryGetBlobs()); + Assert.Single(mockProvider!.TryGetBlobs()); // Force Retry - Assert.True((transmissionHandler as OtlpExporterPersistentStorageTransmissionHandler).InitiateAndWaitForRetryProcess(-1)); + Assert.True((transmissionHandler as OtlpExporterPersistentStorageTransmissionHandler)!.InitiateAndWaitForRetryProcess(-1)); Assert.False(mockProvider.TryGetBlob(out _)); } @@ -485,7 +490,7 @@ public async Task GrpcPersistentStorageRetryTests(bool usePersistentStorageTrans // TODO: update this to configure via experimental environment variable. OtlpExporterTransmissionHandler transmissionHandler; - MockFileProvider mockProvider = null; + MockFileProvider? mockProvider = null; if (usePersistentStorageTransmissionHandler) { mockProvider = new MockFileProvider(); @@ -505,7 +510,7 @@ public async Task GrpcPersistentStorageRetryTests(bool usePersistentStorageTrans transmissionHandler = new OtlpExporterTransmissionHandler(exportClient, exporterOptions.TimeoutMilliseconds); } - var otlpExporter = new OtlpTraceExporter(exporterOptions, new(), new(), transmissionHandler); + using var otlpExporter = new OtlpTraceExporter(exporterOptions, new(), new(), transmissionHandler); var activitySourceName = "otel.grpc.persistent.storage.retry.test"; using var source = new ActivitySource(activitySourceName); @@ -515,6 +520,7 @@ public async Task GrpcPersistentStorageRetryTests(bool usePersistentStorageTrans .Build(); using var activity = source.StartActivity("GrpcPersistentStorageRetryTest"); + Assert.NotNull(activity); activity.Stop(); using var batch = new Batch([activity], 1); @@ -524,12 +530,13 @@ public async Task GrpcPersistentStorageRetryTests(bool usePersistentStorageTrans if (usePersistentStorageTransmissionHandler) { + Assert.NotNull(mockProvider); if (exportResult == ExportResult.Success) { Assert.Single(mockProvider.TryGetBlobs()); // Force Retry - Assert.True((transmissionHandler as OtlpExporterPersistentStorageTransmissionHandler).InitiateAndWaitForRetryProcess(-1)); + Assert.True((transmissionHandler as OtlpExporterPersistentStorageTransmissionHandler)!.InitiateAndWaitForRetryProcess(-1)); Assert.False(mockProvider.TryGetBlob(out _)); } @@ -630,7 +637,7 @@ protected override bool OnTryCreateBlob(byte[] buffer, out PersistentBlob blob) return blob.TryWrite(buffer); } - protected override bool OnTryGetBlob(out PersistentBlob blob) + protected override bool OnTryGetBlob([NotNullWhen(true)] out PersistentBlob? blob) { blob = this.GetBlobs().FirstOrDefault(); 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 ad60a2385a..1b9526a3ea 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj @@ -2,10 +2,27 @@ $(TargetFrameworksForTests) - - disable + + + + + + + + + + + + + @@ -13,9 +30,7 @@ - - runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpAttributeTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpAttributeTests.cs index af31f32c79..a325874fb3 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpAttributeTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpAttributeTests.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Diagnostics.CodeAnalysis; using Google.Protobuf.Collections; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using Xunit; @@ -13,19 +14,19 @@ public class OtlpAttributeTests [Fact] public void NullValueAttribute() { - var kvp = new KeyValuePair("key", null); - Assert.False(TryTransformTag(kvp, out var _)); + var kvp = new KeyValuePair("key", null); + Assert.False(TryTransformTag(kvp, out _)); } [Fact] public void EmptyArrays() { - var kvp = new KeyValuePair("key", Array.Empty()); + var kvp = new KeyValuePair("key", Array.Empty()); Assert.True(TryTransformTag(kvp, out var attribute)); Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase); Assert.Empty(attribute.Value.ArrayValue.Values); - kvp = new KeyValuePair("key", Array.Empty()); + kvp = new KeyValuePair("key", Array.Empty()); Assert.True(TryTransformTag(kvp, out attribute)); Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase); Assert.Empty(attribute.Value.ArrayValue.Values); @@ -48,7 +49,7 @@ public void EmptyArrays() [InlineData(new long[] { 1, 2, 3 })] public void IntegralTypesSupported(object value) { - var kvp = new KeyValuePair("key", value); + var kvp = new KeyValuePair("key", value); Assert.True(TryTransformTag(kvp, out var attribute)); switch (value) @@ -77,7 +78,7 @@ public void IntegralTypesSupported(object value) [InlineData(new double[] { 1, 2, 3 })] public void FloatingPointTypesSupported(object value) { - var kvp = new KeyValuePair("key", value); + var kvp = new KeyValuePair("key", value); Assert.True(TryTransformTag(kvp, out var attribute)); switch (value) @@ -104,7 +105,7 @@ public void FloatingPointTypesSupported(object value) [InlineData(new bool[] { true, false, true })] public void BooleanTypeSupported(object value) { - var kvp = new KeyValuePair("key", value); + var kvp = new KeyValuePair("key", value); Assert.True(TryTransformTag(kvp, out var attribute)); switch (value) @@ -131,7 +132,7 @@ public void BooleanTypeSupported(object value) [InlineData("string")] public void StringTypesSupported(object value) { - var kvp = new KeyValuePair("key", 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); @@ -141,9 +142,9 @@ public void StringTypesSupported(object value) public void ObjectArrayTypesSupported() { var obj = new object(); - var objectArray = new object[] { null, "a", 'b', true, int.MaxValue, long.MaxValue, float.MaxValue, double.MaxValue, obj }; + var objectArray = new object?[] { null, "a", 'b', true, int.MaxValue, long.MaxValue, float.MaxValue, double.MaxValue, obj }; - var kvp = new KeyValuePair("key", objectArray); + var kvp = new KeyValuePair("key", objectArray); Assert.True(TryTransformTag(kvp, out var attribute)); Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase); @@ -179,14 +180,14 @@ public void ObjectArrayTypesSupported() public void StringArrayTypesSupported() { var charArray = new char[] { 'a', 'b', 'c' }; - var stringArray = new string[] { "a", "b", "c", string.Empty, null }; + var stringArray = new string?[] { "a", "b", "c", string.Empty, null }; - var kvp = new KeyValuePair("key", charArray); + var kvp = new KeyValuePair("key", charArray); Assert.True(TryTransformTag(kvp, out var attribute)); Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase); Assert.Equal(charArray.Select(x => x.ToString()), attribute.Value.ArrayValue.Values.Select(x => x.StringValue)); - kvp = new KeyValuePair("key", stringArray); + kvp = new KeyValuePair("key", stringArray); Assert.True(TryTransformTag(kvp, out attribute)); Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase); @@ -221,12 +222,12 @@ 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 object(), null }, }; foreach (var value in testValues) { - var kvp = new KeyValuePair("key", 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(value.ToString(), attribute.Value.StringValue); @@ -234,11 +235,12 @@ public void ToStringIsCalledForAllOtherTypes() foreach (var value in testArrayValues) { - var kvp = new KeyValuePair("key", value); + var kvp = new KeyValuePair("key", value); Assert.True(TryTransformTag(kvp, out var attribute)); Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase); var array = value as Array; + Assert.NotNull(array); for (var i = 0; i < attribute.Value.ArrayValue.Values.Count; ++i) { var expectedValue = array.GetValue(i)?.ToString(); @@ -249,7 +251,7 @@ public void ToStringIsCalledForAllOtherTypes() Assert.Equal(expectedValueCase, attribute.Value.ArrayValue.Values[i].ValueCase); if (expectedValueCase != OtlpCommon.AnyValue.ValueOneofCase.None) { - Assert.Equal(array.GetValue(i).ToString(), attribute.Value.ArrayValue.Values[i].StringValue); + Assert.Equal(array.GetValue(i)!.ToString(), attribute.Value.ArrayValue.Values[i].StringValue); } } } @@ -258,14 +260,14 @@ public void ToStringIsCalledForAllOtherTypes() [Fact] public void ExceptionInToStringIsCaught() { - var kvp = new KeyValuePair("key", new MyToStringMethodThrowsAnException()); - Assert.False(TryTransformTag(kvp, out var _)); + var kvp = new KeyValuePair("key", new MyToStringMethodThrowsAnException()); + Assert.False(TryTransformTag(kvp, out _)); - kvp = new KeyValuePair("key", new object[] { 1, false, new MyToStringMethodThrowsAnException() }); - Assert.False(TryTransformTag(kvp, out var _)); + kvp = new KeyValuePair("key", new object[] { 1, false, new MyToStringMethodThrowsAnException() }); + Assert.False(TryTransformTag(kvp, out _)); } - private static bool TryTransformTag(KeyValuePair tag, out OtlpCommon.KeyValue attribute) + private static bool TryTransformTag(KeyValuePair tag, [NotNullWhen(true)] out OtlpCommon.KeyValue? attribute) { var destination = new RepeatedField(); diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs index 2b7810dfad..4018998b89 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs @@ -73,7 +73,7 @@ public void GetMetadataFromHeadersThrowsExceptionOnInvalidFormat(string headers) [Theory] [InlineData("")] [InlineData(null)] - public void GetHeaders_NoOptionHeaders_ReturnsStandardHeaders(string optionHeaders) + public void GetHeaders_NoOptionHeaders_ReturnsStandardHeaders(string? optionHeaders) { var options = new OtlpExporterOptions { @@ -158,19 +158,19 @@ public void AppendPathIfNotPresent_TracesPath_AppendsCorrectly(string inputUri, [InlineData(OtlpExportProtocol.Grpc, typeof(OtlpGrpcLogExportClient), false, 10000, "disk")] [InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpLogExportClient), false, 10000, "disk")] [InlineData(OtlpExportProtocol.HttpProtobuf, typeof(OtlpHttpLogExportClient), true, 8000, "disk")] - public void GetTransmissionHandler_InitializesCorrectHandlerExportClientAndTimeoutValue(OtlpExportProtocol protocol, Type exportClientType, bool customHttpClient, int expectedTimeoutMilliseconds, string retryStrategy) + public void GetTransmissionHandler_InitializesCorrectHandlerExportClientAndTimeoutValue(OtlpExportProtocol protocol, Type exportClientType, bool customHttpClient, int expectedTimeoutMilliseconds, string? retryStrategy) { var exporterOptions = new OtlpExporterOptions() { Protocol = protocol }; if (customHttpClient) { exporterOptions.HttpClientFactory = () => { - return new HttpClient() { Timeout = TimeSpan.FromMilliseconds(expectedTimeoutMilliseconds) }; + return new HttpClient { Timeout = TimeSpan.FromMilliseconds(expectedTimeoutMilliseconds) }; }; } var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary { [ExperimentalOptions.OtlpRetryEnvVar] = retryStrategy }) + .AddInMemoryCollection(new Dictionary { [ExperimentalOptions.OtlpRetryEnvVar] = retryStrategy }) .Build(); if (exportClientType == typeof(OtlpGrpcTraceExportClient) || exportClientType == typeof(OtlpHttpTraceExportClient)) @@ -193,7 +193,7 @@ public void GetTransmissionHandler_InitializesCorrectHandlerExportClientAndTimeo } } - private static void AssertTransmissionHandler(OtlpExporterTransmissionHandler transmissionHandler, Type exportClientType, int expectedTimeoutMilliseconds, string retryStrategy) + private static void AssertTransmissionHandler(OtlpExporterTransmissionHandler transmissionHandler, Type exportClientType, int expectedTimeoutMilliseconds, string? retryStrategy) { if (retryStrategy == "in_memory") { diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs index dc79c95c07..c22f0e1486 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs @@ -74,7 +74,7 @@ public void OtlpExporterOptions_UsingIConfiguration(object testDataObject) [Fact] public void OtlpExporterOptions_InvalidEnvironmentVariableOverride() { - var values = new Dictionary() + var values = new Dictionary { ["EndpointWithInvalidValue"] = "invalid", ["TimeoutWithInvalidValue"] = "invalid", @@ -104,7 +104,7 @@ public void OtlpExporterOptions_InvalidEnvironmentVariableOverride() [Fact] public void OtlpExporterOptions_SetterOverridesEnvironmentVariable() { - var values = new Dictionary() + var values = new Dictionary { ["Endpoint"] = "http://test:8888", ["Timeout"] = "2000", @@ -169,7 +169,7 @@ public void OtlpExporterOptions_SettingEndpointToNullResetsAppendSignalPathToEnd { var options = new OtlpExporterOptions(OtlpExporterOptionsConfigurationType.Default); - Assert.Throws(() => options.Endpoint = null); + Assert.Throws(() => options.Endpoint = null!); } [Fact] @@ -177,7 +177,7 @@ public void OtlpExporterOptions_HttpClientFactoryThrowsWhenSetToNull() { var options = new OtlpExporterOptions(OtlpExporterOptionsConfigurationType.Default); - Assert.Throws(() => options.HttpClientFactory = null); + Assert.Throws(() => options.HttpClientFactory = null!); } [Fact] diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs index fd0ea958c2..4cb11ec782 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs @@ -3,13 +3,12 @@ using System.Collections.ObjectModel; using System.Diagnostics; -using System.Reflection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; +using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Transmission; using OpenTelemetry.Internal; using OpenTelemetry.Logs; @@ -101,7 +100,7 @@ public void UserHttpFactoryCalledWhenUsingHttpProtobuf() Assert.Equal(2, invocations); } - options.HttpClientFactory = () => null; + options.HttpClientFactory = () => null!; Assert.Throws(() => { using var exporter = new OtlpLogExporter(options); @@ -111,46 +110,32 @@ public void UserHttpFactoryCalledWhenUsingHttpProtobuf() [Fact] public void AddOtlpExporterSetsDefaultBatchExportProcessor() { - var loggerProvider = Sdk.CreateLoggerProviderBuilder() + using var loggerProvider = Sdk.CreateLoggerProviderBuilder() .AddOtlpExporter() .Build(); - CheckProcessorDefaults(); - - loggerProvider.Dispose(); - - void CheckProcessorDefaults() - { - var bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic; - - var processor = typeof(BaseProcessor) - .Assembly - .GetType("OpenTelemetry.Logs.LoggerProviderSdk") - .GetProperty("Processor", bindingFlags) - .GetValue(loggerProvider) as BatchExportProcessor; - - Assert.NotNull(processor); + var loggerProviderSdk = loggerProvider as LoggerProviderSdk; + Assert.NotNull(loggerProviderSdk); - var scheduledDelayMilliseconds = typeof(BatchExportProcessor) - .GetField("scheduledDelayMilliseconds", bindingFlags) - .GetValue(processor); + var batchProcessor = loggerProviderSdk.Processor as BatchLogRecordExportProcessor; + Assert.NotNull(batchProcessor); - Assert.Equal(5000, scheduledDelayMilliseconds); - } + Assert.Equal(BatchLogRecordExportProcessor.DefaultScheduledDelayMilliseconds, batchProcessor.ScheduledDelayMilliseconds); + Assert.Equal(BatchLogRecordExportProcessor.DefaultExporterTimeoutMilliseconds, batchProcessor.ExporterTimeoutMilliseconds); + Assert.Equal(BatchLogRecordExportProcessor.DefaultMaxExportBatchSize, batchProcessor.MaxExportBatchSize); } - [Fact] - public void AddOtlpLogExporterReceivesAttributesWithParseStateValueSetToFalse() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void AddOtlpLogExporterReceivesAttributesWithParseStateValueSetToFalse(bool callUseOpenTelemetry) { bool optionsValidated = false; var logRecords = new List(); using var loggerFactory = LoggerFactory.Create(builder => { - builder - .AddOpenTelemetry(options => options - .AddInMemoryExporter(logRecords) - .AddOtlpExporter()); + ConfigureOtlpExporter(builder, callUseOpenTelemetry, logRecords: logRecords); builder.Services.Configure(o => { @@ -172,21 +157,20 @@ public void AddOtlpLogExporterReceivesAttributesWithParseStateValueSetToFalse() } [Theory] - [InlineData(true)] - [InlineData(false)] - public void AddOtlpLogExporterParseStateValueCanBeTurnedOff(bool parseState) + [InlineData(true, false)] + [InlineData(false, false)] + [InlineData(true, true)] + [InlineData(false, true)] + public void AddOtlpLogExporterParseStateValueCanBeTurnedOff(bool parseState, bool callUseOpenTelemetry) { var logRecords = new List(); using var loggerFactory = LoggerFactory.Create(builder => { - builder - .AddOpenTelemetry(options => - { - options.ParseStateValues = parseState; - options - .AddInMemoryExporter(logRecords) - .AddOtlpExporter(); - }); + ConfigureOtlpExporter( + builder, + callUseOpenTelemetry, + configureOptions: o => o.ParseStateValues = parseState, + logRecords: logRecords); }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); @@ -215,23 +199,26 @@ public void AddOtlpLogExporterParseStateValueCanBeTurnedOff(bool parseState) } [Theory] - [InlineData(true)] - [InlineData(false)] - public void AddOtlpLogExporterParseStateValueCanBeTurnedOffHosting(bool parseState) + [InlineData(true, false)] + [InlineData(false, false)] + [InlineData(true, true)] + [InlineData(false, true)] + public void AddOtlpLogExporterParseStateValueCanBeTurnedOffHosting(bool parseState, bool callUseOpenTelemetry) { var logRecords = new List(); var hostBuilder = new HostBuilder(); - hostBuilder.ConfigureLogging(logging => logging - .AddOpenTelemetry(options => options - .AddInMemoryExporter(logRecords) - .AddOtlpExporter())); + hostBuilder.ConfigureLogging(logging => + { + ConfigureOtlpExporter(logging, callUseOpenTelemetry, logRecords: logRecords); + }); hostBuilder.ConfigureServices(services => services.Configure(options => options.ParseStateValues = parseState)); var host = hostBuilder.Build(); var loggerFactory = host.Services.GetService(); + Assert.NotNull(loggerFactory); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); logger.Log(LogLevel.Information, default, new { propertyA = "valueA" }, null, (s, e) => "Custom state log message"); Assert.Single(logRecords); @@ -257,18 +244,21 @@ public void AddOtlpLogExporterParseStateValueCanBeTurnedOffHosting(bool parseSta #pragma warning restore CS0618 // Type or member is obsolete } - [Fact] - public void OtlpLogRecordTestWhenStateValuesArePopulated() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void OtlpLogRecordTestWhenStateValuesArePopulated(bool useCustomSerializer) { var logRecords = new List(); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.IncludeFormattedMessage = true; - options.ParseStateValues = true; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => + { + options.IncludeFormattedMessage = true; + options.ParseStateValues = true; + }); }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); @@ -279,7 +269,16 @@ public void OtlpLogRecordTestWhenStateValuesArePopulated() var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); var logRecord = logRecords[0]; - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + OtlpLogs.LogRecord? otlpLogRecord; + + if (useCustomSerializer) + { + otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord); + } + else + { + otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + } Assert.NotNull(otlpLogRecord); Assert.Equal("Hello from tomato 2.99.", otlpLogRecord.Body.StringValue); @@ -300,20 +299,24 @@ public void OtlpLogRecordTestWhenStateValuesArePopulated() } [Theory] - [InlineData("true")] - [InlineData("false")] - [InlineData(null)] - public void CheckToOtlpLogRecordEventId(string emitLogEventAttributes) + [InlineData("true", true)] + [InlineData("false", true)] + [InlineData(null, true)] + [InlineData("true", false)] + [InlineData("false", false)] + [InlineData(null, false)] + public void CheckToOtlpLogRecordEventId(string? emitLogEventAttributes, bool useCustomSerializer) { var logRecords = new List(); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.IncludeFormattedMessage = true; - options.ParseStateValues = true; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => + { + options.IncludeFormattedMessage = true; + options.ParseStateValues = true; + }); }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); @@ -321,14 +324,23 @@ public void CheckToOtlpLogRecordEventId(string emitLogEventAttributes) Assert.Single(logRecords); var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary { [ExperimentalOptions.EmitLogEventEnvVar] = emitLogEventAttributes }) + .AddInMemoryCollection(new Dictionary { [ExperimentalOptions.EmitLogEventEnvVar] = emitLogEventAttributes }) .Build(); var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new(configuration)); var logRecord = logRecords[0]; - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + OtlpLogs.LogRecord? otlpLogRecord; + + if (useCustomSerializer) + { + otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new(configuration), logRecord); + } + else + { + otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + } Assert.NotNull(otlpLogRecord); Assert.Equal("Hello from tomato 2.99.", otlpLogRecord.Body.StringValue); @@ -351,7 +363,16 @@ public void CheckToOtlpLogRecordEventId(string emitLogEventAttributes) Assert.Single(logRecords); logRecord = logRecords[0]; - otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + + if (useCustomSerializer) + { + otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new(configuration), logRecord); + } + else + { + otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + } + Assert.NotNull(otlpLogRecord); Assert.Equal("Hello from tomato 2.99.", otlpLogRecord.Body.StringValue); @@ -371,16 +392,15 @@ public void CheckToOtlpLogRecordEventId(string emitLogEventAttributes) } } - [Fact] - public void CheckToOtlpLogRecordTimestamps() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CheckToOtlpLogRecordTimestamps(bool useCustomSerializer) { var logRecords = new List(); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry(logging => logging.AddInMemoryExporter(logRecords)); }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); @@ -388,22 +408,31 @@ public void CheckToOtlpLogRecordTimestamps() var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); var logRecord = logRecords[0]; - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + OtlpLogs.LogRecord? otlpLogRecord; + if (useCustomSerializer) + { + otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord); + } + else + { + otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + } + + Assert.NotNull(otlpLogRecord); Assert.True(otlpLogRecord.TimeUnixNano > 0); Assert.True(otlpLogRecord.ObservedTimeUnixNano > 0); } - [Fact] - public void CheckToOtlpLogRecordTraceIdSpanIdFlagWithNoActivity() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CheckToOtlpLogRecordTraceIdSpanIdFlagWithNoActivity(bool useCustomSerializer) { var logRecords = new List(); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry(logging => logging.AddInMemoryExporter(logRecords)); }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); @@ -412,24 +441,33 @@ public void CheckToOtlpLogRecordTraceIdSpanIdFlagWithNoActivity() var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); var logRecord = logRecords[0]; - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + OtlpLogs.LogRecord? otlpLogRecord; + + if (useCustomSerializer) + { + otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord); + } + else + { + otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + } Assert.Null(Activity.Current); + Assert.NotNull(otlpLogRecord); Assert.True(otlpLogRecord.TraceId.IsEmpty); Assert.True(otlpLogRecord.SpanId.IsEmpty); - Assert.True(otlpLogRecord.Flags == 0); + Assert.Equal(0u, otlpLogRecord.Flags); } - [Fact] - public void CheckToOtlpLogRecordSpanIdTraceIdAndFlag() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CheckToOtlpLogRecordSpanIdTraceIdAndFlag(bool useCustomSerializer) { var logRecords = new List(); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry(logging => logging.AddInMemoryExporter(logRecords)); }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); @@ -447,31 +485,47 @@ public void CheckToOtlpLogRecordSpanIdTraceIdAndFlag() var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); var logRecord = logRecords[0]; - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + OtlpLogs.LogRecord? otlpLogRecord; + + if (useCustomSerializer) + { + otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord); + } + else + { + otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + } + + Assert.NotNull(otlpLogRecord); Assert.Equal(expectedTraceId.ToString(), ActivityTraceId.CreateFromBytes(otlpLogRecord.TraceId.ToByteArray()).ToString()); Assert.Equal(expectedSpanId.ToString(), ActivitySpanId.CreateFromBytes(otlpLogRecord.SpanId.ToByteArray()).ToString()); Assert.Equal((uint)logRecord.TraceFlags, otlpLogRecord.Flags); } [Theory] - [InlineData(LogLevel.Trace)] - [InlineData(LogLevel.Debug)] - [InlineData(LogLevel.Information)] - [InlineData(LogLevel.Warning)] - [InlineData(LogLevel.Error)] - [InlineData(LogLevel.Critical)] - public void CheckToOtlpLogRecordSeverityLevelAndText(LogLevel logLevel) + [InlineData(LogLevel.Trace, true)] + [InlineData(LogLevel.Debug, true)] + [InlineData(LogLevel.Information, true)] + [InlineData(LogLevel.Warning, true)] + [InlineData(LogLevel.Error, true)] + [InlineData(LogLevel.Critical, true)] + [InlineData(LogLevel.Trace, false)] + [InlineData(LogLevel.Debug, false)] + [InlineData(LogLevel.Information, false)] + [InlineData(LogLevel.Warning, false)] + [InlineData(LogLevel.Error, false)] + [InlineData(LogLevel.Critical, false)] + public void CheckToOtlpLogRecordSeverityLevelAndText(LogLevel logLevel, bool useCustomSerializer) { var logRecords = new List(); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.AddInMemoryExporter(logRecords); - options.IncludeFormattedMessage = true; - }) - .AddFilter("CheckToOtlpLogRecordSeverityLevelAndText", LogLevel.Trace); + builder + .UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => options.IncludeFormattedMessage = true) + .AddFilter("CheckToOtlpLogRecordSeverityLevelAndText", LogLevel.Trace); }); var logger = loggerFactory.CreateLogger("CheckToOtlpLogRecordSeverityLevelAndText"); @@ -481,12 +535,22 @@ public void CheckToOtlpLogRecordSeverityLevelAndText(LogLevel logLevel) var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); var logRecord = logRecords[0]; - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + OtlpLogs.LogRecord? otlpLogRecord; + + if (useCustomSerializer) + { + otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord); + } + else + { + otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + } Assert.NotNull(otlpLogRecord); #pragma warning disable CS0618 // Type or member is obsolete Assert.Equal(logRecord.LogLevel.ToString(), otlpLogRecord.SeverityText); #pragma warning restore CS0618 // Type or member is obsolete + Assert.NotNull(logRecord.Severity); Assert.Equal((int)logRecord.Severity, (int)otlpLogRecord.SeverityNumber); switch (logLevel) { @@ -512,19 +576,22 @@ public void CheckToOtlpLogRecordSeverityLevelAndText(LogLevel logLevel) } [Theory] - [InlineData(true)] - [InlineData(false)] - public void CheckToOtlpLogRecordBodyIsPopulated(bool includeFormattedMessage) + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(false, false)] + public void CheckToOtlpLogRecordBodyIsPopulated(bool includeFormattedMessage, bool useCustomSerializer) { var logRecords = new List(); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.AddInMemoryExporter(logRecords); - options.IncludeFormattedMessage = includeFormattedMessage; - options.ParseStateValues = true; - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => + { + options.IncludeFormattedMessage = includeFormattedMessage; + options.ParseStateValues = true; + }); }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); @@ -536,7 +603,16 @@ public void CheckToOtlpLogRecordBodyIsPopulated(bool includeFormattedMessage) var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); var logRecord = logRecords[0]; - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + OtlpLogs.LogRecord? otlpLogRecord; + + if (useCustomSerializer) + { + otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord); + } + else + { + otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + } Assert.NotNull(otlpLogRecord); if (includeFormattedMessage) @@ -555,7 +631,15 @@ public void CheckToOtlpLogRecordBodyIsPopulated(bool includeFormattedMessage) Assert.Single(logRecords); logRecord = logRecords[0]; - otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + + if (useCustomSerializer) + { + otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord); + } + else + { + otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + } Assert.NotNull(otlpLogRecord); @@ -567,11 +651,19 @@ public void CheckToOtlpLogRecordBodyIsPopulated(bool includeFormattedMessage) // Scenario 3 - Using the raw ILogger.Log Method, but with null // formatter. - logger.Log(LogLevel.Information, default, "state", exception: null, formatter: null); + logger.Log(LogLevel.Information, default, "state", exception: null, formatter: null!); Assert.Single(logRecords); logRecord = logRecords[0]; - otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + + if (useCustomSerializer) + { + otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord); + } + else + { + otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + } Assert.NotNull(otlpLogRecord); @@ -611,6 +703,7 @@ public void LogRecordBodyIsExportedWhenUsingBridgeApi(bool isBodySet) var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecords[0]); + Assert.NotNull(otlpLogRecord); if (isBodySet) { Assert.Equal("Hello world", otlpLogRecord.Body?.StringValue); @@ -622,6 +715,7 @@ public void LogRecordBodyIsExportedWhenUsingBridgeApi(bool isBodySet) otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecords[1]); + Assert.NotNull(otlpLogRecord); Assert.Equal(2, otlpLogRecord.Attributes.Count); var index = 0; @@ -636,16 +730,15 @@ public void LogRecordBodyIsExportedWhenUsingBridgeApi(bool isBodySet) Assert.Equal("Hello from {name} {price}.", otlpLogRecord.Body.StringValue); } - [Fact] - public void CheckToOtlpLogRecordExceptionAttributes() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CheckToOtlpLogRecordExceptionAttributes(bool useCustomSerializer) { var logRecords = new List(); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry(logging => logging.AddInMemoryExporter(logRecords)); }); var logger = loggerFactory.CreateLogger("OtlpLogExporterTests"); @@ -656,12 +749,22 @@ public void CheckToOtlpLogRecordExceptionAttributes() var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + OtlpLogs.LogRecord? otlpLogRecord; + + if (useCustomSerializer) + { + otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord); + } + else + { + otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + } Assert.NotNull(otlpLogRecord); var otlpLogRecordAttributes = otlpLogRecord.Attributes.ToString(); Assert.Contains(SemanticConventions.AttributeExceptionType, otlpLogRecordAttributes); + Assert.NotNull(logRecord.Exception); Assert.Contains(logRecord.Exception.GetType().Name, otlpLogRecordAttributes); Assert.Contains(SemanticConventions.AttributeExceptionMessage, otlpLogRecordAttributes); @@ -671,8 +774,10 @@ public void CheckToOtlpLogRecordExceptionAttributes() Assert.Contains(logRecord.Exception.ToInvariantString(), otlpLogRecordAttributes); } - [Fact] - public void CheckToOtlpLogRecordRespectsAttributeLimits() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CheckToOtlpLogRecordRespectsAttributeLimits(bool useCustomSerializer) { var sdkLimitOptions = new SdkLimitOptions { @@ -683,11 +788,9 @@ public void CheckToOtlpLogRecordRespectsAttributeLimits() var logRecords = new List(); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.ParseStateValues = true; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => options.ParseStateValues = true); }); var logger = loggerFactory.CreateLogger(string.Empty); @@ -696,7 +799,16 @@ public void CheckToOtlpLogRecordRespectsAttributeLimits() var otlpLogRecordTransformer = new OtlpLogRecordTransformer(sdkLimitOptions, new()); var logRecord = logRecords[0]; - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + OtlpLogs.LogRecord? otlpLogRecord; + + if (useCustomSerializer) + { + otlpLogRecord = ToOtlpLogs(sdkLimitOptions, new(), logRecord); + } + else + { + otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + } Assert.NotNull(otlpLogRecord); Assert.Equal(1u, otlpLogRecord.DroppedAttributesCount); @@ -782,18 +894,18 @@ public void Export_WhenExportIsSuccessful_ReturnsExportResultSuccess() Assert.Equal(ExportResult.Success, result); } - [Fact] - public void ToOtlpLog_WhenOptionsIncludeScopesIsFalse_DoesNotContainScopeAttribute() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ToOtlpLog_WhenOptionsIncludeScopesIsFalse_DoesNotContainScopeAttribute(bool useCustomSerializer) { // Arrange. var logRecords = new List(1); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.IncludeScopes = false; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => options.IncludeScopes = false); }); var logger = loggerFactory.CreateLogger("Some category"); @@ -812,25 +924,36 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsFalse_DoesNotContainScopeAttribu // Assert. var logRecord = logRecords.Single(); var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + OtlpLogs.LogRecord? otlpLogRecord; + + if (useCustomSerializer) + { + otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord); + } + else + { + otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + } + + Assert.NotNull(otlpLogRecord); var actualScope = TryGetAttribute(otlpLogRecord, expectedScopeKey); Assert.Null(actualScope); } [Theory] - [InlineData("Some scope value")] - [InlineData('a')] - public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeStringValue(object scopeValue) + [InlineData("Some scope value", false)] + [InlineData('a', false)] + [InlineData("Some scope value", true)] + [InlineData('a', true)] + public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeStringValue(object scopeValue, bool useCustomSerializer) { // Arrange. var logRecords = new List(1); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.IncludeScopes = true; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => options.IncludeScopes = true); }); var logger = loggerFactory.CreateLogger(nameof(OtlpLogExporterTests)); @@ -848,7 +971,12 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeStrin // Assert. var logRecord = logRecords.Single(); var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + + var otlpLogRecord = useCustomSerializer + ? ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord) + : otlpLogRecordTransformer.ToOtlpLog(logRecord); + + Assert.NotNull(otlpLogRecord); Assert.Single(otlpLogRecord.Attributes); var actualScope = TryGetAttribute(otlpLogRecord, scopeKey); Assert.NotNull(actualScope); @@ -858,19 +986,19 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeStrin } [Theory] - [InlineData(true)] - [InlineData(false)] - public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeBoolValue(bool scopeValue) + [InlineData(true, false)] + [InlineData(false, false)] + [InlineData(true, true)] + [InlineData(false, true)] + public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeBoolValue(bool scopeValue, bool useCustomSerializer) { // Arrange. var logRecords = new List(1); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.IncludeScopes = true; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => options.IncludeScopes = true); }); var logger = loggerFactory.CreateLogger(nameof(OtlpLogExporterTests)); @@ -888,7 +1016,12 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeBoolV // Assert. var logRecord = logRecords.Single(); var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + + var otlpLogRecord = useCustomSerializer + ? ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord) + : otlpLogRecordTransformer.ToOtlpLog(logRecord); + + Assert.NotNull(otlpLogRecord); Assert.Single(otlpLogRecord.Attributes); var actualScope = TryGetAttribute(otlpLogRecord, scopeKey); Assert.NotNull(actualScope); @@ -898,31 +1031,43 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeBoolV } [Theory] - [InlineData(byte.MinValue)] - [InlineData(byte.MaxValue)] - [InlineData(sbyte.MinValue)] - [InlineData(sbyte.MaxValue)] - [InlineData(short.MinValue)] - [InlineData(short.MaxValue)] - [InlineData(ushort.MinValue)] - [InlineData(ushort.MaxValue)] - [InlineData(int.MinValue)] - [InlineData(int.MaxValue)] - [InlineData(uint.MinValue)] - [InlineData(uint.MaxValue)] - [InlineData(long.MinValue)] - [InlineData(long.MaxValue)] - public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeIntValue(object scopeValue) + [InlineData(byte.MinValue, false)] + [InlineData(byte.MaxValue, false)] + [InlineData(sbyte.MinValue, false)] + [InlineData(sbyte.MaxValue, false)] + [InlineData(short.MinValue, false)] + [InlineData(short.MaxValue, false)] + [InlineData(ushort.MinValue, false)] + [InlineData(ushort.MaxValue, false)] + [InlineData(int.MinValue, false)] + [InlineData(int.MaxValue, false)] + [InlineData(uint.MinValue, false)] + [InlineData(uint.MaxValue, false)] + [InlineData(long.MinValue, false)] + [InlineData(long.MaxValue, false)] + [InlineData(byte.MinValue, true)] + [InlineData(byte.MaxValue, true)] + [InlineData(sbyte.MinValue, true)] + [InlineData(sbyte.MaxValue, true)] + [InlineData(short.MinValue, true)] + [InlineData(short.MaxValue, true)] + [InlineData(ushort.MinValue, true)] + [InlineData(ushort.MaxValue, true)] + [InlineData(int.MinValue, true)] + [InlineData(int.MaxValue, true)] + [InlineData(uint.MinValue, true)] + [InlineData(uint.MaxValue, true)] + [InlineData(long.MinValue, true)] + [InlineData(long.MaxValue, true)] + public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeIntValue(object scopeValue, bool useCustomSerializer) { // Arrange. var logRecords = new List(1); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.IncludeScopes = true; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => options.IncludeScopes = true); }); var logger = loggerFactory.CreateLogger(nameof(OtlpLogExporterTests)); @@ -940,7 +1085,12 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeIntVa // Assert. var logRecord = logRecords.Single(); var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + + var otlpLogRecord = useCustomSerializer + ? ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord) + : otlpLogRecordTransformer.ToOtlpLog(logRecord); + + Assert.NotNull(otlpLogRecord); Assert.Single(otlpLogRecord.Attributes); var actualScope = TryGetAttribute(otlpLogRecord, scopeKey); Assert.NotNull(actualScope); @@ -950,19 +1100,19 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeIntVa } [Theory] - [InlineData(float.MinValue)] - [InlineData(float.MaxValue)] - public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubleValueForFloat(float scopeValue) + [InlineData(float.MinValue, false)] + [InlineData(float.MaxValue, false)] + [InlineData(float.MinValue, true)] + [InlineData(float.MaxValue, true)] + public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubleValueForFloat(float scopeValue, bool useCustomSerializer) { // Arrange. var logRecords = new List(1); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.IncludeScopes = true; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => options.IncludeScopes = true); }); var logger = loggerFactory.CreateLogger(nameof(OtlpLogExporterTests)); @@ -971,7 +1121,7 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubl // Act. using (logger.BeginScope(new List> { - new KeyValuePair(scopeKey, scopeValue), + new(scopeKey, scopeValue), })) { logger.LogInformation("Some log information message."); @@ -980,7 +1130,12 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubl // Assert. var logRecord = logRecords.Single(); var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + + var otlpLogRecord = useCustomSerializer + ? ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord) + : otlpLogRecordTransformer.ToOtlpLog(logRecord); + + Assert.NotNull(otlpLogRecord); Assert.Single(otlpLogRecord.Attributes); var actualScope = TryGetAttribute(otlpLogRecord, scopeKey); Assert.NotNull(actualScope); @@ -990,19 +1145,19 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubl } [Theory] - [InlineData(double.MinValue)] - [InlineData(double.MaxValue)] - public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubleValueForDouble(double scopeValue) + [InlineData(double.MinValue, false)] + [InlineData(double.MaxValue, false)] + [InlineData(double.MinValue, true)] + [InlineData(double.MaxValue, true)] + public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubleValueForDouble(double scopeValue, bool useCustomSerializer) { // Arrange. var logRecords = new List(1); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.IncludeScopes = true; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => options.IncludeScopes = true); }); var logger = loggerFactory.CreateLogger(nameof(OtlpLogExporterTests)); @@ -1011,7 +1166,7 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubl // Act. using (logger.BeginScope(new List> { - new KeyValuePair(scopeKey, scopeValue), + new(scopeKey, scopeValue), })) { logger.LogInformation("Some log information message."); @@ -1020,7 +1175,12 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubl // Assert. var logRecord = logRecords.Single(); var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + + var otlpLogRecord = useCustomSerializer + ? ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord) + : otlpLogRecordTransformer.ToOtlpLog(logRecord); + + Assert.NotNull(otlpLogRecord); Assert.Single(otlpLogRecord.Attributes); var actualScope = TryGetAttribute(otlpLogRecord, scopeKey); Assert.NotNull(actualScope); @@ -1028,18 +1188,18 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeDoubl Assert.Equal(scopeValue.ToString(), actualScope.Value.DoubleValue.ToString()); } - [Fact] - public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfTypeString_ScopeIsIgnored() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfTypeString_ScopeIsIgnored(bool useCustomSerializer) { // Arrange. var logRecords = new List(1); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.IncludeScopes = true; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => options.IncludeScopes = true); }); var logger = loggerFactory.CreateLogger(nameof(OtlpLogExporterTests)); @@ -1054,32 +1214,40 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfTypeString // Assert. var logRecord = logRecords.Single(); var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + + var otlpLogRecord = useCustomSerializer + ? ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord) + : otlpLogRecordTransformer.ToOtlpLog(logRecord); + Assert.NotNull(otlpLogRecord); Assert.Empty(otlpLogRecord.Attributes); } [Theory] - [InlineData(typeof(int))] - [InlineData(typeof(float))] - [InlineData(typeof(decimal))] - [InlineData(typeof(char))] - [InlineData(typeof(bool))] - public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfPrimitiveTypes_ScopeIsIgnored(Type typeOfScopeState) + [InlineData(typeof(int), false)] + [InlineData(typeof(float), false)] + [InlineData(typeof(decimal), false)] + [InlineData(typeof(char), false)] + [InlineData(typeof(bool), false)] + [InlineData(typeof(int), true)] + [InlineData(typeof(float), true)] + [InlineData(typeof(decimal), true)] + [InlineData(typeof(char), true)] + [InlineData(typeof(bool), true)] + public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfPrimitiveTypes_ScopeIsIgnored(Type typeOfScopeState, bool useCustomSerializer) { // Arrange. var logRecords = new List(1); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.IncludeScopes = true; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => options.IncludeScopes = true); }); var logger = loggerFactory.CreateLogger(nameof(OtlpLogExporterTests)); var scopeState = Activator.CreateInstance(typeOfScopeState); + Assert.NotNull(scopeState); // Act. using (logger.BeginScope(scopeState)) @@ -1090,23 +1258,27 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfPrimitiveT // Assert. var logRecord = logRecords.Single(); var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + + var otlpLogRecord = useCustomSerializer + ? ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord) + : otlpLogRecordTransformer.ToOtlpLog(logRecord); + Assert.NotNull(otlpLogRecord); Assert.Empty(otlpLogRecord.Attributes); } - [Fact] - public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfDictionaryType_ScopeIsProcessed() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfDictionaryType_ScopeIsProcessed(bool useCustomSerializer) { // Arrange. var logRecords = new List(1); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.IncludeScopes = true; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => options.IncludeScopes = true); }); var logger = loggerFactory.CreateLogger(nameof(OtlpLogExporterTests)); @@ -1123,7 +1295,12 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfDictionary // Assert. var logRecord = logRecords.Single(); var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + + var otlpLogRecord = useCustomSerializer + ? ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord) + : otlpLogRecordTransformer.ToOtlpLog(logRecord); + + Assert.NotNull(otlpLogRecord); Assert.Single(otlpLogRecord.Attributes); var actualScope = TryGetAttribute(otlpLogRecord, scopeKey); Assert.NotNull(actualScope); @@ -1132,29 +1309,31 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfDictionary } [Theory] - [InlineData(typeof(List>))] - [InlineData(typeof(ReadOnlyCollection>))] - [InlineData(typeof(HashSet>))] - public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfEnumerableType_ScopeIsProcessed(Type typeOfScopeState) + [InlineData(typeof(List>), false)] + [InlineData(typeof(ReadOnlyCollection>), false)] + [InlineData(typeof(HashSet>), false)] + [InlineData(typeof(List>), true)] + [InlineData(typeof(ReadOnlyCollection>), true)] + [InlineData(typeof(HashSet>), true)] + public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfEnumerableType_ScopeIsProcessed(Type typeOfScopeState, bool useCustomSerializer) { // Arrange. var logRecords = new List(1); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.IncludeScopes = true; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => options.IncludeScopes = true); }); var logger = loggerFactory.CreateLogger(nameof(OtlpLogExporterTests)); const string scopeKey = "Some scope key"; const string scopeValue = "Some scope value"; - var scopeValues = new List> { new KeyValuePair(scopeKey, scopeValue) }; + var scopeValues = new List> { new(scopeKey, scopeValue) }; var scopeState = Activator.CreateInstance(typeOfScopeState, scopeValues) as ICollection>; // Act. + Assert.NotNull(scopeState); using (logger.BeginScope(scopeState)) { logger.LogInformation("Some log information message."); @@ -1163,7 +1342,12 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfEnumerable // Assert. var logRecord = logRecords.Single(); var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + + var otlpLogRecord = useCustomSerializer + ? ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord) + : otlpLogRecordTransformer.ToOtlpLog(logRecord); + + Assert.NotNull(otlpLogRecord); Assert.Single(otlpLogRecord.Attributes); var actualScope = TryGetAttribute(otlpLogRecord, scopeKey); Assert.NotNull(actualScope); @@ -1172,19 +1356,19 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeStateIsOfEnumerable } [Theory] - [InlineData("Same scope key", "Same scope key")] - [InlineData("Scope key 1", "Scope key 2")] - public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndMultipleScopesAreAdded_ContainsAllAddedScopeValues(string scopeKey1, string scopeKey2) + [InlineData("Same scope key", "Same scope key", false)] + [InlineData("Scope key 1", "Scope key 2", false)] + [InlineData("Same scope key", "Same scope key", true)] + [InlineData("Scope key 1", "Scope key 2", true)] + public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndMultipleScopesAreAdded_ContainsAllAddedScopeValues(string scopeKey1, string scopeKey2, bool useCustomSerializer) { // Arrange. var logRecords = new List(1); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.IncludeScopes = true; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => options.IncludeScopes = true); }); var logger = loggerFactory.CreateLogger(nameof(OtlpLogExporterTests)); @@ -1204,7 +1388,12 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndMultipleScopesAreAdded_C // Assert. var logRecord = logRecords.Single(); var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + + var otlpLogRecord = useCustomSerializer + ? ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord) + : otlpLogRecordTransformer.ToOtlpLog(logRecord); + + Assert.NotNull(otlpLogRecord); var allScopeValues = otlpLogRecord.Attributes .Where(_ => _.Key == scopeKey1 || _.Key == scopeKey2) .Select(_ => _.Value.StringValue); @@ -1215,19 +1404,19 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndMultipleScopesAreAdded_C } [Theory] - [InlineData("Same scope key", "Same scope key")] - [InlineData("Scope key 1", "Scope key 2")] - public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndMultipleScopeLevelsAreAdded_ContainsAllAddedScopeValues(string scopeKey1, string scopeKey2) + [InlineData("Same scope key", "Same scope key", false)] + [InlineData("Scope key 1", "Scope key 2", false)] + [InlineData("Same scope key", "Same scope key", true)] + [InlineData("Scope key 1", "Scope key 2", true)] + public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndMultipleScopeLevelsAreAdded_ContainsAllAddedScopeValues(string scopeKey1, string scopeKey2, bool useCustomSerializer) { // Arrange. var logRecords = new List(1); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.IncludeScopes = true; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => options.IncludeScopes = true); }); var logger = loggerFactory.CreateLogger(nameof(OtlpLogExporterTests)); @@ -1246,7 +1435,12 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndMultipleScopeLevelsAreAd // Assert. var logRecord = logRecords.Single(); var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + + var otlpLogRecord = useCustomSerializer + ? ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord) + : otlpLogRecordTransformer.ToOtlpLog(logRecord); + + Assert.NotNull(otlpLogRecord); var allScopeValues = otlpLogRecord.Attributes .Where(_ => _.Key == scopeKey1 || _.Key == scopeKey2) .Select(_ => _.Value.StringValue); @@ -1257,19 +1451,19 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndMultipleScopeLevelsAreAd } [Theory] - [InlineData("Same scope key", "Same scope key")] - [InlineData("Scope key 1", "Scope key 2")] - public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeIsUsedInLogMethod_ContainsAllAddedScopeValues(string scopeKey1, string scopeKey2) + [InlineData("Same scope key", "Same scope key", false)] + [InlineData("Scope key 1", "Scope key 2", false)] + [InlineData("Same scope key", "Same scope key", true)] + [InlineData("Scope key 1", "Scope key 2", true)] + public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeIsUsedInLogMethod_ContainsAllAddedScopeValues(string scopeKey1, string scopeKey2, bool useCustomSerializer) { // Arrange. var logRecords = new List(1); using var loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => - { - options.IncludeScopes = true; - options.AddInMemoryExporter(logRecords); - }); + builder.UseOpenTelemetry( + logging => logging.AddInMemoryExporter(logRecords), + options => options.IncludeScopes = true); }); var logger = loggerFactory.CreateLogger(nameof(OtlpLogExporterTests)); @@ -1293,7 +1487,12 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeIsUsedInLogMethod_C // Assert. var logRecord = logRecords.Single(); var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new()); - var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecord); + + var otlpLogRecord = useCustomSerializer + ? ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord) + : otlpLogRecordTransformer.ToOtlpLog(logRecord); + + Assert.NotNull(otlpLogRecord); var allScopeValues = otlpLogRecord.Attributes .Where(_ => _.Key == scopeKey1 || _.Key == scopeKey2) .Select(_ => _.Value.StringValue); @@ -1303,74 +1502,84 @@ public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_AndScopeIsUsedInLogMethod_C Assert.Contains(scopeValue2, allScopeValues); } - [Fact] - public void AddOtlpLogExporterDefaultOptionsTest() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void AddOtlpLogExporterDefaultOptionsTest(bool callUseOpenTelemetry) { - var options = new OpenTelemetryLoggerOptions(); - - options.AddOtlpExporter(); + var services = new ServiceCollection(); + services.AddLogging(builder => + { + ConfigureOtlpExporter(builder, callUseOpenTelemetry); + }); - var provider = new OpenTelemetryLoggerProvider(new TestOptionsMonitor(options)); + using var sp = services.BuildServiceProvider(); - var processor = GetProcessor(provider); + sp.GetRequiredService(); - Assert.NotNull(processor); - - var batchProcesor = processor as BatchLogRecordExportProcessor; + var provider = sp.GetRequiredService() as LoggerProviderSdk; + Assert.NotNull(provider); + var batchProcesor = provider.Processor as BatchLogRecordExportProcessor; Assert.NotNull(batchProcesor); - var batchProcessorType = typeof(BatchExportProcessor); - - Assert.Equal(5000, batchProcessorType.GetField("scheduledDelayMilliseconds", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(batchProcesor)); + Assert.Equal(BatchLogRecordExportProcessor.DefaultScheduledDelayMilliseconds, batchProcesor.ScheduledDelayMilliseconds); } [Theory] - [InlineData(ExportProcessorType.Simple)] - [InlineData(ExportProcessorType.Batch)] - public void AddOtlpLogExporterLogRecordProcessorOptionsTest(ExportProcessorType processorType) + [InlineData(ExportProcessorType.Simple, false)] + [InlineData(ExportProcessorType.Batch, false)] + [InlineData(ExportProcessorType.Simple, true)] + [InlineData(ExportProcessorType.Batch, true)] + public void AddOtlpLogExporterLogRecordProcessorOptionsTest(ExportProcessorType processorType, bool callUseOpenTelemetry) { - var options = new OpenTelemetryLoggerOptions(); - - options.AddOtlpExporter((o, l) => + var services = new ServiceCollection(); + services.AddLogging(builder => { - l.ExportProcessorType = processorType; - l.BatchExportProcessorOptions = new BatchExportLogRecordProcessorOptions() { ScheduledDelayMilliseconds = 1000 }; + ConfigureOtlpExporter( + builder, + callUseOpenTelemetry, + configureExporterAndProcessor: (e, p) => + { + p.ExportProcessorType = processorType; + p.BatchExportProcessorOptions = new BatchExportLogRecordProcessorOptions() { ScheduledDelayMilliseconds = 1000 }; + }); }); - var provider = new OpenTelemetryLoggerProvider(new TestOptionsMonitor(options)); + using var sp = services.BuildServiceProvider(); - var processor = GetProcessor(provider); + sp.GetRequiredService(); + var provider = sp.GetRequiredService() as LoggerProviderSdk; + Assert.NotNull(provider); + + var processor = provider.Processor; Assert.NotNull(processor); if (processorType == ExportProcessorType.Batch) { var batchProcesor = processor as BatchLogRecordExportProcessor; - Assert.NotNull(batchProcesor); - var batchProcessorType = typeof(BatchExportProcessor); - - Assert.Equal(1000, batchProcessorType.GetField("scheduledDelayMilliseconds", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(batchProcesor)); + Assert.Equal(1000, batchProcesor.ScheduledDelayMilliseconds); } else { - var simpleProcesor = processor as SimpleLogRecordExportProcessor; + var simpleProcessor = processor as SimpleLogRecordExportProcessor; - Assert.NotNull(simpleProcesor); + Assert.NotNull(simpleProcessor); } } - [Fact] - public void ValidateInstrumentationScope() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ValidateInstrumentationScope(bool useCustomSerializer) { var logRecords = new List(); using var loggerFactory = LoggerFactory.Create(builder => { - builder - .AddOpenTelemetry(options => options - .AddInMemoryExporter(logRecords)); + builder.UseOpenTelemetry(logging => logging.AddInMemoryExporter(logRecords)); }); var logger1 = loggerFactory.CreateLogger("OtlpLogExporterTests-A"); @@ -1387,7 +1596,15 @@ public void ValidateInstrumentationScope() var resourceBuilder = ResourceBuilder.CreateEmpty(); var processResource = resourceBuilder.Build().ToOtlpResource(); - var request = logRecordTransformer.BuildExportRequest(processResource, batch); + OtlpCollector.ExportLogsServiceRequest request; + if (useCustomSerializer) + { + request = CreateLogsExportRequest(DefaultSdkLimitOptions, new ExperimentalOptions(), batch, resourceBuilder.Build()); + } + else + { + request = logRecordTransformer.BuildExportRequest(processResource, batch); + } Assert.Single(request.ResourceLogs); @@ -1412,18 +1629,27 @@ public void ValidateInstrumentationScope() logRecordTransformer.Return(request); Assert.Equal(2, OtlpLogRecordTransformer.LogListPool.Count); - request = logRecordTransformer.BuildExportRequest(processResource, batch); + if (useCustomSerializer) + { + request = CreateLogsExportRequest(DefaultSdkLimitOptions, new ExperimentalOptions(), batch, resourceBuilder.Build()); + } + else + { + request = logRecordTransformer.BuildExportRequest(processResource, batch); + + // ScopeLogs will be reused. + Assert.Empty(OtlpLogRecordTransformer.LogListPool); + } Assert.Single(request.ResourceLogs); - - // ScopeLogs will be reused. - Assert.Empty(OtlpLogRecordTransformer.LogListPool); } [Theory] - [InlineData(null)] - [InlineData("logging")] - public void VerifyEnvironmentVariablesTakenFromIConfigurationWhenUsingLoggerFactoryCreate(string optionsName) + [InlineData(null, false)] + [InlineData("logging", false)] + [InlineData(null, true)] + [InlineData("logging", true)] + public void VerifyEnvironmentVariablesTakenFromIConfigurationWhenUsingLoggerFactoryCreate(string? optionsName, bool callUseOpenTelemetry) { RunVerifyEnvironmentVariablesTakenFromIConfigurationTest( optionsName, @@ -1433,7 +1659,10 @@ public void VerifyEnvironmentVariablesTakenFromIConfigurationWhenUsingLoggerFact { configure(logging.Services); - logging.AddOpenTelemetry(o => o.AddOtlpExporter(optionsName, configure: null)); + ConfigureOtlpExporter( + logging, + callUseOpenTelemetry, + name: optionsName); }); return (factory, factory); @@ -1441,9 +1670,11 @@ public void VerifyEnvironmentVariablesTakenFromIConfigurationWhenUsingLoggerFact } [Theory] - [InlineData(null)] - [InlineData("logging")] - public void VerifyEnvironmentVariablesTakenFromIConfigurationWhenUsingLoggingBuilder(string optionsName) + [InlineData(null, false)] + [InlineData("logging", false)] + [InlineData(null, true)] + [InlineData("logging", true)] + public void VerifyEnvironmentVariablesTakenFromIConfigurationWhenUsingLoggingBuilder(string? optionsName, bool callUseOpenTelemetry) { RunVerifyEnvironmentVariablesTakenFromIConfigurationTest( optionsName, @@ -1454,8 +1685,10 @@ public void VerifyEnvironmentVariablesTakenFromIConfigurationWhenUsingLoggingBui configure(services); services.AddLogging( - logging => logging.AddOpenTelemetry(o => - o.AddOtlpExporter(optionsName, configure: null))); + logging => ConfigureOtlpExporter( + logging, + callUseOpenTelemetry, + name: optionsName)); var sp = services.BuildServiceProvider(); @@ -1466,9 +1699,11 @@ public void VerifyEnvironmentVariablesTakenFromIConfigurationWhenUsingLoggingBui } [Theory] - [InlineData("my_instrumentation_scope_name", "my_instrumentation_scope_name")] - [InlineData(null, "")] - public void LogRecordLoggerNameIsExportedWhenUsingBridgeApi(string loggerName, string expectedScopeName) + [InlineData("my_instrumentation_scope_name", "my_instrumentation_scope_name", true)] + [InlineData(null, "", true)] + [InlineData("my_instrumentation_scope_name", "my_instrumentation_scope_name", false)] + [InlineData(null, "", false)] + public void LogRecordLoggerNameIsExportedWhenUsingBridgeApi(string? loggerName, string expectedScopeName, bool useCustomSerializer) { LogRecordAttributeList attributes = default; attributes.Add("name", "tomato"); @@ -1492,9 +1727,17 @@ public void LogRecordLoggerNameIsExportedWhenUsingBridgeApi(string loggerName, s var batch = new Batch(new[] { logRecords[0] }, 1); - var request = otlpLogRecordTransformer.BuildExportRequest( + OtlpCollector.ExportLogsServiceRequest request; + if (useCustomSerializer) + { + request = CreateLogsExportRequest(DefaultSdkLimitOptions, new ExperimentalOptions(), batch, ResourceBuilder.CreateEmpty().Build()); + } + else + { + request = otlpLogRecordTransformer.BuildExportRequest( new Proto.Resource.V1.Resource(), batch); + } Assert.NotNull(request); Assert.Single(request.ResourceLogs); @@ -1504,10 +1747,10 @@ public void LogRecordLoggerNameIsExportedWhenUsingBridgeApi(string loggerName, s } private static void RunVerifyEnvironmentVariablesTakenFromIConfigurationTest( - string optionsName, + string? optionsName, Func, (IDisposable Container, ILoggerFactory LoggerFactory)> createLoggerFactoryFunc) { - var values = new Dictionary() + var values = new Dictionary { [OtlpSpecConfigDefinitions.DefaultEndpointEnvVarName] = "http://test:8888", }; @@ -1577,34 +1820,86 @@ private static void RunVerifyEnvironmentVariablesTakenFromIConfigurationTest( Assert.True(allConfigureDelegateCalled); } - private static OtlpCommon.KeyValue TryGetAttribute(OtlpLogs.LogRecord record, string key) + private static OtlpCommon.KeyValue? TryGetAttribute(OtlpLogs.LogRecord record, string key) { return record.Attributes.FirstOrDefault(att => att.Key == key); } - private static BaseProcessor GetProcessor(OpenTelemetryLoggerProvider provider) - { - var sdkProvider = typeof(OpenTelemetryLoggerProvider).GetField("Provider", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(provider); - - return (BaseProcessor)sdkProvider.GetType().GetProperty("Processor", BindingFlags.Instance | BindingFlags.Public).GetMethod.Invoke(sdkProvider, null); - } - - private sealed class TestOptionsMonitor : IOptionsMonitor + private static void ConfigureOtlpExporter( + ILoggingBuilder builder, + bool callUseOpenTelemetry, + string? name = null, + Action? configureExporter = null, + Action? configureExporterAndProcessor = null, + Action? configureOptions = null, + List? logRecords = null) { - private readonly T instance; - - public TestOptionsMonitor(T instance) + if (callUseOpenTelemetry) { - this.instance = instance; + builder.UseOpenTelemetry( + logging => + { + if (configureExporterAndProcessor != null) + { + logging.AddOtlpExporter(name, configureExporterAndProcessor); + } + else + { + logging.AddOtlpExporter(name, configureExporter); + } + + if (logRecords != null) + { + logging.AddInMemoryExporter(logRecords); + } + }, + options => + { + configureOptions?.Invoke(options); + }); } + else + { +#pragma warning disable CS0618 // Type or member is obsolete + builder.AddOpenTelemetry(options => + { + configureOptions?.Invoke(options); - public T CurrentValue => this.instance; - - public T Get(string name) => this.instance; + if (configureExporterAndProcessor != null) + { + options.AddOtlpExporter(name, configureExporterAndProcessor); + } + else + { + options.AddOtlpExporter(name, configureExporter); + } - public IDisposable OnChange(Action listener) - { - throw new NotImplementedException(); + if (logRecords != null) + { + options.AddInMemoryExporter(logRecords); + } + }); +#pragma warning restore CS0618 // Type or member is obsolete } } + + private static OtlpCollector.ExportLogsServiceRequest CreateLogsExportRequest(SdkLimitOptions sdkOptions, ExperimentalOptions experimentalOptions, in Batch batch, Resource resource) + { + var buffer = new byte[4096]; + var writePosition = ProtobufOtlpLogSerializer.WriteLogsData(buffer, 0, sdkOptions, experimentalOptions, resource, batch); + using var stream = new MemoryStream(buffer, 0, writePosition); + var logsData = OtlpLogs.ResourceLogs.Parser.ParseFrom(stream); + var request = new OtlpCollector.ExportLogsServiceRequest(); + request.ResourceLogs.Add(logsData); + return request; + } + + private static OtlpLogs.LogRecord? ToOtlpLogs(SdkLimitOptions sdkOptions, ExperimentalOptions experimentalOptions, LogRecord logRecord) + { + var buffer = new byte[4096]; + var writePosition = ProtobufOtlpLogSerializer.WriteLogRecord(buffer, 0, sdkOptions, experimentalOptions, logRecord); + using var stream = new MemoryStream(buffer, 0, writePosition); + var scopeLogs = OtlpLogs.ScopeLogs.Parser.ParseFrom(stream); + return scopeLogs.LogRecords.FirstOrDefault(); + } } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs index 07124e63a8..d92f33ee6a 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs @@ -21,10 +21,10 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; [Collection("EnvVars")] public class OtlpMetricsExporterTests : IDisposable { - private static readonly KeyValuePair[] KeyValues = new KeyValuePair[] + private static readonly KeyValuePair[] KeyValues = new KeyValuePair[] { - new KeyValuePair("key1", "value1"), - new KeyValuePair("key2", 123), + new("key1", "value1"), + new("key2", 123), }; public OtlpMetricsExporterTests() @@ -57,14 +57,14 @@ void CheckMetricReaderDefaults() var metricReader = typeof(MetricReader) .Assembly - .GetType("OpenTelemetry.Metrics.MeterProviderSdk") - .GetField("reader", bindingFlags) + .GetType("OpenTelemetry.Metrics.MeterProviderSdk")? + .GetField("reader", bindingFlags)? .GetValue(meterProvider) as PeriodicExportingMetricReader; Assert.NotNull(metricReader); - var exportIntervalMilliseconds = (int)typeof(PeriodicExportingMetricReader) - .GetField("ExportIntervalMilliseconds", bindingFlags) + var exportIntervalMilliseconds = (int?)typeof(PeriodicExportingMetricReader)? + .GetField("ExportIntervalMilliseconds", bindingFlags)? .GetValue(metricReader); Assert.Equal(60000, exportIntervalMilliseconds); @@ -129,8 +129,8 @@ public void UserHttpFactoryCalled() Assert.Equal(2, invocations); } - options.HttpClientFactory = () => null; - Assert.Throws(() => + options.HttpClientFactory = () => throw new NotSupportedException(); + Assert.Throws(() => { using var exporter = new OtlpMetricExporter(options); }); @@ -219,7 +219,7 @@ public void ToOtlpResourceMetricsTest(bool includeServiceNameInResource) [InlineData("test_gauge", null, null, 123L, null, true)] [InlineData("test_gauge", null, null, null, 123.45, true)] [InlineData("test_gauge", "description", "unit", 123L, null)] - public void TestGaugeToOtlpMetric(string name, string description, string unit, long? longValue, double? doubleValue, bool enableExemplars = false) + public void TestGaugeToOtlpMetric(string name, string? description, string? unit, long? longValue, double? doubleValue, bool enableExemplars = false) { var metrics = new List(); @@ -236,7 +236,7 @@ public void TestGaugeToOtlpMetric(string name, string description, string unit, } else { - meter.CreateObservableGauge(name, () => doubleValue.Value, unit, description); + meter.CreateObservableGauge(name, () => doubleValue!.Value, unit, description); } provider.ForceFlush(); @@ -294,7 +294,7 @@ public void TestGaugeToOtlpMetric(string name, string description, string unit, [InlineData("test_counter", null, null, null, 123.45, MetricReaderTemporalityPreference.Delta, false, true)] [InlineData("test_counter", "description", "unit", 123L, null, MetricReaderTemporalityPreference.Cumulative)] [InlineData("test_counter", null, null, 123L, null, MetricReaderTemporalityPreference.Delta, true)] - public void TestCounterToOtlpMetric(string name, string description, string unit, long? longValue, double? doubleValue, MetricReaderTemporalityPreference aggregationTemporality, bool enableKeyValues = false, bool enableExemplars = false) + public void TestCounterToOtlpMetric(string name, string? description, string? unit, long? longValue, double? doubleValue, MetricReaderTemporalityPreference aggregationTemporality, bool enableKeyValues = false, bool enableExemplars = false) { var metrics = new List(); @@ -308,7 +308,7 @@ public void TestCounterToOtlpMetric(string name, string description, string unit }) .Build(); - var attributes = enableKeyValues ? KeyValues : Array.Empty>(); + var attributes = enableKeyValues ? KeyValues : Array.Empty>(); if (longValue.HasValue) { var counter = meter.CreateCounter(name, unit, description); @@ -317,7 +317,7 @@ public void TestCounterToOtlpMetric(string name, string description, string unit else { var counter = meter.CreateCounter(name, unit, description); - counter.Add(doubleValue.Value, attributes); + counter.Add(doubleValue!.Value, attributes); } provider.ForceFlush(); @@ -391,7 +391,7 @@ public void TestCounterToOtlpMetric(string name, string description, string unit [InlineData("test_counter", null, null, null, -123.45, MetricReaderTemporalityPreference.Delta, false, true)] [InlineData("test_counter", "description", "unit", 123L, null, MetricReaderTemporalityPreference.Cumulative)] [InlineData("test_counter", null, null, 123L, null, MetricReaderTemporalityPreference.Delta, true)] - public void TestUpDownCounterToOtlpMetric(string name, string description, string unit, long? longValue, double? doubleValue, MetricReaderTemporalityPreference aggregationTemporality, bool enableKeyValues = false, bool enableExemplars = false) + public void TestUpDownCounterToOtlpMetric(string name, string? description, string? unit, long? longValue, double? doubleValue, MetricReaderTemporalityPreference aggregationTemporality, bool enableKeyValues = false, bool enableExemplars = false) { var metrics = new List(); @@ -405,7 +405,7 @@ public void TestUpDownCounterToOtlpMetric(string name, string description, strin }) .Build(); - var attributes = enableKeyValues ? KeyValues : Array.Empty>(); + var attributes = enableKeyValues ? KeyValues : Array.Empty>(); if (longValue.HasValue) { var counter = meter.CreateUpDownCounter(name, unit, description); @@ -414,7 +414,7 @@ public void TestUpDownCounterToOtlpMetric(string name, string description, strin else { var counter = meter.CreateUpDownCounter(name, unit, description); - counter.Add(doubleValue.Value, attributes); + counter.Add(doubleValue!.Value, attributes); } provider.ForceFlush(); @@ -488,7 +488,7 @@ public void TestUpDownCounterToOtlpMetric(string name, string description, strin [InlineData("test_histogram", null, null, null, 123.45, MetricReaderTemporalityPreference.Delta, false, true)] [InlineData("test_histogram", "description", "unit", 123L, null, MetricReaderTemporalityPreference.Cumulative)] [InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Delta, true)] - public void TestExponentialHistogramToOtlpMetric(string name, string description, string unit, long? longValue, double? doubleValue, MetricReaderTemporalityPreference aggregationTemporality, bool enableKeyValues = false, bool enableExemplars = false) + public void TestExponentialHistogramToOtlpMetric(string name, string? description, string? unit, long? longValue, double? doubleValue, MetricReaderTemporalityPreference aggregationTemporality, bool enableKeyValues = false, bool enableExemplars = false) { var metrics = new List(); @@ -506,7 +506,7 @@ public void TestExponentialHistogramToOtlpMetric(string name, string description }) .Build(); - var attributes = enableKeyValues ? KeyValues : Array.Empty>(); + var attributes = enableKeyValues ? KeyValues : Array.Empty>(); if (longValue.HasValue) { var histogram = meter.CreateHistogram(name, unit, description); @@ -516,7 +516,7 @@ public void TestExponentialHistogramToOtlpMetric(string name, string description else { var histogram = meter.CreateHistogram(name, unit, description); - histogram.Record(doubleValue.Value, attributes); + histogram.Record(doubleValue!.Value, attributes); histogram.Record(0, attributes); } @@ -577,7 +577,7 @@ public void TestExponentialHistogramToOtlpMetric(string name, string description { Assert.Equal(0, dataPoint.Sum); Assert.Null(dataPoint.Negative); - Assert.True(dataPoint.Positive.Offset == 0); + Assert.Equal(0, dataPoint.Positive.Offset); Assert.Empty(dataPoint.Positive.BucketCounts); } } @@ -594,7 +594,7 @@ public void TestExponentialHistogramToOtlpMetric(string name, string description { Assert.Equal(0, dataPoint.Sum); Assert.Null(dataPoint.Negative); - Assert.True(dataPoint.Positive.Offset == 0); + Assert.Equal(0, dataPoint.Positive.Offset); Assert.Empty(dataPoint.Positive.BucketCounts); } } @@ -628,7 +628,7 @@ public void TestExponentialHistogramToOtlpMetric(string name, string description [InlineData("test_histogram", null, null, null, 123.45, MetricReaderTemporalityPreference.Delta, false, true)] [InlineData("test_histogram", "description", "unit", 123L, null, MetricReaderTemporalityPreference.Cumulative)] [InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Delta, true)] - public void TestHistogramToOtlpMetric(string name, string description, string unit, long? longValue, double? doubleValue, MetricReaderTemporalityPreference aggregationTemporality, bool enableKeyValues = false, bool enableExemplars = false) + public void TestHistogramToOtlpMetric(string name, string? description, string? unit, long? longValue, double? doubleValue, MetricReaderTemporalityPreference aggregationTemporality, bool enableKeyValues = false, bool enableExemplars = false) { var metrics = new List(); @@ -642,7 +642,7 @@ public void TestHistogramToOtlpMetric(string name, string description, string un }) .Build(); - var attributes = enableKeyValues ? KeyValues : Array.Empty>(); + var attributes = enableKeyValues ? KeyValues : Array.Empty>(); if (longValue.HasValue) { var histogram = meter.CreateHistogram(name, unit, description); @@ -651,7 +651,7 @@ public void TestHistogramToOtlpMetric(string name, string description, string un else { var histogram = meter.CreateHistogram(name, unit, description); - histogram.Record(doubleValue.Value, attributes); + histogram.Record(doubleValue!.Value, attributes); } provider.ForceFlush(); @@ -732,7 +732,7 @@ public void TestTemporalityPreferenceUsingConfiguration(string configValue, Metr { var testExecuted = false; - var configData = new Dictionary { [OtlpSpecConfigDefinitionTests.MetricsData.TemporalityKeyName] = configValue }; + var configData = new Dictionary { [OtlpSpecConfigDefinitionTests.MetricsData.TemporalityKeyName] = configValue }; var configuration = new ConfigurationBuilder() .AddInMemoryCollection(configData) .Build(); @@ -786,9 +786,9 @@ public void TestTemporalityPreferenceUsingEnvVar(string configValue, MetricReade [InlineData(true, true)] public void ToOtlpExemplarTests(bool enableTagFiltering, bool enableTracing) { - ActivitySource activitySource = null; - Activity activity = null; - TracerProvider tracerProvider = null; + ActivitySource? activitySource = null; + Activity? activity = null; + TracerProvider? tracerProvider = null; using var meter = new Meter(Utils.GetCurrentMethodName()); @@ -823,8 +823,8 @@ public void ToOtlpExemplarTests(bool enableTagFiltering, bool enableTracing) var counterDouble = meter.CreateCounter("testCounterDouble"); var counterLong = meter.CreateCounter("testCounterLong"); - counterDouble.Add(1.18D, new KeyValuePair("key1", "value1")); - counterLong.Add(18L, new KeyValuePair("key1", "value1")); + counterDouble.Add(1.18D, new KeyValuePair("key1", "value1")); + counterLong.Add(18L, new KeyValuePair("key1", "value1")); meterProvider.ForceFlush(); @@ -869,6 +869,7 @@ void AssertExemplars(T value, Metric metric) else { byte[] traceIdBytes = new byte[16]; + Assert.NotNull(activity); activity.TraceId.CopyTo(traceIdBytes); byte[] spanIdBytes = new byte[8]; @@ -914,7 +915,7 @@ public void Dispose() GC.SuppressFinalize(this); } - private static void VerifyExemplars(long? longValue, double? doubleValue, bool enableExemplars, Func getExemplarFunc, T state) + private static void VerifyExemplars(long? longValue, double? doubleValue, bool enableExemplars, Func getExemplarFunc, T state) { var exemplar = getExemplarFunc(state); @@ -928,6 +929,7 @@ private static void VerifyExemplars(long? longValue, double? doubleValue, boo } else { + Assert.NotNull(doubleValue); Assert.Equal(doubleValue.Value, exemplar.AsDouble); } } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpResourceTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpResourceTests.cs index 4a82310103..57b1c58fdf 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpResourceTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpResourceTests.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; +using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; +using OpenTelemetry.Proto.Trace.V1; using OpenTelemetry.Resources; using Xunit; @@ -10,9 +12,11 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; public class OtlpResourceTests { [Theory] - [InlineData(true)] - [InlineData(false)] - public void ToOtlpResourceTest(bool includeServiceNameInResource) + [InlineData(true, false)] + [InlineData(false, false)] + [InlineData(true, true)] + [InlineData(false, true)] + public void ToOtlpResourceTest(bool includeServiceNameInResource, bool useCustomSerializer) { // Targeted test to cover OTel Resource to OTLP Resource // conversion, independent of signals. @@ -23,7 +27,25 @@ public void ToOtlpResourceTest(bool includeServiceNameInResource) } var resource = resourceBuilder.Build(); - var otlpResource = resource.ToOtlpResource(); + Proto.Resource.V1.Resource otlpResource; + + if (useCustomSerializer) + { + byte[] buffer = new byte[1024]; + var writePosition = ProtobufOtlpResourceSerializer.WriteResource(buffer, 0, resource); + + // Deserialize the ResourceSpans and validate the attributes. + using (var stream = new MemoryStream(buffer, 0, writePosition)) + { + var resourceSpans = ResourceSpans.Parser.ParseFrom(stream); + otlpResource = resourceSpans.Resource; + } + } + else + { + otlpResource = resource.ToOtlpResource(); + } + if (includeServiceNameInResource) { Assert.Contains(otlpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.StringValue == "service-name"); diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpRetryTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpRetryTests.cs index 25acb30e3e..23ba18d0f6 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpRetryTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpRetryTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Net; using System.Net.Http.Headers; #if NETFRAMEWORK @@ -263,7 +261,7 @@ 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 || NET6_0_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET yield return new[] { new HttpRetryTestCase("ServiceUnavailable", [new(statusCode: HttpStatusCode.TooManyRequests, throttleDelay: TimeSpan.FromSeconds(1))]) }; #endif diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpSpecConfigDefinitionTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpSpecConfigDefinitionTests.cs index 4e4e546918..7ad28e6752 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpSpecConfigDefinitionTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpSpecConfigDefinitionTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Collections; using Microsoft.Extensions.Configuration; using OpenTelemetry.Metrics; diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTestHelpers.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTestHelpers.cs index f6011fed01..7cc6b10336 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTestHelpers.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTestHelpers.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using Google.Protobuf.Collections; using Xunit; using OtlpCommon = OpenTelemetry.Proto.Common.V1; @@ -10,7 +11,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; internal static class OtlpTestHelpers { public static void AssertOtlpAttributes( - IEnumerable> expected, + IEnumerable> expected, RepeatedField actual) { var expectedAttributes = expected.ToList(); @@ -19,6 +20,7 @@ public static void AssertOtlpAttributes( { var current = expectedAttributes[i].Value; Assert.Equal(expectedAttributes[i].Key, actual[i].Key); + Assert.NotNull(current); if (current.GetType().IsArray) { @@ -90,7 +92,7 @@ public static void AssertOtlpAttributes( Assert.Equal(expectedSize, actual.Count); } - private static void AssertOtlpAttributeValue(object expected, OtlpCommon.AnyValue actual) + private static void AssertOtlpAttributeValue(object? expected, OtlpCommon.AnyValue actual) { switch (expected) { @@ -110,7 +112,7 @@ private static void AssertOtlpAttributeValue(object expected, OtlpCommon.AnyValu Assert.Equal(i, actual.IntValue); break; default: - Assert.Equal(expected.ToString(), actual.StringValue); + Assert.Equal(Convert.ToString(expected, CultureInfo.InvariantCulture), actual.StringValue); break; } } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs index 18434043bc..9d43558d3a 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs @@ -5,6 +5,7 @@ using Google.Protobuf.Collections; using Microsoft.Extensions.DependencyInjection; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; +using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Transmission; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; @@ -63,8 +64,8 @@ public void AddOtlpTraceExporterNamedOptionsSupported() [Fact] public void OtlpExporter_BadArgs() { - TracerProviderBuilder builder = null; - Assert.Throws(() => builder.AddOtlpExporter()); + TracerProviderBuilder? builder = null; + Assert.Throws(() => builder!.AddOtlpExporter()); } [Fact] @@ -98,7 +99,7 @@ public void UserHttpFactoryCalled() Assert.Equal(2, invocations); } - options.HttpClientFactory = () => null; + options.HttpClientFactory = () => null!; Assert.Throws(() => { using var exporter = new OtlpTraceExporter(options); @@ -127,12 +128,14 @@ public void ServiceProviderHttpClientFactoryInvoked() } [Theory] - [InlineData(true)] - [InlineData(false)] - public void ToOtlpResourceSpansTest(bool includeServiceNameInResource) + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(false, false)] + public void ToOtlpResourceSpansTest(bool includeServiceNameInResource, bool useCustomSerializer) { - var evenTags = new[] { new KeyValuePair("k0", "v0") }; - var oddTags = new[] { new KeyValuePair("k1", "v1") }; + var evenTags = new[] { new KeyValuePair("k0", "v0") }; + var oddTags = new[] { new KeyValuePair("k1", "v1") }; var sources = new[] { new ActivitySource("even", "2.4.6"), @@ -163,7 +166,7 @@ public void ToOtlpResourceSpansTest(bool includeServiceNameInResource) var activityKind = isEven ? ActivityKind.Client : ActivityKind.Server; var activityTags = isEven ? evenTags : oddTags; - using Activity activity = source.StartActivity($"span-{i}", activityKind, parentContext: default, activityTags); + using Activity? activity = source.StartActivity($"span-{i}", activityKind, parentContext: default, activityTags); } Assert.Equal(10, exportedItems.Count); @@ -174,7 +177,14 @@ void RunTest(SdkLimitOptions sdkOptions, Batch batch) { var request = new OtlpCollector.ExportTraceServiceRequest(); - request.AddBatch(sdkOptions, resourceBuilder.Build().ToOtlpResource(), batch); + if (useCustomSerializer) + { + request = CreateTraceExportRequest(sdkOptions, batch, resourceBuilder.Build()); + } + else + { + request.AddBatch(sdkOptions, resourceBuilder.Build().ToOtlpResource(), batch); + } Assert.Single(request.ResourceSpans); var otlpResource = request.ResourceSpans.First().Resource; @@ -221,8 +231,180 @@ void RunTest(SdkLimitOptions sdkOptions, Batch batch) } } - [Fact] - public void SpanLimitsTest() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ScopeAttributesRemainConsistentAcrossMultipleBatches(bool useCustomSerializer) + { + var activitySourceTags = new TagList + { + new("k0", "v0"), + }; + + using var activitySourceWithTags = new ActivitySource($"{nameof(this.ScopeAttributesRemainConsistentAcrossMultipleBatches)}_WithTags", "1.1.1.3", activitySourceTags); + using var activitySourceWithoutTags = new ActivitySource($"{nameof(this.ScopeAttributesRemainConsistentAcrossMultipleBatches)}_WithoutTags", "1.1.1.4"); + + var resourceBuilder = ResourceBuilder.CreateDefault(); + + var exportedItems = new List(); + var builder = Sdk.CreateTracerProviderBuilder() + .SetResourceBuilder(resourceBuilder) + .AddSource(activitySourceWithTags.Name) + .AddSource(activitySourceWithoutTags.Name) + .AddProcessor(new SimpleActivityExportProcessor(new InMemoryExporter(exportedItems))); + + using var openTelemetrySdk = builder.Build(); + + var parentActivity = activitySourceWithTags.StartActivity("parent", ActivityKind.Server, default(ActivityContext)); + var nestedChildActivity = activitySourceWithTags.StartActivity("nested-child", ActivityKind.Client); + parentActivity?.Dispose(); + nestedChildActivity?.Dispose(); + + Assert.Equal(2, exportedItems.Count); + var batch = new Batch(exportedItems.ToArray(), exportedItems.Count); + RunTest(DefaultSdkLimitOptions, batch, activitySourceWithTags); + + exportedItems.Clear(); + + var parentActivityNoTags = activitySourceWithoutTags.StartActivity("parent", ActivityKind.Server, default(ActivityContext)); + parentActivityNoTags?.Dispose(); + + Assert.Single(exportedItems); + batch = new Batch(exportedItems.ToArray(), exportedItems.Count); + RunTest(DefaultSdkLimitOptions, batch, activitySourceWithoutTags); + + void RunTest(SdkLimitOptions sdkOptions, Batch batch, ActivitySource activitySource) + { + var request = new OtlpCollector.ExportTraceServiceRequest(); + + if (useCustomSerializer) + { + request = CreateTraceExportRequest(sdkOptions, batch, resourceBuilder.Build()); + } + else + { + request.AddBatch(sdkOptions, resourceBuilder.Build().ToOtlpResource(), batch); + } + + var resourceSpans = request.ResourceSpans.First(); + Assert.NotNull(request.ResourceSpans.First()); + + var scopeSpans = resourceSpans.ScopeSpans.First(); + Assert.NotNull(scopeSpans); + + var scope = scopeSpans.Scope; + Assert.NotNull(scope); + + Assert.Equal(activitySource.Name, scope.Name); + Assert.Equal(activitySource.Version, scope.Version); + Assert.Equal(activitySource.Tags?.Count() ?? 0, scope.Attributes.Count); + + foreach (var tag in activitySource.Tags ?? []) + { + Assert.Contains(scope.Attributes, (kvp) => kvp.Key == tag.Key && kvp.Value.StringValue == (string?)tag.Value); + } + + // Return and re-add batch to simulate reuse + request.Return(); + request.AddBatch(DefaultSdkLimitOptions, ResourceBuilder.CreateDefault().Build().ToOtlpResource(), batch); + + resourceSpans = request.ResourceSpans.First(); + scopeSpans = resourceSpans.ScopeSpans.First(); + scope = scopeSpans.Scope; + + Assert.Equal(activitySource.Name, scope.Name); + Assert.Equal(activitySource.Version, scope.Version); + Assert.Equal(activitySource.Tags?.Count() ?? 0, scope.Attributes.Count); + + foreach (var tag in activitySource.Tags ?? []) + { + Assert.Contains(scope.Attributes, (kvp) => kvp.Key == tag.Key && kvp.Value.StringValue == (string?)tag.Value); + } + + // Return and re-add batch to simulate reuse + request.Return(); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ScopeAttributesLimitsTest(bool useCustomSerializer) + { + var sdkOptions = new SdkLimitOptions() + { + AttributeValueLengthLimit = 4, + AttributeCountLimit = 3, + }; + + // ActivitySource Tags are sorted in .NET. + var activitySourceTags = new TagList + { + new("1_TruncatedSourceTag", "12345"), + new("2_TruncatedSourceStringArray", new string?[] { "12345", "1234", string.Empty, null }), + new("3_TruncatedSourceObjectTag", new object()), + new("4_OneSourceTagTooMany", 1), + }; + + var resourceBuilder = ResourceBuilder.CreateDefault(); + + using var activitySource = new ActivitySource(name: nameof(this.ScopeAttributesLimitsTest), tags: activitySourceTags); + + var exportedItems = new List(); + var builder = Sdk.CreateTracerProviderBuilder() + .SetResourceBuilder(resourceBuilder) + .AddSource(activitySource.Name) + .AddProcessor(new SimpleActivityExportProcessor(new InMemoryExporter(exportedItems))); + + using var openTelemetrySdk = builder.Build(); + + var activity = activitySource.StartActivity("parent", ActivityKind.Server, default(ActivityContext)); + activity?.Dispose(); + + Assert.Single(exportedItems); + var batch = new Batch(exportedItems.ToArray(), exportedItems.Count); + RunTest(sdkOptions, batch); + + void RunTest(SdkLimitOptions sdkOptions, Batch batch) + { + var request = new OtlpCollector.ExportTraceServiceRequest(); + + if (useCustomSerializer) + { + request = CreateTraceExportRequest(sdkOptions, batch, resourceBuilder.Build()); + } + else + { + request.AddBatch(sdkOptions, resourceBuilder.Build().ToOtlpResource(), batch); + } + + var resourceSpans = request.ResourceSpans.First(); + Assert.NotNull(request.ResourceSpans.First()); + + var scopeSpans = resourceSpans.ScopeSpans.First(); + Assert.NotNull(scopeSpans); + + var scope = scopeSpans.Scope; + Assert.NotNull(scope); + + 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); + Assert.Equal(new object().ToString()!.Substring(0, 4), scope.Attributes[2].Value.StringValue); + + // Return and re-add batch to simulate reuse + if (!useCustomSerializer) + { + request.Return(); + } + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SpanLimitsTest(bool useCustomSerializer) { var sdkOptions = new SdkLimitOptions() { @@ -232,12 +414,12 @@ public void SpanLimitsTest() SpanLinkCountLimit = 1, }; - var tags = new ActivityTagsCollection() + var tags = new ActivityTagsCollection { - new KeyValuePair("TruncatedTag", "12345"), - new KeyValuePair("TruncatedStringArray", new string[] { "12345", "1234", string.Empty, null }), - new KeyValuePair("TruncatedObjectTag", new object()), - new KeyValuePair("OneTagTooMany", 1), + new("TruncatedTag", "12345"), + new("TruncatedStringArray", new string?[] { "12345", "1234", string.Empty, null }), + new("TruncatedObjectTag", new object()), + new("OneTagTooMany", 1), }; var links = new[] @@ -249,79 +431,65 @@ public void SpanLimitsTest() using var activitySource = new ActivitySource(nameof(this.SpanLimitsTest)); using var activity = activitySource.StartActivity("root", ActivityKind.Server, default(ActivityContext), tags, links); + Assert.NotNull(activity); + var event1 = new ActivityEvent("Event", DateTime.UtcNow, tags); var event2 = new ActivityEvent("OneEventTooMany", DateTime.Now, tags); activity.AddEvent(event1); activity.AddEvent(event2); - var otlpSpan = activity.ToOtlpSpan(sdkOptions); + var otlpSpan = useCustomSerializer ? ToOtlpSpan(sdkOptions, activity) : activity.ToOtlpSpan(sdkOptions); Assert.NotNull(otlpSpan); Assert.Equal(3, otlpSpan.Attributes.Count); Assert.Equal(1u, otlpSpan.DroppedAttributesCount); Assert.Equal("1234", otlpSpan.Attributes[0].Value.StringValue); - ArrayValueAsserts(otlpSpan.Attributes[1].Value.ArrayValue.Values); - Assert.Equal(new object().ToString().Substring(0, 4), otlpSpan.Attributes[2].Value.StringValue); + this.ArrayValueAsserts(otlpSpan.Attributes[1].Value.ArrayValue.Values); + Assert.Equal(new object().ToString()!.Substring(0, 4), otlpSpan.Attributes[2].Value.StringValue); Assert.Single(otlpSpan.Events); Assert.Equal(1u, otlpSpan.DroppedEventsCount); 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); - ArrayValueAsserts(otlpSpan.Events[0].Attributes[1].Value.ArrayValue.Values); - Assert.Equal(new object().ToString().Substring(0, 4), otlpSpan.Events[0].Attributes[2].Value.StringValue); + this.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); Assert.Equal(1u, otlpSpan.DroppedLinksCount); 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); - ArrayValueAsserts(otlpSpan.Links[0].Attributes[1].Value.ArrayValue.Values); - Assert.Equal(new object().ToString().Substring(0, 4), otlpSpan.Links[0].Attributes[2].Value.StringValue); - - void ArrayValueAsserts(RepeatedField values) - { - var expectedStringArray = new string[] { "1234", "1234", string.Empty, null }; - for (var i = 0; i < expectedStringArray.Length; ++i) - { - var expectedValue = expectedStringArray[i]; - var expectedValueCase = expectedValue != null - ? OtlpCommon.AnyValue.ValueOneofCase.StringValue - : OtlpCommon.AnyValue.ValueOneofCase.None; - - var actual = values[i]; - Assert.Equal(expectedValueCase, actual.ValueCase); - if (expectedValueCase != OtlpCommon.AnyValue.ValueOneofCase.None) - { - Assert.Equal(expectedValue, actual.StringValue); - } - } - } + this.ArrayValueAsserts(otlpSpan.Links[0].Attributes[1].Value.ArrayValue.Values); + Assert.Equal(new object().ToString()!.Substring(0, 4), otlpSpan.Links[0].Attributes[2].Value.StringValue); } - [Fact] - public void ToOtlpSpanTest() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ToOtlpSpanTest(bool useCustomSerializer) { using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest)); using var rootActivity = activitySource.StartActivity("root", ActivityKind.Producer); - var attributes = new List> - { - new KeyValuePair("bool", true), - new KeyValuePair("long", 1L), - new KeyValuePair("string", "text"), - new KeyValuePair("double", 3.14), - new KeyValuePair("int", 1), - new KeyValuePair("datetime", DateTime.UtcNow), - new KeyValuePair("bool_array", new bool[] { true, false }), - new KeyValuePair("int_array", new int[] { 1, 2 }), - new KeyValuePair("double_array", new double[] { 1.0, 2.09 }), - new KeyValuePair("string_array", new string[] { "a", "b" }), - new KeyValuePair("datetime_array", new DateTime[] { DateTime.UtcNow, DateTime.Now }), + var attributes = new List> + { + new("bool", true), + new("long", 1L), + new("string", "text"), + 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("datetime_array", new DateTime[] { DateTime.UtcNow, DateTime.Now }), }; + Assert.NotNull(rootActivity); foreach (var kvp in attributes) { rootActivity.SetTag(kvp.Key, kvp.Value); @@ -342,7 +510,7 @@ public void ToOtlpSpanTest() rootActivity.TraceId.CopyTo(traceIdSpan); var traceId = traceIdSpan.ToArray(); - var otlpSpan = rootActivity.ToOtlpSpan(DefaultSdkLimitOptions); + var otlpSpan = useCustomSerializer ? ToOtlpSpan(DefaultSdkLimitOptions, rootActivity) : rootActivity.ToOtlpSpan(DefaultSdkLimitOptions); Assert.NotNull(otlpSpan); Assert.Equal("root", otlpSpan.Name); @@ -359,16 +527,18 @@ public void ToOtlpSpanTest() var expectedEndTimeUnixNano = expectedStartTimeUnixNano + (duration.TotalMilliseconds * 1_000_000); Assert.Equal(expectedEndTimeUnixNano, otlpSpan.EndTimeUnixNano); - var childLinks = new List { new ActivityLink(rootActivity.Context, new ActivityTagsCollection(attributes)) }; + var childLinks = new List { new(rootActivity.Context, new ActivityTagsCollection(attributes)) }; var childActivity = activitySource.StartActivity( "child", ActivityKind.Client, rootActivity.Context, links: childLinks); - childActivity.SetStatus(Status.Error); + Assert.NotNull(childActivity); + + childActivity.SetStatus(ActivityStatusCode.Error); - var childEvents = new List { new ActivityEvent("e0"), new ActivityEvent("e1", default, new ActivityTagsCollection(attributes)) }; + var childEvents = new List { new("e0"), new("e1", default, new ActivityTagsCollection(attributes)) }; childActivity.AddEvent(childEvents[0]); childActivity.AddEvent(childEvents[1]); @@ -376,7 +546,7 @@ public void ToOtlpSpanTest() rootActivity.Context.SpanId.CopyTo(parentIdSpan); var parentId = parentIdSpan.ToArray(); - otlpSpan = childActivity.ToOtlpSpan(DefaultSdkLimitOptions); + otlpSpan = useCustomSerializer ? ToOtlpSpan(DefaultSdkLimitOptions, childActivity) : childActivity.ToOtlpSpan(DefaultSdkLimitOptions); Assert.NotNull(otlpSpan); Assert.Equal("child", otlpSpan.Name); @@ -384,7 +554,8 @@ public void ToOtlpSpanTest() Assert.Equal(traceId, otlpSpan.TraceId); Assert.Equal(parentId, otlpSpan.ParentSpanId); - // Assert.Equal(OtlpTrace.Status.Types.StatusCode.NotFound, otlpSpan.Status.Code); + 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.Empty(otlpSpan.Attributes); @@ -400,7 +571,9 @@ public void ToOtlpSpanTest() Assert.Equal(childLinks.Count, otlpSpan.Links.Count); for (var i = 0; i < childLinks.Count; i++) { - OtlpTestHelpers.AssertOtlpAttributes(childLinks[i].Tags.ToList(), otlpSpan.Links[i].Attributes); + var tags = childLinks[i].Tags; + Assert.NotNull(tags); + OtlpTestHelpers.AssertOtlpAttributes(tags, otlpSpan.Links[i].Attributes); } var flags = (OtlpTrace.SpanFlags)otlpSpan.Flags; @@ -408,18 +581,20 @@ public void ToOtlpSpanTest() Assert.False(flags.HasFlag(OtlpTrace.SpanFlags.ContextIsRemoteMask)); } - [Fact] - public void ToOtlpSpanActivitiesWithNullArrayTest() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ToOtlpSpanActivitiesWithNullArrayTest(bool useCustomSerializer) { using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest)); using var rootActivity = activitySource.StartActivity("root", ActivityKind.Client); Assert.NotNull(rootActivity); - var stringArr = new string[] { "test", string.Empty, null }; + var stringArr = new string?[] { "test", string.Empty, null }; rootActivity.SetTag("stringArray", stringArr); - var otlpSpan = rootActivity.ToOtlpSpan(DefaultSdkLimitOptions); + var otlpSpan = useCustomSerializer ? ToOtlpSpan(DefaultSdkLimitOptions, rootActivity) : rootActivity.ToOtlpSpan(DefaultSdkLimitOptions); Assert.NotNull(otlpSpan); @@ -432,17 +607,21 @@ public void ToOtlpSpanActivitiesWithNullArrayTest() } [Theory] - [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.")] - public void ToOtlpSpanNativeActivityStatusTest(ActivityStatusCode expectedStatusCode, string statusDescription) + [InlineData(ActivityStatusCode.Unset, "Description will be ignored if status is Unset.", true)] + [InlineData(ActivityStatusCode.Ok, "Description will be ignored if status is Okay.", true)] + [InlineData(ActivityStatusCode.Error, "Description will be kept if status is Error.", true)] + [InlineData(ActivityStatusCode.Unset, "Description will be ignored if status is Unset.", false)] + [InlineData(ActivityStatusCode.Ok, "Description will be ignored if status is Okay.", false)] + [InlineData(ActivityStatusCode.Error, "Description will be kept if status is Error.", false)] + public void ToOtlpSpanNativeActivityStatusTest(ActivityStatusCode expectedStatusCode, string statusDescription, bool useCustomSerializer) { using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest)); using var activity = activitySource.StartActivity("Name"); + Assert.NotNull(activity); activity.SetStatus(expectedStatusCode, statusDescription); - var otlpSpan = activity.ToOtlpSpan(DefaultSdkLimitOptions); - + var otlpSpan = useCustomSerializer ? ToOtlpSpan(DefaultSdkLimitOptions, activity) : activity.ToOtlpSpan(DefaultSdkLimitOptions); + Assert.NotNull(otlpSpan); if (expectedStatusCode == ActivityStatusCode.Unset) { Assert.Null(otlpSpan.Status); @@ -467,15 +646,18 @@ public void ToOtlpSpanNativeActivityStatusTest(ActivityStatusCode expectedStatus [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.")] + [Obsolete("Remove when ActivityExtensions status APIs are removed")] public void ToOtlpSpanStatusTagTest(StatusCode expectedStatusCode, string statusCodeTagValue, string statusDescription) { using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest)); using var activity = activitySource.StartActivity("Name"); + Assert.NotNull(activity); activity.SetTag(SpanAttributeConstants.StatusCodeKey, statusCodeTagValue); activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, statusDescription); var otlpSpan = activity.ToOtlpSpan(DefaultSdkLimitOptions); + Assert.NotNull(otlpSpan); Assert.NotNull(otlpSpan.Status); Assert.Equal((int)expectedStatusCode, (int)otlpSpan.Status.Code); @@ -493,65 +675,78 @@ public void ToOtlpSpanStatusTagTest(StatusCode expectedStatusCode, string status [InlineData(StatusCode.Unset, "uNsET")] [InlineData(StatusCode.Ok, "oK")] [InlineData(StatusCode.Error, "ERROR")] + [Obsolete("Remove when ActivityExtensions status APIs are removed")] public void ToOtlpSpanStatusTagIsCaseInsensitiveTest(StatusCode expectedStatusCode, string statusCodeTagValue) { using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest)); using var activity = activitySource.StartActivity("Name"); + Assert.NotNull(activity); activity.SetTag(SpanAttributeConstants.StatusCodeKey, statusCodeTagValue); var otlpSpan = activity.ToOtlpSpan(DefaultSdkLimitOptions); + Assert.NotNull(otlpSpan); Assert.NotNull(otlpSpan.Status); Assert.Equal((int)expectedStatusCode, (int)otlpSpan.Status.Code); } [Fact] + [Obsolete("Remove when ActivityExtensions status APIs are removed")] public void ToOtlpSpanActivityStatusTakesPrecedenceOverStatusTagsWhenActivityStatusCodeIsOk() { using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest)); using var activity = activitySource.StartActivity("Name"); - const string TagDescriptionOnError = "Description when TagStatusCode is Error."; + const string tagDescriptionOnError = "Description when TagStatusCode is Error."; + Assert.NotNull(activity); activity.SetStatus(ActivityStatusCode.Ok); activity.SetTag(SpanAttributeConstants.StatusCodeKey, "ERROR"); - activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, TagDescriptionOnError); + activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, tagDescriptionOnError); var otlpSpan = activity.ToOtlpSpan(DefaultSdkLimitOptions); + Assert.NotNull(otlpSpan); Assert.NotNull(otlpSpan.Status); Assert.Equal((int)ActivityStatusCode.Ok, (int)otlpSpan.Status.Code); Assert.Empty(otlpSpan.Status.Message); } [Fact] + [Obsolete("Remove when ActivityExtensions status APIs are removed")] public void ToOtlpSpanActivityStatusTakesPrecedenceOverStatusTagsWhenActivityStatusCodeIsError() { using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest)); using var activity = activitySource.StartActivity("Name"); - const string StatusDescriptionOnError = "Description when ActivityStatusCode is Error."; - activity.SetStatus(ActivityStatusCode.Error, StatusDescriptionOnError); + const string statusDescriptionOnError = "Description when ActivityStatusCode is Error."; + Assert.NotNull(activity); + activity.SetStatus(ActivityStatusCode.Error, statusDescriptionOnError); activity.SetTag(SpanAttributeConstants.StatusCodeKey, "OK"); var otlpSpan = activity.ToOtlpSpan(DefaultSdkLimitOptions); + Assert.NotNull(otlpSpan); Assert.NotNull(otlpSpan.Status); Assert.Equal((int)ActivityStatusCode.Error, (int)otlpSpan.Status.Code); - Assert.Equal(StatusDescriptionOnError, otlpSpan.Status.Message); + Assert.Equal(statusDescriptionOnError, otlpSpan.Status.Message); } [Theory] - [InlineData(true)] - [InlineData(false)] - public void ToOtlpSpanTraceStateTest(bool traceStateWasSet) + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(false, false)] + public void ToOtlpSpanTraceStateTest(bool traceStateWasSet, bool useCustomSerializer) { using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest)); using var activity = activitySource.StartActivity("Name"); + Assert.NotNull(activity); string tracestate = "a=b;c=d"; if (traceStateWasSet) { activity.TraceStateString = tracestate; } - var otlpSpan = activity.ToOtlpSpan(DefaultSdkLimitOptions); + var otlpSpan = useCustomSerializer ? ToOtlpSpan(DefaultSdkLimitOptions, activity) : activity.ToOtlpSpan(DefaultSdkLimitOptions); + Assert.NotNull(otlpSpan); if (traceStateWasSet) { @@ -571,6 +766,7 @@ public void ToOtlpSpanPeerServiceTest() using var rootActivity = activitySource.StartActivity("root", ActivityKind.Client); + Assert.NotNull(rootActivity); rootActivity.SetTag(SemanticConventions.AttributeHttpHost, "opentelemetry.io"); var otlpSpan = rootActivity.ToOtlpSpan(DefaultSdkLimitOptions); @@ -626,7 +822,7 @@ public void Shutdown_ClientShutdownIsCalled() var exporterOptions = new OtlpExporterOptions(); var transmissionHandler = new OtlpExporterTransmissionHandler(exportClientMock, exporterOptions.TimeoutMilliseconds); - var exporter = new OtlpTraceExporter(new OtlpExporterOptions(), DefaultSdkLimitOptions, DefaultExperimentalOptions, transmissionHandler); + using var exporter = new OtlpTraceExporter(new OtlpExporterOptions(), DefaultSdkLimitOptions, DefaultExperimentalOptions, transmissionHandler); exporter.Shutdown(); Assert.True(exportClientMock.ShutdownCalled); @@ -641,7 +837,7 @@ public void Null_BatchExportProcessorOptions_SupportedTest() { o.Protocol = OtlpExportProtocol.HttpProtobuf; o.ExportProcessorType = ExportProcessorType.Batch; - o.BatchExportProcessorOptions = null; + o.BatchExportProcessorOptions = null!; }); } @@ -650,8 +846,8 @@ public void NonnamedOptionsMutateSharedInstanceTest() { var testOptionsInstance = new OtlpExporterOptions(); - OtlpExporterOptions tracerOptions = null; - OtlpExporterOptions meterOptions = null; + OtlpExporterOptions? tracerOptions = null; + OtlpExporterOptions? meterOptions = null; var services = new ServiceCollection(); @@ -695,8 +891,8 @@ public void NonnamedOptionsMutateSharedInstanceTest() [Fact] public void NamedOptionsMutateSeparateInstancesTest() { - OtlpExporterOptions tracerOptions = null; - OtlpExporterOptions meterOptions = null; + OtlpExporterOptions? tracerOptions = null; + OtlpExporterOptions? meterOptions = null; var services = new ServiceCollection(); @@ -738,11 +934,15 @@ public void NamedOptionsMutateSeparateInstancesTest() } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void SpanFlagsTest(bool isRecorded, bool isRemote) + [InlineData(true, true, true)] + [InlineData(true, false, true)] + [InlineData(false, true, true)] + [InlineData(false, false, true)] + [InlineData(true, true, false)] + [InlineData(true, false, false)] + [InlineData(false, true, false)] + [InlineData(false, false, false)] + public void SpanFlagsTest(bool isRecorded, bool isRemote, bool useCustomSerializer) { using var activitySource = new ActivitySource(nameof(this.SpanFlagsTest)); @@ -753,9 +953,11 @@ public void SpanFlagsTest(bool isRecorded, bool isRemote) isRemote: isRemote); using var rootActivity = activitySource.StartActivity("root", ActivityKind.Server, ctx); + Assert.NotNull(rootActivity); - var otlpSpan = rootActivity.ToOtlpSpan(DefaultSdkLimitOptions); + var otlpSpan = useCustomSerializer ? ToOtlpSpan(DefaultSdkLimitOptions, rootActivity) : rootActivity.ToOtlpSpan(DefaultSdkLimitOptions); + Assert.NotNull(otlpSpan); var flags = (OtlpTrace.SpanFlags)otlpSpan.Flags; ActivityTraceFlags traceFlags = (ActivityTraceFlags)(flags & OtlpTrace.SpanFlags.TraceFlagsMask); @@ -782,11 +984,15 @@ public void SpanFlagsTest(bool isRecorded, bool isRemote) } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void SpanLinkFlagsTest(bool isRecorded, bool isRemote) + [InlineData(true, true, true)] + [InlineData(true, false, true)] + [InlineData(false, true, true)] + [InlineData(false, false, true)] + [InlineData(true, true, false)] + [InlineData(true, false, false)] + [InlineData(false, true, false)] + [InlineData(false, false, false)] + public void SpanLinkFlagsTest(bool isRecorded, bool isRemote, bool useCustomSerializer) { using var activitySource = new ActivitySource(nameof(this.SpanLinkFlagsTest)); @@ -802,9 +1008,11 @@ public void SpanLinkFlagsTest(bool isRecorded, bool isRemote) }; using var rootActivity = activitySource.StartActivity("root", ActivityKind.Server, default(ActivityContext), links: links); + Assert.NotNull(rootActivity); - var otlpSpan = rootActivity.ToOtlpSpan(DefaultSdkLimitOptions); + var otlpSpan = useCustomSerializer ? ToOtlpSpan(DefaultSdkLimitOptions, rootActivity) : rootActivity.ToOtlpSpan(DefaultSdkLimitOptions); + Assert.NotNull(otlpSpan); var spanLink = Assert.Single(otlpSpan.Links); var flags = (OtlpTrace.SpanFlags)spanLink.Flags; @@ -831,4 +1039,43 @@ public void SpanLinkFlagsTest(bool isRecorded, bool isRemote) Assert.False(flags.HasFlag(OtlpTrace.SpanFlags.ContextIsRemoteMask)); } } + + private static OtlpTrace.Span? ToOtlpSpan(SdkLimitOptions sdkOptions, Activity activity) + { + var buffer = new byte[4096]; + var writePosition = ProtobufOtlpTraceSerializer.WriteSpan(buffer, 0, sdkOptions, activity); + using var stream = new MemoryStream(buffer, 0, writePosition); + var scopeSpans = OtlpTrace.ScopeSpans.Parser.ParseFrom(stream); + return scopeSpans.Spans.FirstOrDefault(); + } + + private static OtlpCollector.ExportTraceServiceRequest CreateTraceExportRequest(SdkLimitOptions sdkOptions, in Batch batch, Resource resource) + { + var buffer = new byte[4096]; + var writePosition = ProtobufOtlpTraceSerializer.WriteTraceData(buffer, 0, sdkOptions, resource, batch); + using var stream = new MemoryStream(buffer, 0, writePosition); + var tracesData = OtlpTrace.ResourceSpans.Parser.ParseFrom(stream); + var request = new OtlpCollector.ExportTraceServiceRequest(); + request.ResourceSpans.Add(tracesData); + return request; + } + + private void ArrayValueAsserts(RepeatedField values) + { + var expectedStringArray = new string?[] { "1234", "1234", string.Empty, null }; + for (var i = 0; i < expectedStringArray.Length; ++i) + { + var expectedValue = expectedStringArray[i]; + var expectedValueCase = expectedValue != null + ? OtlpCommon.AnyValue.ValueOneofCase.StringValue + : OtlpCommon.AnyValue.ValueOneofCase.None; + + var actual = values[i]; + Assert.Equal(expectedValueCase, actual.ValueCase); + if (expectedValueCase != OtlpCommon.AnyValue.ValueOneofCase.None) + { + Assert.Equal(expectedValue, actual.StringValue); + } + } + } } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/SdkLimitOptionsTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/SdkLimitOptionsTests.cs index dc36def466..0a737ff143 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/SdkLimitOptionsTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/SdkLimitOptionsTests.cs @@ -135,7 +135,7 @@ public void SpanAttributeCountLimitFallback() [Fact] public void SdkLimitOptionsUsingIConfiguration() { - var values = new Dictionary + var values = new Dictionary { ["OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT"] = "23", ["OTEL_ATTRIBUTE_COUNT_LIMIT"] = "24", diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestExportClient.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestExportClient.cs index eab9178db4..bbecfa2fa3 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestExportClient.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestExportClient.cs @@ -32,7 +32,7 @@ public bool Shutdown(int timeoutMilliseconds) private class TestExportClientResponse : ExportClientResponse { - public TestExportClientResponse(bool success, DateTime deadline, Exception exception) + 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 a15775cb1a..2e5d6ede2b 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestHttpMessageHandler.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestHttpMessageHandler.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if !NET6_0_OR_GREATER +#if !NET using System.Net.Http; #endif @@ -9,18 +9,18 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; internal class TestHttpMessageHandler : HttpMessageHandler { - public HttpRequestMessage HttpRequestMessage { get; private set; } + public HttpRequestMessage? HttpRequestMessage { get; private set; } - public byte[] HttpRequestContent { get; private set; } + public byte[]? HttpRequestContent { get; private set; } public virtual HttpResponseMessage InternalSend(HttpRequestMessage request, CancellationToken cancellationToken) { this.HttpRequestMessage = request; - this.HttpRequestContent = request.Content.ReadAsByteArrayAsync().Result; + this.HttpRequestContent = request.Content!.ReadAsByteArrayAsync().Result; return new HttpResponseMessage(); } -#if NET6_0_OR_GREATER +#if NET protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken) { return this.InternalSend(request, cancellationToken); diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/UseOtlpExporterExtensionTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/UseOtlpExporterExtensionTests.cs index 8a03a8e47b..467ed6c0c2 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/UseOtlpExporterExtensionTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/UseOtlpExporterExtensionTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/gen_test_cert.ps1 b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/gen_test_cert.ps1 new file mode 100644 index 0000000000..d9443ca85f --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/gen_test_cert.ps1 @@ -0,0 +1,90 @@ +using namespace System.Security.Cryptography; +using namespace System.Security.Cryptography.X509Certificates; + +param ( + [string] $OutDir +) + +function Write-Certificate { + param ( + [X509Certificate2] $Cert, + [string] $Name, + [string] $Dir + ) + + # write cert content + $certPem = $Cert.ExportCertificatePem(); + $certPemPath = Join-Path $Dir -ChildPath "$Name-cert.pem"; + [System.IO.File]::WriteAllText($certPemPath, $certPem); + + # write pkey + [AsymmetricAlgorithm] $pkey = [RSACertificateExtensions]::GetRSAPrivateKey($Cert); + [string] $pkeyPem = $null; + + if ($null -ne $pkey) { + $pkeyPem = $pkey.ExportRSAPrivateKeyPem(); + } + + if ($null -eq $pkey) { + $pkey = [ECDsaCertificateExtensions]::GetECDsaPrivateKey($Cert); + $pkeyPem = $pkey.ExportECPrivateKeyPem(); + } + + if ($null -eq $pkeyPem) { + return; + } + + + $pKeyPath = Join-Path $Dir -ChildPath "$Name-key.pem"; + [System.IO.File]::WriteAllText($pKeyPath, $pkeyPem); +} + +$ca = New-SelfSignedCertificate -CertStoreLocation 'Cert:\CurrentUser\My' ` + -DnsName "otel-test-ca" ` + -NotAfter (Get-Date).AddYears(20) ` + -FriendlyName "otel-test-ca" ` + -KeyAlgorithm ECDSA_nistP256 ` + -KeyExportPolicy Exportable ` + -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature; + + +try { + Write-Certificate -Cert $ca -Name "otel-test-ca" -Dir $OutDir; + $serverCert = New-SelfSignedCertificate -CertStoreLocation 'Cert:\CurrentUser\My' ` + -DnsName "otel-collector" ` + -Signer $ca ` + -NotAfter (Get-Date).AddYears(20) ` + -FriendlyName "otel-test-server" ` + -KeyAlgorithm ECDSA_nistP256 ` + -KeyUsageProperty All ` + -KeyExportPolicy Exportable ` + -KeyUsage CertSign, CRLSign, DigitalSignature ` + -TextExtension @("2.5.29.19={text}CA=1&pathlength=1", "2.5.29.37={text}1.3.6.1.5.5.7.3.1"); + + try { + Write-Certificate -Cert $serverCert -Name "otel-test-server" -Dir $OutDir; + + $clientCert = New-SelfSignedCertificate -CertStoreLocation 'Cert:\CurrentUser\My' ` + -DnsName "otel-test-client" ` + -Signer $ca ` + -NotAfter (Get-Date).AddYears(20) ` + -FriendlyName "otel-test-client" ` + -KeyAlgorithm ECDSA_nistP256 ` + -KeyUsageProperty All ` + -KeyExportPolicy Exportable ` + -KeyUsage CertSign, CRLSign, DigitalSignature ` + -TextExtension @("2.5.29.19={text}CA=1&pathlength=1", "2.5.29.37={text}1.3.6.1.5.5.7.3.2"); + try { + Write-Certificate -Cert $clientCert -Name "otel-test-client" -Dir $OutDir; + } + finally { + Get-Item -Path "Cert:\CurrentUser\My\$($clientCert.Thumbprint)" | Remove-Item; + } + } + finally { + Get-Item -Path "Cert:\CurrentUser\My\$($serverCert.Thumbprint)" | Remove-Item; + } +} +finally { + Get-Item -Path "Cert:\CurrentUser\My\$($ca.Thumbprint)" | Remove-Item; +} diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/gen_test_cert.sh b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/gen_test_cert.sh new file mode 100644 index 0000000000..bd129a4b83 --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/gen_test_cert.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# Set output directory, default is the current directory +OUT_DIR=${1:-"."} + +# Create output directory if it doesn't exist +mkdir -p "$OUT_DIR" + +# Generate CA certificate (Certificate Authority) +openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \ + -subj "/CN=otel-test-ca" \ + -keyout "$OUT_DIR/otel-test-ca-key.pem" -out "$OUT_DIR/otel-test-ca-cert.pem" + +# Create the extension configuration file for the server certificate +cat > "$OUT_DIR/server_cert_ext.cnf" < "$OUT_DIR/client_cert_ext.cnf" < + Unit test project for Prometheus Exporter AspNetCore for OpenTelemetry $(TargetFrameworksForAspNetCoreTests) $(DefineConstants);PROMETHEUS_ASPNETCORE - - - disable - - runtime; build; native; contentfiles; analyzers - + + + + + - + + + + + @@ -32,4 +36,5 @@ + diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs index 7386a5a972..c34dba2958 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs @@ -110,7 +110,7 @@ public Task PrometheusExporterMiddlewareIntegration_MixedPredicateAndPath() services => services.Configure(o => o.ScrapeEndpointPath = "/metrics_options"), validateResponse: rsp => { - if (!rsp.Headers.TryGetValues("X-MiddlewareExecuted", out IEnumerable headers)) + if (!rsp.Headers.TryGetValues("X-MiddlewareExecuted", out IEnumerable? headers)) { headers = Array.Empty(); } @@ -137,7 +137,7 @@ public Task PrometheusExporterMiddlewareIntegration_MixedPath() services => services.Configure(o => o.ScrapeEndpointPath = "/metrics_options"), validateResponse: rsp => { - if (!rsp.Headers.TryGetValues("X-MiddlewareExecuted", out IEnumerable headers)) + if (!rsp.Headers.TryGetValues("X-MiddlewareExecuted", out IEnumerable? headers)) { headers = Array.Empty(); } @@ -249,24 +249,97 @@ public Task PrometheusExporterMiddlewareIntegration_UseOpenMetricsVersionHeader( } [Fact] - public async Task PrometheusExporterMiddlewareIntegration_CanServeOpenMetricsAndPlainFormats() + public Task PrometheusExporterMiddlewareIntegration_TextPlainResponse_WithMeterTags() + { + var meterTags = new KeyValuePair[] + { + new("meterKey1", "value1"), + new("meterKey2", "value2"), + }; + + return RunPrometheusExporterMiddlewareIntegrationTest( + "/metrics", + app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), + acceptHeader: "text/plain", + meterTags: meterTags); + } + + [Fact] + public Task PrometheusExporterMiddlewareIntegration_UseOpenMetricsVersionHeader_WithMeterTags() + { + var meterTags = new KeyValuePair[] + { + new("meterKey1", "value1"), + new("meterKey2", "value2"), + }; + + return RunPrometheusExporterMiddlewareIntegrationTest( + "/metrics", + app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), + acceptHeader: "application/openmetrics-text; version=1.0.0", + meterTags: meterTags); + } + + [Fact] + public async Task PrometheusExporterMiddlewareIntegration_CanServeOpenMetricsAndPlainFormats_NoMeterTags() + { + await RunPrometheusExporterMiddlewareIntegrationTestWithBothFormats(); + } + + [Fact] + public async Task PrometheusExporterMiddlewareIntegration_CanServeOpenMetricsAndPlainFormats_WithMeterTags() + { + var meterTags = new KeyValuePair[] + { + new("meterKey1", "value1"), + new("meterKey2", "value2"), + }; + + await RunPrometheusExporterMiddlewareIntegrationTestWithBothFormats(meterTags); + } + + [Fact] + public async Task PrometheusExporterMiddlewareIntegration_TestBufferSizeIncrease_With_LotOfMetrics() { using var host = await StartTestHostAsync( app => app.UseOpenTelemetryPrometheusScrapingEndpoint()); - var tags = new KeyValuePair[] + using var meter = new Meter(MeterName, MeterVersion); + + for (var x = 0; x < 1000; x++) { - new KeyValuePair("key1", "value1"), - new KeyValuePair("key2", "value2"), + var counter = meter.CreateCounter("counter_double_" + x, unit: "By"); + counter.Add(1); + } + + using var client = host.GetTestClient(); + + using var response = await client.GetAsync("/metrics"); + var text = await response.Content.ReadAsStringAsync(); + + Assert.NotEmpty(text); + + await host.StopAsync(); + } + + private static async Task RunPrometheusExporterMiddlewareIntegrationTestWithBothFormats(KeyValuePair[]? meterTags = null) + { + using var host = await StartTestHostAsync( + app => app.UseOpenTelemetryPrometheusScrapingEndpoint()); + + var counterTags = new KeyValuePair[] + { + new("key1", "value1"), + new("key2", "value2"), }; - using var meter = new Meter(MeterName, MeterVersion); + using var meter = new Meter(MeterName, MeterVersion, meterTags); var beginTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); - var counter = meter.CreateCounter("counter_double"); - counter.Add(100.18D, tags); - counter.Add(0.99D, tags); + var counter = meter.CreateCounter("counter_double", unit: "By"); + counter.Add(100.18D, counterTags); + counter.Add(0.99D, counterTags); var testCases = new bool[] { true, false, true, true, false }; @@ -282,7 +355,7 @@ public async Task PrometheusExporterMiddlewareIntegration_CanServeOpenMetricsAnd }; using var response = await client.SendAsync(request); var endTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); - await VerifyAsync(beginTimestamp, endTimestamp, response, testCase); + await VerifyAsync(beginTimestamp, endTimestamp, response, testCase, meterTags); } await host.StopAsync(); @@ -291,32 +364,33 @@ public async Task PrometheusExporterMiddlewareIntegration_CanServeOpenMetricsAnd private static async Task RunPrometheusExporterMiddlewareIntegrationTest( string path, Action configure, - Action configureServices = null, - Action validateResponse = null, + Action? configureServices = null, + Action? validateResponse = null, bool registerMeterProvider = true, - Action configureOptions = null, + Action? configureOptions = null, bool skipMetrics = false, - string acceptHeader = "application/openmetrics-text") + string acceptHeader = "application/openmetrics-text", + KeyValuePair[]? meterTags = null) { var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text"); using var host = await StartTestHostAsync(configure, configureServices, registerMeterProvider, configureOptions); - var tags = new KeyValuePair[] + var counterTags = new KeyValuePair[] { - new KeyValuePair("key1", "value1"), - new KeyValuePair("key2", "value2"), + new("key1", "value1"), + new("key2", "value2"), }; - using var meter = new Meter(MeterName, MeterVersion); + using var meter = new Meter(MeterName, MeterVersion, meterTags); var beginTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); - var counter = meter.CreateCounter("counter_double"); + var counter = meter.CreateCounter("counter_double", unit: "By"); if (!skipMetrics) { - counter.Add(100.18D, tags); - counter.Add(0.99D, tags); + counter.Add(100.18D, counterTags); + counter.Add(0.99D, counterTags); } using var client = host.GetTestClient(); @@ -332,7 +406,7 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( if (!skipMetrics) { - await VerifyAsync(beginTimestamp, endTimestamp, response, requestOpenMetrics); + await VerifyAsync(beginTimestamp, endTimestamp, response, requestOpenMetrics, meterTags); } else { @@ -344,20 +418,24 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( await host.StopAsync(); } - private static async Task VerifyAsync(long beginTimestamp, long endTimestamp, HttpResponseMessage response, bool requestOpenMetrics) + private static async Task VerifyAsync(long beginTimestamp, long endTimestamp, HttpResponseMessage response, bool requestOpenMetrics, KeyValuePair[]? meterTags) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.True(response.Content.Headers.Contains("Last-Modified")); if (requestOpenMetrics) { - Assert.Equal("application/openmetrics-text; version=1.0.0; charset=utf-8", response.Content.Headers.ContentType.ToString()); + Assert.Equal("application/openmetrics-text; version=1.0.0; charset=utf-8", response.Content.Headers.ContentType!.ToString()); } else { - Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType.ToString()); + Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType!.ToString()); } + var additionalTags = meterTags != null && meterTags.Any() + ? $"{string.Join(",", meterTags.Select(x => $"{x.Key}=\"{x.Value}\""))}," + : string.Empty; + string content = (await response.Content.ReadAsStringAsync()).ReplaceLineEndings(); string expected = requestOpenMetrics @@ -368,14 +446,16 @@ private static async Task VerifyAsync(long beginTimestamp, long endTimestamp, Ht # TYPE otel_scope_info info # HELP otel_scope_info Scope metadata otel_scope_info{otel_scope_name="{{MeterName}}"} 1 - # TYPE counter_double_total counter - counter_double_total{otel_scope_name="{{MeterName}}",otel_scope_version="{{MeterVersion}}",key1="value1",key2="value2"} 101.17 (\d+\.\d{3}) + # TYPE counter_double_bytes counter + # UNIT counter_double_bytes bytes + counter_double_bytes_total{otel_scope_name="{{MeterName}}",otel_scope_version="{{MeterVersion}}",{{additionalTags}}key1="value1",key2="value2"} 101.17 (\d+\.\d{3}) # EOF """.ReplaceLineEndings() : $$""" - # TYPE counter_double_total counter - counter_double_total{otel_scope_name="{{MeterName}}",otel_scope_version="{{MeterVersion}}",key1="value1",key2="value2"} 101.17 (\d+) + # TYPE counter_double_bytes_total counter + # UNIT counter_double_bytes_total bytes + counter_double_bytes_total{otel_scope_name="{{MeterName}}",otel_scope_version="{{MeterVersion}}",{{additionalTags}}key1="value1",key2="value2"} 101.17 (\d+) # EOF """.ReplaceLineEndings(); @@ -391,9 +471,9 @@ private static async Task VerifyAsync(long beginTimestamp, long endTimestamp, Ht private static Task StartTestHostAsync( Action configure, - Action configureServices = null, + Action? configureServices = null, bool registerMeterProvider = true, - Action configureOptions = null) + Action? configureOptions = null) { return new HostBuilder() .ConfigureWebHost(webBuilder => webBuilder 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 7ab05160d7..6d6c38ff48 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 @@ -1,26 +1,29 @@ + Unit test project for Prometheus Exporter HttpListener for OpenTelemetry $(TargetFrameworksForTests) $(DefineConstants);PROMETHEUS_HTTP_LISTENER - - - disable - - runtime; build; native; contentfiles; analyzers - + + + + + + + + diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs index 32ee87e3de..36f5e124e6 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs @@ -31,7 +31,7 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon #endif .Build()) { - if (!provider.TryFindExporter(out PrometheusExporter exporter)) + if (!provider.TryFindExporter(out PrometheusExporter? exporter)) { throw new InvalidOperationException("PrometheusExporter could not be found on MeterProvider."); } @@ -40,7 +40,7 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon var collectFunc = exporter.Collect; exporter.Collect = (timeout) => { - bool result = collectFunc(timeout); + bool result = collectFunc!(timeout); runningCollectCount++; Thread.Sleep(5000); return result; @@ -156,6 +156,6 @@ private class Response { public PrometheusCollectionManager.CollectionResponse CollectionResponse; - public byte[] ViewPayload; + public byte[]? ViewPayload; } } diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs index 3a4ca1c415..b9b6201183 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs @@ -38,7 +38,7 @@ public void UriPrefixesNull() { Assert.Throws(() => { - TestPrometheusHttpListenerUriPrefixOptions(null); + TestPrometheusHttpListenerUriPrefixOptions(null!); }); } @@ -84,16 +84,40 @@ public async Task PrometheusExporterHttpServerIntegration_UseOpenMetricsVersionH await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: "application/openmetrics-text; version=1.0.0"); } + [Fact] + public async Task PrometheusExporterHttpServerIntegration_NoOpenMetrics_WithMeterTags() + { + var tags = new KeyValuePair[] + { + new("meter1", "value1"), + new("meter2", "value2"), + }; + + await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty, meterTags: tags); + } + + [Fact] + public async Task PrometheusExporterHttpServerIntegration_OpenMetrics_WithMeterTags() + { + var tags = new KeyValuePair[] + { + new("meter1", "value1"), + new("meter2", "value2"), + }; + + await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: "application/openmetrics-text; version=1.0.0", meterTags: tags); + } + [Fact] public void PrometheusHttpListenerThrowsOnStart() { Random random = new Random(); int retryAttempts = 5; int port = 0; - string address = null; + string? address = null; - PrometheusExporter exporter = null; - PrometheusHttpListener listener = null; + PrometheusExporter? exporter = null; + PrometheusHttpListener? listener = null; // Step 1: Start a listener on a random port. while (retryAttempts-- != 0) @@ -134,7 +158,7 @@ public void PrometheusHttpListenerThrowsOnStart() exporter, new() { - UriPrefixes = new string[] { address }, + UriPrefixes = new string[] { address! }, }); listener.Start(); @@ -144,6 +168,45 @@ public void PrometheusHttpListenerThrowsOnStart() listener?.Dispose(); } + [Theory] + [InlineData("application/openmetrics-text")] + [InlineData("")] + public async Task PrometheusExporterHttpServerIntegration_TestBufferSizeIncrease_With_LargePayload(string acceptHeader) + { + using var meter = new Meter(MeterName, MeterVersion); + + var attributes = new List>(); + var oneKb = new string('A', 1024); + for (var x = 0; x < 8500; x++) + { + attributes.Add(new KeyValuePair(x.ToString(), oneKb)); + } + + var provider = BuildMeterProvider(meter, attributes, out var address); + + for (var x = 0; x < 1000; x++) + { + var counter = meter.CreateCounter("counter_double_" + x, unit: "By"); + counter.Add(1); + } + + using HttpClient client = new HttpClient(); + + if (!string.IsNullOrEmpty(acceptHeader)) + { + client.DefaultRequestHeaders.Add("Accept", acceptHeader); + } + + using var response = await client.GetAsync($"{address}metrics"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + Assert.Contains("counter_double_999", content); + Assert.DoesNotContain('\0', content); + + provider.Dispose(); + } + private static void TestPrometheusHttpListenerUriPrefixOptions(string[] uriPrefixes) { using var exporter = new PrometheusExporter(new()); @@ -155,31 +218,27 @@ private static void TestPrometheusHttpListenerUriPrefixOptions(string[] uriPrefi }); } - private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text") + private static MeterProvider BuildMeterProvider(Meter meter, IEnumerable> attributes, out string address) { - var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text"); - Random random = new Random(); int retryAttempts = 5; int port = 0; - string address = null; - - MeterProvider provider = null; - using var meter = new Meter(MeterName, MeterVersion); + string? generatedAddress = null; + MeterProvider? provider = null; while (retryAttempts-- != 0) { port = random.Next(2000, 5000); - address = $"http://localhost:{port}/"; + generatedAddress = $"http://localhost:{port}/"; try { provider = Sdk.CreateMeterProviderBuilder() .AddMeter(meter.Name) - .ConfigureResource(x => x.Clear().AddService("my_service", serviceInstanceId: "id1")) + .ConfigureResource(x => x.Clear().AddService("my_service", serviceInstanceId: "id1").AddAttributes(attributes)) .AddPrometheusHttpListener(options => { - options.UriPrefixes = new string[] { address }; + options.UriPrefixes = new string[] { generatedAddress }; }) .Build(); @@ -191,22 +250,35 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri } } + address = generatedAddress!; + if (provider == null) { throw new InvalidOperationException("HttpListener could not be started"); } - var tags = new KeyValuePair[] + return provider; + } + + private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text", KeyValuePair[]? meterTags = null) + { + var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text"); + + using var meter = new Meter(MeterName, MeterVersion, meterTags); + + var provider = BuildMeterProvider(meter, [], out var address); + + var counterTags = new KeyValuePair[] { - new KeyValuePair("key1", "value1"), - new KeyValuePair("key2", "value2"), + new("key1", "value1"), + new("key2", "value2"), }; - var counter = meter.CreateCounter("counter_double"); + var counter = meter.CreateCounter("counter_double", unit: "By"); if (!skipMetrics) { - counter.Add(100.18D, tags); - counter.Add(0.99D, tags); + counter.Add(100.18D, counterTags); + counter.Add(0.99D, counterTags); } using HttpClient client = new HttpClient(); @@ -225,13 +297,17 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri if (requestOpenMetrics) { - Assert.Equal("application/openmetrics-text; version=1.0.0; charset=utf-8", response.Content.Headers.ContentType.ToString()); + Assert.Equal("application/openmetrics-text; version=1.0.0; charset=utf-8", response.Content.Headers.ContentType!.ToString()); } else { - Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType.ToString()); + Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType!.ToString()); } + var additionalTags = meterTags != null && meterTags.Any() + ? $"{string.Join(",", meterTags.Select(x => $"{x.Key}='{x.Value}'"))}," + : string.Empty; + var content = await response.Content.ReadAsStringAsync(); var expected = requestOpenMetrics @@ -241,11 +317,13 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri + "# TYPE otel_scope_info info\n" + "# HELP otel_scope_info Scope metadata\n" + $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n" - + "# TYPE counter_double_total counter\n" - + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+\\.\\d{{3}})\n" + + "# TYPE counter_double_bytes counter\n" + + "# UNIT counter_double_bytes bytes\n" + + $"counter_double_bytes_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',{additionalTags}key1='value1',key2='value2'}} 101.17 (\\d+\\.\\d{{3}})\n" + "# EOF\n" - : "# TYPE counter_double_total counter\n" - + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+)\n" + : "# TYPE counter_double_bytes_total counter\n" + + "# UNIT counter_double_bytes_total bytes\n" + + $"counter_double_bytes_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',{additionalTags}key1='value1',key2='value2'}} 101.17 (\\d+)\n" + "# EOF\n"; Assert.Matches(("^" + expected + "$").Replace('\'', '"'), content); diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusMetricTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusMetricTests.cs index 81ce3e1f7d..a1350f03ec 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusMetricTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusMetricTests.cs @@ -26,7 +26,7 @@ public void SanitizeMetricName_SupportLeadingAndTrailingUnderscores() } [Fact] - public void SanitizeMetricName_RemoveUnsupportedChracters() + public void SanitizeMetricName_RemoveUnsupportedCharacters() { AssertSanitizeMetricName("metric_unit_$1000", "metric_unit_1000"); } @@ -116,13 +116,7 @@ public void Unit_AnnotationMismatch_Close() } [Fact] - public void Name_SpecialCaseGuage_AppendRatio() - { - AssertName("sample", "1", PrometheusType.Gauge, false, "sample_ratio"); - } - - [Fact] - public void Name_GuageWithUnit_NoAppendRatio() + public void Name_GaugeWithUnit_NoAppendRatio() { AssertName("sample", "unit", PrometheusType.Gauge, false, "sample_unit"); } @@ -205,6 +199,54 @@ public void Name_StartWithNumber_UnderscoreStart() AssertName("2_metric_name", "By", PrometheusType.Summary, false, "_metric_name_bytes"); } + [Fact] + public void OpenMetricsName_UnitAlreadyPresentInName_Appended() + { + AssertOpenMetricsName("db_bytes_written", "By", PrometheusType.Gauge, false, "db_bytes_written_bytes"); + } + + [Fact] + public void OpenMetricsName_SuffixedWithUnit_NotAppended() + { + AssertOpenMetricsName("db_written_bytes", "By", PrometheusType.Gauge, false, "db_written_bytes"); + } + + [Fact] + public void OpenMetricsName_Counter_AppendTotal() + { + AssertOpenMetricsName("db_bytes_written", "By", PrometheusType.Counter, false, "db_bytes_written_bytes_total"); + } + + [Fact] + public void OpenMetricsName_Counter_DisableSuffixTotal_AppendTotal() + { + AssertOpenMetricsName("db_bytes_written", "By", PrometheusType.Counter, true, "db_bytes_written_bytes_total"); + } + + [Fact] + public void OpenMetricsName_CounterSuffixedWithTotal_AppendUnitAndTotal() + { + AssertOpenMetricsName("db_bytes_written_total", "By", PrometheusType.Counter, false, "db_bytes_written_bytes_total"); + } + + [Fact] + public void OpenMetricsName_CounterSuffixedWithTotal_DisableSuffixTotal_AppendTotal() + { + AssertOpenMetricsName("db_bytes_written_total", "By", PrometheusType.Counter, false, "db_bytes_written_bytes_total"); + } + + [Fact] + public void OpenMetricsMetadataName_Counter_NotAppendTotal() + { + AssertOpenMetricsMetadataName("db_bytes_written", "By", PrometheusType.Counter, false, "db_bytes_written_bytes"); + } + + [Fact] + public void OpenMetricsMetadataName_Counter_DisableSuffixTotal_NotAppendTotal() + { + AssertOpenMetricsMetadataName("db_bytes_written", "By", PrometheusType.Counter, true, "db_bytes_written_bytes"); + } + private static void AssertName( string name, string unit, PrometheusType type, bool disableTotalNameSuffixForCounters, string expected) { @@ -217,4 +259,18 @@ private static void AssertSanitizeMetricName(string name, string expected) var sanatizedName = PrometheusMetric.SanitizeMetricName(name); Assert.Equal(expected, sanatizedName); } + + private static void AssertOpenMetricsName( + string name, string unit, PrometheusType type, bool disableTotalNameSuffixForCounters, string expected) + { + var prometheusMetric = new PrometheusMetric(name, unit, type, disableTotalNameSuffixForCounters); + Assert.Equal(expected, prometheusMetric.OpenMetricsName); + } + + private static void AssertOpenMetricsMetadataName( + string name, string unit, PrometheusType type, bool disableTotalNameSuffixForCounters, string expected) + { + var prometheusMetric = new PrometheusMetric(name, unit, type, disableTotalNameSuffixForCounters); + Assert.Equal(expected, prometheusMetric.OpenMetricsMetadataName); + } } diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs index 7c4a95b05f..2864fe59b5 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs @@ -129,7 +129,7 @@ public void GaugeOneDimension() meter.CreateObservableGauge( "test_gauge", - () => new Measurement(123, new KeyValuePair("tagKey", "tagValue"))); + () => new Measurement(123, new KeyValuePair("tagKey", "tagValue"))); provider.ForceFlush(); @@ -156,7 +156,7 @@ public void GaugeBoolDimension() meter.CreateObservableGauge( "test_gauge", - () => new Measurement(123, new KeyValuePair("tagKey", true))); + () => new Measurement(123, new KeyValuePair("tagKey", true))); provider.ForceFlush(); @@ -312,8 +312,8 @@ public void HistogramOneDimension() .Build(); var histogram = meter.CreateHistogram("test_histogram"); - histogram.Record(18, new KeyValuePair("x", "1")); - histogram.Record(100, new KeyValuePair("x", "1")); + histogram.Record(18, new KeyValuePair("x", "1")); + histogram.Record(100, new KeyValuePair("x", "1")); provider.ForceFlush(); @@ -539,8 +539,8 @@ public void HistogramOneDimensionWithOpenMetricsFormat() .Build(); var histogram = meter.CreateHistogram("test_histogram"); - histogram.Record(18, new KeyValuePair("x", "1")); - histogram.Record(100, new KeyValuePair("x", "1")); + histogram.Record(18, new KeyValuePair("x", "1")); + histogram.Record(100, new KeyValuePair("x", "1")); provider.ForceFlush(); @@ -622,8 +622,8 @@ public void HistogramOneDimensionWithScopeVersion() .Build(); var histogram = meter.CreateHistogram("test_histogram"); - histogram.Record(18, new KeyValuePair("x", "1")); - histogram.Record(100, new KeyValuePair("x", "1")); + histogram.Record(18, new KeyValuePair("x", "1")); + histogram.Record(100, new KeyValuePair("x", "1")); provider.ForceFlush(); diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionExtensionsTest.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionExtensionsTest.cs index 6e28b18d88..14f2847417 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionExtensionsTest.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionExtensionsTest.cs @@ -19,7 +19,7 @@ public void CheckProcessTag(string key, object value) { var attributeEnumerationState = new TagEnumerationState { - Tags = PooledList>.Create(), + Tags = PooledList>.Create(), }; using var activity = new Activity("TestActivity"); @@ -36,11 +36,11 @@ public void CheckProcessTag(string key, object value) [InlineData("string", null)] [InlineData("bool", null)] [InlineData("double", null)] - public void CheckNullValueProcessTag(string key, object value) + public void CheckNullValueProcessTag(string key, object? value) { var attributeEnumerationState = new TagEnumerationState { - Tags = PooledList>.Create(), + Tags = PooledList>.Create(), }; using var activity = new Activity("TestActivity"); diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs index da6cdcbfcb..9a23ba329d 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs @@ -79,6 +79,7 @@ public void ToZipkinSpan_NoEvents() [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 @@ -105,7 +106,7 @@ 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 == "error" && (string?)t.Value == string.Empty); } else { @@ -148,7 +149,7 @@ public void ToZipkinSpan_Activity_Status_And_StatusDescription_is_Set(ActivitySt Assert.Contains( zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName && - (string)t.Value == description); + (string?)t.Value == description); } else { @@ -159,6 +160,7 @@ public void ToZipkinSpan_Activity_Status_And_StatusDescription_is_Set(ActivitySt } [Fact] + [Obsolete("Remove when ActivityExtensions status APIs are removed")] public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeIsOk() { // Arrange. @@ -175,15 +177,16 @@ public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeI // Assert. Assert.Equal("OK", zipkinSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).Value); - Assert.Contains(zipkinSpan.Tags, t => t.Key == "otel.status_code" && (string)t.Value == "OK"); - Assert.DoesNotContain(zipkinSpan.Tags, t => t.Key == "otel.status_code" && (string)t.Value == "ERROR"); + Assert.Contains(zipkinSpan.Tags, t => t.Key == "otel.status_code" && (string?)t.Value == "OK"); + Assert.DoesNotContain(zipkinSpan.Tags, t => t.Key == "otel.status_code" && (string?)t.Value == "ERROR"); // Ensure additional Activity tags were being converted. - Assert.Contains(zipkinSpan.Tags, t => t.Key == "myCustomTag" && (string)t.Value == "myCustomTagValue"); + Assert.Contains(zipkinSpan.Tags, t => t.Key == "myCustomTag" && (string?)t.Value == "myCustomTagValue"); Assert.DoesNotContain(zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName); } [Fact] + [Obsolete("Remove when ActivityExtensions status APIs are removed")] public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeIsError() { // Arrange. @@ -208,17 +211,18 @@ public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeI Assert.Contains( zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName && - (string)t.Value == StatusDescriptionOnError); + (string?)t.Value == StatusDescriptionOnError); Assert.DoesNotContain( zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName && - (string)t.Value == TagDescriptionOnError); + (string?)t.Value == TagDescriptionOnError); // Ensure additional Activity tags were being converted. - Assert.Contains(zipkinSpan.Tags, t => t.Key == "myCustomTag" && (string)t.Value == "myCustomTagValue"); + Assert.Contains(zipkinSpan.Tags, t => t.Key == "myCustomTag" && (string?)t.Value == "myCustomTagValue"); } [Fact] + [Obsolete("Remove when ActivityExtensions status APIs are removed")] public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeIsError_SettingTagFirst() { // Arrange. @@ -243,13 +247,13 @@ public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeI Assert.Contains( zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName && - (string)t.Value == StatusDescriptionOnError); + (string?)t.Value == StatusDescriptionOnError); Assert.DoesNotContain( zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName && - (string)t.Value == TagDescriptionOnError); + (string?)t.Value == TagDescriptionOnError); // Ensure additional Activity tags were being converted. - Assert.Contains(zipkinSpan.Tags, t => t.Key == "myCustomTag" && (string)t.Value == "myCustomTagValue"); + Assert.Contains(zipkinSpan.Tags, t => t.Key == "myCustomTag" && (string?)t.Value == "myCustomTagValue"); } } diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs index ddbd059c0c..0f7c9e1bdb 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs @@ -45,7 +45,7 @@ public void GenerateActivity_RemoteEndpointResolution() public void GenerateActivity_RemoteEndpointResolutionPriority(RemoteEndpointPriorityTestCase testCase) { // Arrange - using var activity = ZipkinExporterTests.CreateTestActivity(additionalAttributes: testCase.RemoteEndpointAttributes); + using var activity = ZipkinExporterTests.CreateTestActivity(additionalAttributes: testCase.RemoteEndpointAttributes!); // Act & Assert var zipkinSpan = ZipkinActivityConversionExtensions.ToZipkinSpan(activity, DefaultZipkinEndpoint); @@ -56,11 +56,11 @@ public void GenerateActivity_RemoteEndpointResolutionPriority(RemoteEndpointPrio public class RemoteEndpointPriorityTestCase { - public string Name { get; set; } + public string? Name { get; set; } - public string ExpectedResult { get; set; } + public string? ExpectedResult { get; set; } - public Dictionary RemoteEndpointAttributes { get; set; } + public Dictionary? RemoteEndpointAttributes { get; set; } public static IEnumerable GetTestCases() { @@ -174,7 +174,7 @@ public static IEnumerable GetTestCases() }; } - public override string ToString() + 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 f5a72a2f25..a5494a2f92 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj @@ -1,9 +1,8 @@ + Unit test project for Zipkin Exporter for OpenTelemetry $(TargetFrameworksForTests) - - disable @@ -19,9 +18,7 @@ - - runtime; build; native; contentfiles; analyzers - + diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs index 308f2e6715..97b90a5ec6 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Diagnostics; +using System.Globalization; using System.Net; #if NETFRAMEWORK using System.Net.Http; @@ -57,7 +58,7 @@ static void ProcessServerRequest(HttpListenerContext context) string requestContent = readStream.ReadToEnd(); Responses.TryAdd( - Guid.Parse(context.Request.QueryString["requestId"]), + Guid.Parse(context.Request.QueryString["requestId"]!), requestContent); context.Response.OutputStream.Close(); @@ -94,8 +95,8 @@ public void AddAddZipkinExporterNamedOptionsSupported() [Fact] public void BadArgs() { - TracerProviderBuilder builder = null; - Assert.Throws(() => builder.AddZipkinExporter()); + TracerProviderBuilder? builder = null; + Assert.Throws(() => builder!.AddZipkinExporter()); } [Fact] @@ -150,7 +151,7 @@ public void EndpointConfigurationUsingEnvironmentVariable() var exporterOptions = new ZipkinExporterOptions(); - Assert.Equal(new Uri(Environment.GetEnvironmentVariable(ZipkinExporterOptions.ZipkinEndpointEnvVar)).AbsoluteUri, exporterOptions.Endpoint.AbsoluteUri); + Assert.Equal(new Uri(Environment.GetEnvironmentVariable(ZipkinExporterOptions.ZipkinEndpointEnvVar)!).AbsoluteUri, exporterOptions.Endpoint.AbsoluteUri); } finally { @@ -204,7 +205,7 @@ public void EndpointConfigurationUsingIConfiguration() }; var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(values) + .AddInMemoryCollection(values!) .Build(); var options = new ZipkinExporterOptions(configuration, new()); @@ -246,13 +247,13 @@ public void UserHttpFactoryCalled() Assert.Equal(2, invocations); } - options.HttpClientFactory = null; + options.HttpClientFactory = null!; Assert.Throws(() => { using var exporter = new ZipkinExporter(options); }); - options.HttpClientFactory = () => null; + options.HttpClientFactory = () => null!; Assert.Throws(() => { using var exporter = new ZipkinExporter(options); @@ -287,7 +288,7 @@ public void UpdatesServiceNameFromDefaultResource() zipkinExporter.SetLocalEndpointFromResource(Resource.Empty); - Assert.StartsWith("unknown_service:", zipkinExporter.LocalEndpoint.ServiceName); + Assert.StartsWith("unknown_service:", zipkinExporter.LocalEndpoint!.ServiceName); } [Fact] @@ -302,7 +303,7 @@ public void UpdatesServiceNameFromIConfiguration() }; services.AddSingleton( - new ConfigurationBuilder().AddInMemoryCollection(configuration).Build()); + new ConfigurationBuilder().AddInMemoryCollection(configuration!).Build()); }); var zipkinExporter = new ZipkinExporter(new ZipkinExporterOptions()); @@ -313,7 +314,7 @@ public void UpdatesServiceNameFromIConfiguration() zipkinExporter.SetLocalEndpointFromResource(Resource.Empty); - Assert.Equal("myservicename", zipkinExporter.LocalEndpoint.ServiceName); + Assert.Equal("myservicename", zipkinExporter.LocalEndpoint!.ServiceName); } [Theory] @@ -321,31 +322,18 @@ public void UpdatesServiceNameFromIConfiguration() [InlineData(false, false, false)] [InlineData(false, true, false)] [InlineData(false, false, true)] - [InlineData(false, false, false, StatusCode.Ok)] - [InlineData(false, false, false, StatusCode.Ok, null, true)] - [InlineData(false, false, false, StatusCode.Error)] - [InlineData(false, false, false, StatusCode.Error, "Error description")] + [InlineData(false, false, false, ActivityStatusCode.Ok)] + [InlineData(false, false, false, ActivityStatusCode.Ok, null, true)] + [InlineData(false, false, false, ActivityStatusCode.Error)] + [InlineData(false, false, false, ActivityStatusCode.Error, "Error description")] public void IntegrationTest( bool useShortTraceIds, bool useTestResource, bool isRootSpan, - StatusCode statusCode = StatusCode.Unset, - string statusDescription = null, + ActivityStatusCode statusCode = ActivityStatusCode.Unset, + string? statusDescription = null, bool addErrorTag = false) { - var status = statusCode switch - { - StatusCode.Unset => Status.Unset, - StatusCode.Ok => Status.Ok, - StatusCode.Error => Status.Error, - _ => throw new InvalidOperationException(), - }; - - if (!string.IsNullOrEmpty(statusDescription)) - { - status = status.WithDescription(statusDescription); - } - Guid requestId = Guid.NewGuid(); ZipkinExporter exporter = new ZipkinExporter( @@ -358,7 +346,8 @@ public void IntegrationTest( var serviceName = (string)exporter.ParentProvider.GetDefaultResource().Attributes .Where(pair => pair.Key == ResourceSemanticConventions.AttributeServiceName).FirstOrDefault().Value; var resourceTags = string.Empty; - var activity = CreateTestActivity(isRootSpan: isRootSpan, status: status); + var dateTime = DateTime.UtcNow; + var activity = CreateTestActivity(isRootSpan: isRootSpan, statusCode: statusCode, statusDescription: statusDescription, dateTime: dateTime); if (useTestResource) { serviceName = "MyService"; @@ -389,7 +378,7 @@ public void IntegrationTest( var eventTimestamp = activity.Events.First().Timestamp.ToEpochMicroseconds(); StringBuilder ipInformation = new StringBuilder(); - if (!string.IsNullOrEmpty(exporter.LocalEndpoint.Ipv4)) + if (!string.IsNullOrEmpty(exporter.LocalEndpoint!.Ipv4)) { ipInformation.Append($@",""ipv4"":""{exporter.LocalEndpoint.Ipv4}"""); } @@ -407,13 +396,13 @@ public void IntegrationTest( string errorTag = string.Empty; switch (statusCode) { - case StatusCode.Ok: + case ActivityStatusCode.Ok: statusTag = $@"""{SpanAttributeConstants.StatusCodeKey}"":""OK"","; break; - case StatusCode.Unset: + case ActivityStatusCode.Unset: statusTag = string.Empty; break; - case StatusCode.Error: + case ActivityStatusCode.Error: statusTag = $@"""{SpanAttributeConstants.StatusCodeKey}"":""ERROR"","; errorTag = $@"""{ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName}"":""{statusDescription}"","; break; @@ -422,25 +411,57 @@ public void IntegrationTest( } Assert.Equal( - $@"[{{""traceId"":""{traceId}"",""name"":""Name"",{parentId}""id"":""{ZipkinActivityConversionExtensions.EncodeSpanId(context.SpanId)}"",""kind"":""CLIENT"",""timestamp"":{timestamp},""duration"":60000000,""localEndpoint"":{{""serviceName"":""{serviceName}""{ipInformation}}},""remoteEndpoint"":{{""serviceName"":""http://localhost:44312/""}},""annotations"":[{{""timestamp"":{eventTimestamp},""value"":""Event1""}},{{""timestamp"":{eventTimestamp},""value"":""Event2""}}],""tags"":{{{resourceTags}""stringKey"":""value"",""longKey"":""1"",""longKey2"":""1"",""doubleKey"":""1"",""doubleKey2"":""1"",""longArrayKey"":""[1,2]"",""boolKey"":""true"",""boolArrayKey"":""[true,false]"",""http.host"":""http://localhost:44312/"",{statusTag}{errorTag}""otel.scope.name"":""CreateTestActivity"",""otel.library.name"":""CreateTestActivity"",""peer.service"":""http://localhost:44312/""}}}}]", + $@"[{{""traceId"":""{traceId}""," + + @"""name"":""Name""," + + parentId + + $@"""id"":""{ZipkinActivityConversionExtensions.EncodeSpanId(context.SpanId)}""," + + @"""kind"":""CLIENT""," + + $@"""timestamp"":{timestamp}," + + @"""duration"":60000000," + + $@"""localEndpoint"":{{""serviceName"":""{serviceName}""{ipInformation}}}," + + @"""remoteEndpoint"":{""serviceName"":""http://localhost:44312/""}," + + $@"""annotations"":[{{""timestamp"":{eventTimestamp},""value"":""Event1""}},{{""timestamp"":{eventTimestamp},""value"":""Event2""}}]," + + @"""tags"":{" + + resourceTags + + $@"""stringKey"":""value""," + + @"""longKey"":""1""," + + @"""longKey2"":""1""," + + @"""doubleKey"":""1""," + + @"""doubleKey2"":""1""," + + @"""longArrayKey"":""[1,2]""," + + @"""boolKey"":""true""," + + @"""boolArrayKey"":""[true,false]""," + + @"""http.host"":""http://localhost:44312/""," + + $@"""dateTimeKey"":""{Convert.ToString(dateTime, CultureInfo.InvariantCulture)}""," + + $@"""dateTimeArrayKey"":""[\u0022{Convert.ToString(dateTime, CultureInfo.InvariantCulture)}\u0022]""," + + statusTag + + errorTag + + @"""otel.scope.name"":""CreateTestActivity""," + + @"""otel.library.name"":""CreateTestActivity""," + + @"""peer.service"":""http://localhost:44312/""" + + "}}]", Responses[requestId]); } internal static Activity CreateTestActivity( bool isRootSpan = false, bool setAttributes = true, - Dictionary additionalAttributes = null, + Dictionary? additionalAttributes = null, bool addEvents = true, bool addLinks = true, - Resource resource = null, + Resource? resource = null, ActivityKind kind = ActivityKind.Client, - Status? status = null) + 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 @@ -454,6 +475,8 @@ internal static Activity CreateTestActivity( { "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) { @@ -471,14 +494,14 @@ internal static Activity CreateTestActivity( new ActivityEvent( "Event1", eventTimestamp, - new ActivityTagsCollection(new Dictionary + new ActivityTagsCollection(new Dictionary { { "key", "value" }, })), new ActivityEvent( "Event2", eventTimestamp, - new ActivityTagsCollection(new Dictionary + new ActivityTagsCollection(new Dictionary { { "key", "value" }, })), @@ -489,7 +512,7 @@ internal static Activity CreateTestActivity( var activitySource = new ActivitySource(nameof(CreateTestActivity)); var tags = setAttributes ? - attributes.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value)) + attributes.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value)) : null; var links = addLinks ? new[] @@ -507,7 +530,7 @@ internal static Activity CreateTestActivity( parentContext: new ActivityContext(traceId, parentSpanId, ActivityTraceFlags.Recorded), tags, links, - startTime: startTimestamp); + startTime: startTimestamp)!; if (addEvents) { @@ -517,10 +540,7 @@ internal static Activity CreateTestActivity( } } - if (status.HasValue) - { - activity.SetStatus(status.Value); - } + activity.SetStatus(statusCode, statusDescription); activity.SetEndTime(endTimestamp); activity.Stop(); diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs index 99db85423b..3957ffd5b1 100644 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NET6_0_OR_GREATER +#if NET using System.Diagnostics.Metrics; using System.Net; 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 1ea17a421d..d089eaa707 100644 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj @@ -1,9 +1,8 @@ + Unit test project for OpenTelemetry .NET Core hosting library $(TargetFrameworksForTests) - - disable $(DefineConstants);BUILDING_HOSTING_TESTS @@ -38,8 +37,7 @@ - - runtime; build; native; contentfiles; analyzers - + + diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs index 0753220b6c..16ee08af2c 100644 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs @@ -129,7 +129,7 @@ public async Task AddOpenTelemetry_WithTracing_HostConfigurationHonoredTest() var builder = new HostBuilder() .ConfigureAppConfiguration(builder => { - builder.AddInMemoryCollection(new Dictionary + builder.AddInMemoryCollection(new Dictionary { ["TEST_KEY"] = "TEST_KEY_VALUE", }); @@ -147,7 +147,7 @@ public async Task AddOpenTelemetry_WithTracing_HostConfigurationHonoredTest() var configuration = sp.GetRequiredService(); - var testKeyValue = configuration.GetValue("TEST_KEY", null); + var testKeyValue = configuration.GetValue("TEST_KEY", null); Assert.Equal("TEST_KEY_VALUE", testKeyValue); }); @@ -252,7 +252,7 @@ public async Task AddOpenTelemetry_WithMetrics_HostConfigurationHonoredTest() var builder = new HostBuilder() .ConfigureAppConfiguration(builder => { - builder.AddInMemoryCollection(new Dictionary + builder.AddInMemoryCollection(new Dictionary { ["TEST_KEY"] = "TEST_KEY_VALUE", }); @@ -270,7 +270,7 @@ public async Task AddOpenTelemetry_WithMetrics_HostConfigurationHonoredTest() var configuration = sp.GetRequiredService(); - var testKeyValue = configuration.GetValue("TEST_KEY", null); + var testKeyValue = configuration.GetValue("TEST_KEY", null); Assert.Equal("TEST_KEY_VALUE", testKeyValue); }); @@ -375,7 +375,7 @@ public void AddOpenTelemetry_WithLogging_HostConfigurationHonoredTest() var builder = new HostBuilder() .ConfigureAppConfiguration(builder => { - builder.AddInMemoryCollection(new Dictionary + builder.AddInMemoryCollection(new Dictionary { ["TEST_KEY"] = "TEST_KEY_VALUE", }); @@ -393,7 +393,7 @@ public void AddOpenTelemetry_WithLogging_HostConfigurationHonoredTest() var configuration = sp.GetRequiredService(); - var testKeyValue = configuration.GetValue("TEST_KEY", null); + var testKeyValue = configuration.GetValue("TEST_KEY", null); Assert.Equal("TEST_KEY_VALUE", testKeyValue); }); diff --git a/test/OpenTelemetry.Extensions.Propagators.Tests/B3PropagatorTest.cs b/test/OpenTelemetry.Extensions.Propagators.Tests/B3PropagatorTest.cs index 5373573ad6..15ed68c96d 100644 --- a/test/OpenTelemetry.Extensions.Propagators.Tests/B3PropagatorTest.cs +++ b/test/OpenTelemetry.Extensions.Propagators.Tests/B3PropagatorTest.cs @@ -25,8 +25,12 @@ public class B3PropagatorTest private static readonly Func, string, IEnumerable> Getter = (d, k) => { - d.TryGetValue(k, out var v); - return new string[] { v }; + if (d.TryGetValue(k, out var v)) + { + return [v]; + } + + return []; }; private readonly B3Propagator b3propagator = new(); @@ -354,6 +358,7 @@ public void ParseMissingSpanId_SingleHeader() [Fact] 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 }); diff --git a/test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTest.cs b/test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTest.cs index 9e040746dd..4e2b0486f6 100644 --- a/test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTest.cs +++ b/test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTest.cs @@ -77,7 +77,7 @@ public void ExtractReturnsOriginalContextIfGetterIsNull() var headers = new Dictionary(); // act - var result = new JaegerPropagator().Extract(propagationContext, headers, null); + var result = new JaegerPropagator().Extract(propagationContext, headers, null!); // assert Assert.Equal(propagationContext, result); @@ -183,7 +183,7 @@ public void InjectDoesNoopIfSetterIsNull() var headers = new Dictionary(); // act - new JaegerPropagator().Inject(propagationContext, headers, null); + new JaegerPropagator().Inject(propagationContext, headers, null!); // assert Assert.Empty(headers); 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 77eaa8a1f6..757c1d61ec 100644 --- a/test/OpenTelemetry.Extensions.Propagators.Tests/OpenTelemetry.Extensions.Propagators.Tests.csproj +++ b/test/OpenTelemetry.Extensions.Propagators.Tests/OpenTelemetry.Extensions.Propagators.Tests.csproj @@ -2,8 +2,6 @@ $(TargetFrameworksForTests) - - disable diff --git a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/Dockerfile b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/Dockerfile index 9723867f4f..fc50a196ec 100644 --- a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/Dockerfile +++ b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/Dockerfile @@ -2,8 +2,8 @@ # This should be run from the root of the repo: # docker build --file test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/Dockerfile . -ARG BUILD_SDK_VERSION=8.0 -ARG TEST_SDK_VERSION=8.0 +ARG BUILD_SDK_VERSION=9.0 +ARG TEST_SDK_VERSION=9.0 FROM ubuntu AS w3c #Install git @@ -13,7 +13,7 @@ RUN git clone --branch level-1 https://github.com/w3c/trace-context.git FROM mcr.microsoft.com/dotnet/sdk:${BUILD_SDK_VERSION} AS build ARG PUBLISH_CONFIGURATION=Release -ARG PUBLISH_FRAMEWORK=net8.0 +ARG PUBLISH_FRAMEWORK=net9.0 WORKDIR /repo COPY . ./ WORKDIR "/repo/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests" @@ -26,7 +26,9 @@ COPY --from=w3c /w3c . RUN apt-get update \ && apt-get install -y python3-pip python3-dev \ && cd /usr/local/bin \ - && ln -s /usr/bin/python3 python \ - && pip3 install --upgrade pip \ - && pip3 install aiohttp + && 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 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 8d9e8800c5..fb9e640731 100644 --- a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/OpenTelemetry.Instrumentation.W3cTraceContext.Tests.csproj +++ b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/OpenTelemetry.Instrumentation.W3cTraceContext.Tests.csproj @@ -3,8 +3,6 @@ Unit test project for OpenTelemetry ASP.NET Core instrumentation for W3C Trace Context Trace $(TargetFrameworksForAspNetCoreTests) - - disable diff --git a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/W3CTraceContextTests.cs b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/W3CTraceContextTests.cs index 323b35ecba..95f8d9b551 100644 --- a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/W3CTraceContextTests.cs +++ b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/W3CTraceContextTests.cs @@ -20,8 +20,7 @@ public class W3CTraceContextTests : IDisposable To run the tests, invoke docker-compose.yml 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 */ - private const string W3cTraceContextEnvVarName = "OTEL_W3CTRACECONTEXT"; - private static readonly Version AspNetCoreHostingVersion = typeof(Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory).Assembly.GetName().Version; + private const string W3CTraceContextEnvVarName = "OTEL_W3CTRACECONTEXT"; private readonly HttpClient httpClient = new(); private readonly ITestOutputHelper output; @@ -31,13 +30,13 @@ public W3CTraceContextTests(ITestOutputHelper output) } [Trait("CategoryName", "W3CTraceContextTests")] - [SkipUnlessEnvVarFoundTheory(W3cTraceContextEnvVarName)] + [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) { // configure SDK - using var tracerprovider = Sdk.CreateTracerProviderBuilder() + using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddAspNetCoreInstrumentation() .Build(); @@ -51,13 +50,11 @@ public void W3CTraceContextTestSuiteAsync(string value) { foreach (var argument in data) { - using var request = new HttpRequestMessage(HttpMethod.Post, argument.Url) - { - Content = new StringContent( - JsonSerializer.Serialize(argument.Arguments), - Encoding.UTF8, - "application/json"), - }; + using var request = new HttpRequestMessage(HttpMethod.Post, argument.Url); + request.Content = new StringContent( + JsonSerializer.Serialize(argument.Arguments), + Encoding.UTF8, + "application/json"); await this.httpClient.SendAsync(request); } } @@ -69,7 +66,7 @@ public void W3CTraceContextTestSuiteAsync(string value) return result; }); - app.RunAsync(); + app.RunAsync("http://localhost:5000/"); string result = RunCommand("python", "trace-context/test/test.py http://localhost:5000/"); @@ -79,19 +76,7 @@ public void W3CTraceContextTestSuiteAsync(string value) this.output.WriteLine("result:" + result); // Assert on the last line - - // TODO: Investigate failures on .NET6 vs .NET7. To see the details - // run the tests with console logger (done automatically by the CI - // jobs). - - if (AspNetCoreHostingVersion.Major <= 6) - { - Assert.StartsWith("FAILED (failures=3)", lastLine); - } - else - { - Assert.StartsWith("OK", lastLine); - } + Assert.StartsWith("OK", lastLine); } public void Dispose() @@ -137,9 +122,9 @@ private static string ParseLastLine(string output) public class Data { [JsonPropertyName("url")] - public string Url { get; set; } + public string? Url { get; set; } [JsonPropertyName("arguments")] - public Data[] Arguments { get; set; } + public Data[]? Arguments { get; set; } } } diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/IntegrationTests.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/IntegrationTests.cs index d8f93b2df0..cb592e8906 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/IntegrationTests.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/IntegrationTests.cs @@ -76,7 +76,7 @@ public void WithActivities( } } - var expectedExportedSpans = new string[] + var expectedExportedSpans = new string?[] { childActivitySamplingDecision == SamplingDecision.RecordAndSample ? ChildActivityName : null, shimSamplingDecision == SamplingDecision.RecordAndSample ? ShimActivityName : null, 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 7e08df554f..d21c157d1b 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/OpenTelemetry.Shims.OpenTracing.Tests.csproj +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/OpenTelemetry.Shims.OpenTracing.Tests.csproj @@ -1,22 +1,28 @@ + Unit test project for OpenTelemetry.Shims.OpenTracing $(TargetFrameworksForTests) - - disable + $(DefineConstants);BUILDING_USING_PROJECTS + - - runtime; build; native; contentfiles; analyzers - + - - + + + + + + + + + diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/ScopeManagerShimTests.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/ScopeManagerShimTests.cs index 6f4ca876c5..0e24b2b167 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/ScopeManagerShimTests.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/ScopeManagerShimTests.cs @@ -33,6 +33,7 @@ public void Active_IsNotNull() Assert.NotNull(scope); var activeScope = shim.Active; + Assert.NotNull(activeScope); Assert.Equal(scope.Span.Context.SpanId, activeScope.Span.Context.SpanId); openTracingSpan.Finish(); } @@ -64,6 +65,7 @@ public void Activate() #endif spanShim.Finish(); + Assert.NotNull(spanShim.Span.Activity); Assert.NotEqual(default, spanShim.Span.Activity.Duration); } } diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanBuilderShimTests.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanBuilderShimTests.cs index ce546c868b..b99bcb2f89 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanBuilderShimTests.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanBuilderShimTests.cs @@ -18,8 +18,8 @@ public class SpanBuilderShimTests public void CtorArgumentValidation() { var tracer = TracerProvider.Default.GetTracer(TracerName); - Assert.Throws(() => new SpanBuilderShim(null, "foo")); - Assert.Throws(() => new SpanBuilderShim(tracer, null)); + Assert.Throws(() => new SpanBuilderShim(null!, "foo")); + Assert.Throws(() => new SpanBuilderShim(tracer, null!)); } [Fact] @@ -36,7 +36,7 @@ public void IgnoreActiveSpan() // build var spanShim = (SpanShim)shim.Start(); - + Assert.NotNull(spanShim.Span.Activity); Assert.Equal("foo", spanShim.Span.Activity.OperationName); } @@ -51,7 +51,7 @@ public void StartWithExplicitTimestamp() // build var spanShim = (SpanShim)shim.Start(); - + Assert.NotNull(spanShim.Span.Activity); Assert.Equal(startTimestamp, spanShim.Span.Activity.StartTimeUtc); } @@ -62,11 +62,12 @@ public void AsChildOf_WithNullSpan() var shim = new SpanBuilderShim(tracer, "foo"); // Add a null parent - shim.AsChildOf((global::OpenTracing.ISpan)null); + shim.AsChildOf((global::OpenTracing.ISpan?)null); // build var spanShim = (SpanShim)shim.Start(); + Assert.NotNull(spanShim.Span.Activity); Assert.Equal("foo", spanShim.Span.Activity.OperationName); Assert.Null(spanShim.Span.Activity.Parent); } @@ -84,6 +85,7 @@ public void AsChildOf_WithSpan() // build var spanShim = (SpanShim)shim.Start(); + Assert.NotNull(spanShim.Span.Activity); Assert.Equal("foo", spanShim.Span.Activity.OperationName); Assert.NotNull(spanShim.Span.Activity.ParentId); } @@ -101,12 +103,14 @@ public void Start_ActivityOperationRootSpanChecks() var shim = new SpanBuilderShim(tracer, "foo"); var spanShim1 = (SpanShim)shim.Start(); + Assert.NotNull(spanShim1.Span.Activity); Assert.Equal("foo", spanShim1.Span.Activity.OperationName); // mis-matched root operation name shim = new SpanBuilderShim(tracer, "foo"); var spanShim2 = (SpanShim)shim.Start(); + Assert.NotNull(spanShim2.Span.Activity); Assert.Equal("foo", spanShim2.Span.Activity.OperationName); Assert.Equal(spanShim1.Context.TraceId, spanShim2.Context.TraceId); } @@ -126,6 +130,7 @@ public void AsChildOf_MultipleCallsWithSpan() // build var spanShim = (SpanShim)shim.Start(); + Assert.NotNull(spanShim.Span.Activity); Assert.Equal("foo", spanShim.Span.Activity.OperationName); Assert.Contains(spanShim.Context.TraceId, spanShim.Span.Activity.TraceId.ToHexString()); @@ -139,12 +144,13 @@ public void AsChildOf_WithNullSpanContext() var shim = new SpanBuilderShim(tracer, "foo"); // Add a null parent - shim.AsChildOf((global::OpenTracing.ISpanContext)null); + shim.AsChildOf((global::OpenTracing.ISpanContext?)null); // build var spanShim = (SpanShim)shim.Start(); // should be no parent. + Assert.NotNull(spanShim.Span.Activity); Assert.Null(spanShim.Span.Activity.Parent); } @@ -161,6 +167,7 @@ public void AsChildOfWithSpanContext() // build var spanShim = (SpanShim)shim.Start(); + Assert.NotNull(spanShim.Span.Activity); Assert.NotNull(spanShim.Span.Activity.ParentId); } @@ -182,7 +189,7 @@ public void AsChildOf_MultipleCallsWithSpanContext() // build 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.Equal(spanContext2.SpanId, spanShim.Span.Activity.Links.First().Context.SpanId.ToHexString()); @@ -200,6 +207,7 @@ public void WithTag_KeyIsSpanKindStringValue() var spanShim = (SpanShim)shim.Start(); // Not an attribute + Assert.NotNull(spanShim.Span.Activity); Assert.Empty(spanShim.Span.Activity.Tags); Assert.Equal("foo", spanShim.Span.Activity.OperationName); Assert.Equal(ActivityKind.Client, spanShim.Span.Activity.Kind); @@ -216,8 +224,19 @@ public void WithTag_KeyIsErrorStringValue() // build var spanShim = (SpanShim)shim.Start(); - // Span status should be set - Assert.Equal(Status.Error, spanShim.Span.Activity.GetStatus()); + // Legacy span status tag should be set + Assert.NotNull(spanShim.Span.Activity); + Assert.Equal("ERROR", spanShim.Span.Activity.GetTagValue(SpanAttributeConstants.StatusCodeKey)); + + if (VersionHelper.IsApiVersionGreaterThanOrEqualTo(1, 10)) + { + // Activity status code should also be set + Assert.Equal(ActivityStatusCode.Error, spanShim.Span.Activity.Status); + } + else + { + Assert.Equal(ActivityStatusCode.Unset, spanShim.Span.Activity.Status); + } } [Fact] @@ -226,17 +245,18 @@ public void WithTag_KeyIsNullStringValue() var tracer = TracerProvider.Default.GetTracer(TracerName); var shim = new SpanBuilderShim(tracer, "foo"); - shim.WithTag((string)null, "unused"); + shim.WithTag((string)null!, "unused"); // build var spanShim = (SpanShim)shim.Start(); // Null key was ignored + Assert.NotNull(spanShim.Span.Activity); Assert.Empty(spanShim.Span.Activity.Tags); } [Fact] - public void WithTag_ValueIsNullStringValue() + public void WithTag_ValueIsIgnoredWhenNull() { var tracer = TracerProvider.Default.GetTracer(TracerName); var shim = new SpanBuilderShim(tracer, "foo"); @@ -247,8 +267,8 @@ public void WithTag_ValueIsNullStringValue() var spanShim = (SpanShim)shim.Start(); // Null value was turned into string.empty - Assert.Equal("foo", spanShim.Span.Activity.Tags.First().Key); - Assert.Equal(string.Empty, spanShim.Span.Activity.Tags.First().Value); + Assert.NotNull(spanShim.Span.Activity); + Assert.Empty(spanShim.Span.Activity.TagObjects); } [Fact] @@ -262,8 +282,18 @@ public void WithTag_KeyIsErrorBoolValue() // build var spanShim = (SpanShim)shim.Start(); - // Span status should be set - Assert.Equal(Status.Error, spanShim.Span.Activity.GetStatus()); + // Legacy span status tag should be set + Assert.NotNull(spanShim.Span.Activity); + Assert.Equal("ERROR", spanShim.Span.Activity.GetTagValue(SpanAttributeConstants.StatusCodeKey)); + if (VersionHelper.IsApiVersionGreaterThanOrEqualTo(1, 10)) + { + // Activity status code should also be set + Assert.Equal(ActivityStatusCode.Error, spanShim.Span.Activity.Status); + } + else + { + Assert.Equal(ActivityStatusCode.Unset, spanShim.Span.Activity.Status); + } } [Fact] @@ -284,7 +314,8 @@ public void WithTag_VariousValueTypes() var spanShim = (SpanShim)shim.Start(); // Just verify the count - Assert.Equal(7, spanShim.Span.Activity.Tags.Count()); + Assert.NotNull(spanShim.Span.Activity); + Assert.Equal(7, spanShim.Span.Activity.TagObjects.Count()); } [Fact] @@ -299,6 +330,7 @@ public void Start() // Just check the return value is a SpanShim and that the underlying OpenTelemetry Span. // There is nothing left to verify because the rest of the tests were already calling .Start() prior to verification. Assert.NotNull(span); + Assert.NotNull(span.Span.Activity); Assert.Equal("foo", span.Span.Activity.OperationName); } @@ -317,6 +349,7 @@ public void Start_UnderAspNetCoreInstrumentation() Assert.NotNull(spanShim); var telemetrySpan = spanShim.Span; + Assert.NotNull(telemetrySpan.Activity); Assert.Same(telemetrySpan.Activity, Activity.Current); Assert.Same(parentSpan, telemetrySpan.Activity.Parent); diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanShimTests.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanShimTests.cs index 80178f405b..66e8b63652 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanShimTests.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanShimTests.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Diagnostics; using OpenTelemetry.Trace; using OpenTracing.Tag; using Xunit; @@ -16,7 +17,7 @@ public class SpanShimTests [Fact] public void CtorArgumentValidation() { - Assert.Throws(() => new SpanShim(null)); + Assert.Throws(() => new SpanShim(null!)); } [Fact] @@ -34,9 +35,9 @@ public void FinishSpan() { var tracer = TracerProvider.Default.GetTracer(TracerName); var shim = new SpanShim(tracer.StartSpan(SpanName)); - shim.Finish(); + Assert.NotNull(shim.Span.Activity); Assert.NotEqual(default, shim.Span.Activity.Duration); } @@ -57,7 +58,7 @@ public void FinishSpanUsingSpecificTimestamp() var endTime = DateTimeOffset.UtcNow; shim.Finish(endTime); - Assert.Equal(endTime - shim.Span.Activity.StartTimeUtc, shim.Span.Activity.Duration); + Assert.Equal(endTime - shim.Span.Activity!.StartTimeUtc, shim.Span.Activity.Duration); } [Fact] @@ -67,10 +68,10 @@ public void SetOperationName() var shim = new SpanShim(tracer.StartSpan(SpanName)); // parameter validation - Assert.Throws(() => shim.SetOperationName(null)); + Assert.Throws(() => shim.SetOperationName(null!)); shim.SetOperationName("bar"); - Assert.Equal("bar", shim.Span.Activity.DisplayName); + Assert.Equal("bar", shim.Span.Activity!.DisplayName); } [Fact] @@ -80,7 +81,7 @@ public void GetBaggageItem() var shim = new SpanShim(tracer.StartSpan(SpanName)); // parameter validation - Assert.Throws(() => shim.GetBaggageItem(null)); + Assert.Throws(() => shim.GetBaggageItem(null!)); // TODO - Method not implemented } @@ -93,8 +94,8 @@ public void Log() shim.Log("foo"); - Assert.Single(shim.Span.Activity.Events); - var first = shim.Span.Activity.Events.First(); + Assert.NotNull(shim.Span.Activity); + var first = Assert.Single(shim.Span.Activity.Events); Assert.Equal("foo", first.Name); Assert.False(first.Tags.Any()); } @@ -108,8 +109,8 @@ public void LogWithExplicitTimestamp() var now = DateTimeOffset.UtcNow; shim.Log(now, "foo"); - Assert.Single(shim.Span.Activity.Events); - var first = shim.Span.Activity.Events.First(); + Assert.NotNull(shim.Span.Activity); + var first = Assert.Single(shim.Span.Activity.Events); Assert.Equal("foo", first.Name); Assert.Equal(now, first.Timestamp); Assert.False(first.Tags.Any()); @@ -121,19 +122,21 @@ public void LogUsingFields() var tracer = TracerProvider.Default.GetTracer(TracerName); var shim = new SpanShim(tracer.StartSpan(SpanName)); - Assert.Throws(() => shim.Log((IEnumerable>)null)); + Assert.Throws(() => shim.Log((IEnumerable>)null!)); shim.Log(new List> { - new KeyValuePair("foo", "bar"), + new("foo", "bar"), }); // "event" is a special event name shim.Log(new List> { - new KeyValuePair("event", "foo"), + new("event", "foo"), }); + Assert.NotNull(shim.Span.Activity); + var first = shim.Span.Activity.Events.FirstOrDefault(); var last = shim.Span.Activity.Events.LastOrDefault(); @@ -152,20 +155,21 @@ public void LogUsingFieldsWithExplicitTimestamp() var tracer = TracerProvider.Default.GetTracer(TracerName); var shim = new SpanShim(tracer.StartSpan(SpanName)); - Assert.Throws(() => shim.Log((IEnumerable>)null)); + Assert.Throws(() => shim.Log((IEnumerable>)null!)); var now = DateTimeOffset.UtcNow; shim.Log(now, new List> { - new KeyValuePair("foo", "bar"), + new("foo", "bar"), }); // "event" is a special event name shim.Log(now, new List> { - new KeyValuePair("event", "foo"), + new("event", "foo"), }); + Assert.NotNull(shim.Span.Activity); Assert.Equal(2, shim.Span.Activity.Events.Count()); var first = shim.Span.Activity.Events.First(); var last = shim.Span.Activity.Events.Last(); @@ -185,13 +189,14 @@ public void SetTagStringValue() var tracer = TracerProvider.Default.GetTracer(TracerName); var shim = new SpanShim(tracer.StartSpan(SpanName)); - Assert.Throws(() => shim.SetTag((string)null, "foo")); + Assert.Throws(() => shim.SetTag((string)null!, "foo")); shim.SetTag("foo", "bar"); - Assert.Single(shim.Span.Activity.Tags); - Assert.Equal("foo", shim.Span.Activity.Tags.First().Key); - Assert.Equal("bar", shim.Span.Activity.Tags.First().Value); + Assert.NotNull(shim.Span.Activity); + var first = Assert.Single(shim.Span.Activity.Tags); + Assert.Equal("foo", first.Key); + Assert.Equal("bar", first.Value); } [Fact] @@ -200,19 +205,46 @@ public void SetTagBoolValue() var tracer = TracerProvider.Default.GetTracer(TracerName); var shim = new SpanShim(tracer.StartSpan(SpanName)); - Assert.Throws(() => shim.SetTag((string)null, true)); + Assert.Throws(() => shim.SetTag((string)null!, true)); shim.SetTag("foo", true); shim.SetTag(Tags.Error.Key, true); - Assert.Equal("foo", shim.Span.Activity.TagObjects.First().Key); - Assert.True((bool)shim.Span.Activity.TagObjects.First().Value); + Assert.NotNull(shim.Span.Activity); + var first = shim.Span.Activity.TagObjects.First(); + Assert.Equal("foo", first.Key); + Assert.NotNull(first.Value); + Assert.True((bool)first.Value); // A boolean tag named "error" is a special case that must be checked - Assert.Equal(Status.Error, shim.Span.Activity.GetStatus()); + + // Legacy span status tag should be set + Assert.Equal("ERROR", shim.Span.Activity.GetTagValue(SpanAttributeConstants.StatusCodeKey)); + + if (VersionHelper.IsApiVersionGreaterThanOrEqualTo(1, 10)) + { + // Activity status code should also be set + Assert.Equal(ActivityStatusCode.Error, shim.Span.Activity.Status); + } + else + { + Assert.Equal(ActivityStatusCode.Unset, shim.Span.Activity.Status); + } shim.SetTag(Tags.Error.Key, false); - Assert.Equal(Status.Ok, shim.Span.Activity.GetStatus()); + + // Legacy span status tag should be set + Assert.Equal("OK", shim.Span.Activity.GetTagValue(SpanAttributeConstants.StatusCodeKey)); + + if (VersionHelper.IsApiVersionGreaterThanOrEqualTo(1, 10)) + { + // Activity status code should also be set + Assert.Equal(ActivityStatusCode.Ok, shim.Span.Activity.Status); + } + else + { + Assert.Equal(ActivityStatusCode.Unset, shim.Span.Activity.Status); + } } [Fact] @@ -221,13 +253,14 @@ public void SetTagIntValue() var tracer = TracerProvider.Default.GetTracer(TracerName); var shim = new SpanShim(tracer.StartSpan(SpanName)); - Assert.Throws(() => shim.SetTag((string)null, 1)); + Assert.Throws(() => shim.SetTag((string)null!, 1)); shim.SetTag("foo", 1); + Assert.NotNull(shim.Span.Activity); Assert.Single(shim.Span.Activity.TagObjects); Assert.Equal("foo", shim.Span.Activity.TagObjects.First().Key); - Assert.Equal(1L, (int)shim.Span.Activity.TagObjects.First().Value); + Assert.Equal(1L, (int)shim.Span.Activity.TagObjects.First().Value!); } [Fact] @@ -236,34 +269,15 @@ public void SetTagDoubleValue() var tracer = TracerProvider.Default.GetTracer(TracerName); var shim = new SpanShim(tracer.StartSpan(SpanName)); - Assert.Throws(() => shim.SetTag(null, 1D)); + Assert.Throws(() => shim.SetTag(null!, 1D)); shim.SetTag("foo", 1D); - Assert.Single(shim.Span.Activity.TagObjects); - Assert.Equal("foo", shim.Span.Activity.TagObjects.First().Key); - Assert.Equal(1, (double)shim.Span.Activity.TagObjects.First().Value); - } - - [Fact] - public void SetTagBooleanTagValue() - { - var tracer = TracerProvider.Default.GetTracer(TracerName); - var shim = new SpanShim(tracer.StartSpan(SpanName)); - - Assert.Throws(() => shim.SetTag((BooleanTag)null, true)); - - shim.SetTag(new BooleanTag("foo"), true); - shim.SetTag(new BooleanTag(Tags.Error.Key), true); - - Assert.Equal("foo", shim.Span.Activity.TagObjects.First().Key); - Assert.True((bool)shim.Span.Activity.TagObjects.First().Value); - - // A boolean tag named "error" is a special case that must be checked - Assert.Equal(Status.Error, shim.Span.Activity.GetStatus()); - - shim.SetTag(Tags.Error.Key, false); - Assert.Equal(Status.Ok, shim.Span.Activity.GetStatus()); + Assert.NotNull(shim.Span.Activity); + var first = Assert.Single(shim.Span.Activity.TagObjects); + Assert.Equal("foo", first.Key); + Assert.NotNull(first.Value); + Assert.Equal(1, (double)first.Value); } [Fact] @@ -272,13 +286,14 @@ public void SetTagStringTagValue() var tracer = TracerProvider.Default.GetTracer(TracerName); var shim = new SpanShim(tracer.StartSpan(SpanName)); - Assert.Throws(() => shim.SetTag((StringTag)null, "foo")); + Assert.Throws(() => shim.SetTag((StringTag)null!, "foo")); shim.SetTag(new StringTag("foo"), "bar"); - Assert.Single(shim.Span.Activity.Tags); - Assert.Equal("foo", shim.Span.Activity.Tags.First().Key); - Assert.Equal("bar", shim.Span.Activity.Tags.First().Value); + Assert.NotNull(shim.Span.Activity); + var first = Assert.Single(shim.Span.Activity.Tags); + Assert.Equal("foo", first.Key); + Assert.Equal("bar", first.Value); } [Fact] @@ -287,13 +302,15 @@ public void SetTagIntTagValue() var tracer = TracerProvider.Default.GetTracer(TracerName); var shim = new SpanShim(tracer.StartSpan(SpanName)); - Assert.Throws(() => shim.SetTag((IntTag)null, 1)); + Assert.Throws(() => shim.SetTag((IntTag)null!, 1)); shim.SetTag(new IntTag("foo"), 1); - Assert.Single(shim.Span.Activity.TagObjects); - Assert.Equal("foo", shim.Span.Activity.TagObjects.First().Key); - Assert.Equal(1L, (int)shim.Span.Activity.TagObjects.First().Value); + Assert.NotNull(shim.Span.Activity); + var first = Assert.Single(shim.Span.Activity.TagObjects); + Assert.Equal("foo", first.Key); + Assert.NotNull(first.Value); + Assert.Equal(1L, (int)first.Value); } [Fact] @@ -302,17 +319,21 @@ public void SetTagIntOrStringTagValue() var tracer = TracerProvider.Default.GetTracer(TracerName); var shim = new SpanShim(tracer.StartSpan(SpanName)); - Assert.Throws(() => shim.SetTag((IntOrStringTag)null, "foo")); + Assert.Throws(() => shim.SetTag((IntOrStringTag)null!, "foo")); shim.SetTag(new IntOrStringTag("foo"), 1); shim.SetTag(new IntOrStringTag("bar"), "baz"); + Assert.NotNull(shim.Span.Activity); Assert.Equal(2, shim.Span.Activity.TagObjects.Count()); - Assert.Equal("foo", shim.Span.Activity.TagObjects.First().Key); - Assert.Equal(1L, (int)shim.Span.Activity.TagObjects.First().Value); + var first = shim.Span.Activity.TagObjects.First(); + Assert.Equal("foo", first.Key); + Assert.NotNull(first.Value); + Assert.Equal(1L, (int)first.Value); - Assert.Equal("bar", shim.Span.Activity.TagObjects.Last().Key); - Assert.Equal("baz", shim.Span.Activity.TagObjects.Last().Value); + var second = shim.Span.Activity.TagObjects.Last(); + Assert.Equal("bar", second.Key); + Assert.Equal("baz", second.Value); } } diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs index 578ed8ccbf..64f0895942 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs @@ -17,10 +17,10 @@ public class TracerShimTests public void CtorArgumentValidation() { // null tracer provider and text format - Assert.Throws(() => new TracerShim(null, null)); + Assert.Throws(() => new TracerShim(null!, null)); // null tracer provider - Assert.Throws(() => new TracerShim(null, new TraceContextPropagator())); + Assert.Throws(() => new TracerShim(null!, new TraceContextPropagator())); } [Fact] @@ -50,10 +50,10 @@ public void Inject_ArgumentValidation() var testFormat = new TestFormatTextMap(); var testCarrier = new TestTextMap(); - Assert.Throws(() => shim.Inject(null, testFormat, testCarrier)); + Assert.Throws(() => shim.Inject(null!, testFormat, testCarrier)); Assert.Throws(() => shim.Inject(new TestSpanContext(), testFormat, testCarrier)); - Assert.Throws(() => shim.Inject(spanContextShim, null, testCarrier)); - Assert.Throws(() => shim.Inject(spanContextShim, testFormat, null)); + Assert.Throws(() => shim.Inject(spanContextShim, null!, testCarrier)); + Assert.Throws(() => shim.Inject(spanContextShim, testFormat!, null)); } [Fact] @@ -76,8 +76,8 @@ public void Extract_ArgumentValidation() { var shim = new TracerShim(TracerProvider.Default, new TraceContextPropagator()); - Assert.Throws(() => shim.Extract(null, new TestTextMap())); - Assert.Throws(() => shim.Extract(new TestFormatTextMap(), null)); + Assert.Throws(() => shim.Extract(null!, new TestTextMap())); + Assert.Throws(() => shim.Extract(new TestFormatTextMap()!, null)); } [Fact] @@ -129,7 +129,7 @@ public void InjectExtract_TextMap_Ok() // then extract var extractedSpanContext = shim.Extract(BuiltinFormats.TextMap, carrier); - AssertOpenTracerSpanContextEqual(spanContextShim, extractedSpanContext); + AssertOpenTracerSpanContextEqual(spanContextShim, extractedSpanContext!); } private static void AssertOpenTracerSpanContextEqual(ISpanContext source, ISpanContext target) diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/VersionHelper.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/VersionHelper.cs new file mode 100644 index 0000000000..65c96a2e50 --- /dev/null +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/VersionHelper.cs @@ -0,0 +1,40 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using NuGet.Versioning; +using OpenTelemetry.Internal; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Shims.OpenTracing.Tests; + +internal static class VersionHelper +{ +#if BUILDING_USING_PROJECTS + private static NuGetVersion? apiVersion = new(100, 0, 0); +#else + private static NuGetVersion? apiVersion; +#endif + + public static NuGetVersion ApiVersion + { + get + { + return apiVersion ??= ResolveApiVersion(); + + static NuGetVersion ResolveApiVersion() + { + if (!typeof(TracerProvider).Assembly.TryGetPackageVersion(out var packageVersion)) + { + throw new InvalidOperationException("OpenTelemetry.Api package version could not be resolved"); + } + + return NuGetVersion.Parse(packageVersion); + } + } + } + + public static bool IsApiVersionGreaterThanOrEqualTo(int major, int minor) + { + return ApiVersion >= new NuGetVersion(major, minor, 0); + } +} diff --git a/test/OpenTelemetry.Tests.Stress.Logs/LoggerExtensions.cs b/test/OpenTelemetry.Tests.Stress.Logs/LoggerExtensions.cs new file mode 100644 index 0000000000..ae3c143666 --- /dev/null +++ b/test/OpenTelemetry.Tests.Stress.Logs/LoggerExtensions.cs @@ -0,0 +1,18 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.Logging; + +namespace OpenTelemetry.Tests.Stress; + +internal static partial class LoggerExtensions +{ + [LoggerMessage(LogLevel.Critical, "A `{productType}` recall notice was published for `{brandName} {productDescription}` produced by `{companyName}` ({recallReasonDescription}).")] + public static partial void FoodRecallNotice( + this ILogger logger, + string brandName, + string productDescription, + string productType, + string recallReasonDescription, + string companyName); +} diff --git a/test/OpenTelemetry.Tests.Stress.Logs/Program.cs b/test/OpenTelemetry.Tests.Stress.Logs/Program.cs index dececdacb5..f8ecff5064 100644 --- a/test/OpenTelemetry.Tests.Stress.Logs/Program.cs +++ b/test/OpenTelemetry.Tests.Stress.Logs/Program.cs @@ -23,9 +23,9 @@ public LogsStressTest(StressTestOptions options) { this.loggerFactory = LoggerFactory.Create(builder => { - builder.AddOpenTelemetry(options => + builder.AddOpenTelemetry(logging => { - options.AddProcessor(new DummyProcessor()); + logging.AddProcessor(new DummyProcessor()); }); }); @@ -34,12 +34,12 @@ public LogsStressTest(StressTestOptions options) protected override void RunWorkItemInParallel() { - this.logger.Log( - logLevel: LogLevel.Information, - eventId: 2, - state: Payload, - exception: null, - formatter: (state, ex) => string.Empty); + this.logger.FoodRecallNotice( + brandName: "Contoso", + productDescription: "Salads", + productType: "Food & Beverages", + recallReasonDescription: "due to a possible health risk from Listeria monocytogenes", + companyName: "Contoso Fresh Vegetables, Inc."); } protected override void Dispose(bool isDisposing) diff --git a/test/OpenTelemetry.Tests.Stress/OpenTelemetry.Tests.Stress.csproj b/test/OpenTelemetry.Tests.Stress/OpenTelemetry.Tests.Stress.csproj index 01af1c993a..9e6464cf48 100644 --- a/test/OpenTelemetry.Tests.Stress/OpenTelemetry.Tests.Stress.csproj +++ b/test/OpenTelemetry.Tests.Stress/OpenTelemetry.Tests.Stress.csproj @@ -1,17 +1,19 @@ + Exe $(TargetFrameworksForTests) + true - + diff --git a/test/OpenTelemetry.Tests/Internal/AssemblyVersionExtensionsTests.cs b/test/OpenTelemetry.Tests/Internal/AssemblyVersionExtensionsTests.cs index 22cf81be0e..b009846c8d 100644 --- a/test/OpenTelemetry.Tests/Internal/AssemblyVersionExtensionsTests.cs +++ b/test/OpenTelemetry.Tests/Internal/AssemblyVersionExtensionsTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Reflection; using Xunit; diff --git a/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs b/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs index 2e0e422552..4f19a099e3 100644 --- a/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs +++ b/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs @@ -22,14 +22,6 @@ public void CheckCapacity() Assert.Equal(capacity, circularBuffer.Capacity); } - [Fact] - public void CheckNullValueWhenAdding() - { - int capacity = 1; - var circularBuffer = new CircularBuffer(capacity); - Assert.Throws(() => circularBuffer.Add(null)); - } - [Fact] public void CheckValueWhenAdding() { diff --git a/test/OpenTelemetry.Tests/Internal/JsonStringArrayTagWriterTests.cs b/test/OpenTelemetry.Tests/Internal/JsonStringArrayTagWriterTests.cs index 0e7a8d45fa..fbdc3da9cf 100644 --- a/test/OpenTelemetry.Tests/Internal/JsonStringArrayTagWriterTests.cs +++ b/test/OpenTelemetry.Tests/Internal/JsonStringArrayTagWriterTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Text; using Xunit; diff --git a/test/OpenTelemetry.Tests/Internal/PeriodicExportingMetricReaderHelperTests.cs b/test/OpenTelemetry.Tests/Internal/PeriodicExportingMetricReaderHelperTests.cs index d0da994e01..f1d5f84327 100644 --- a/test/OpenTelemetry.Tests/Internal/PeriodicExportingMetricReaderHelperTests.cs +++ b/test/OpenTelemetry.Tests/Internal/PeriodicExportingMetricReaderHelperTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using Microsoft.Extensions.Configuration; using OpenTelemetry.Exporter; using OpenTelemetry.Metrics; diff --git a/test/OpenTelemetry.Tests/Internal/PooledListTest.cs b/test/OpenTelemetry.Tests/Internal/PooledListTest.cs index eccbef450a..6db06b6fad 100644 --- a/test/OpenTelemetry.Tests/Internal/PooledListTest.cs +++ b/test/OpenTelemetry.Tests/Internal/PooledListTest.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 using System.Collections; -using System.Reflection; using Xunit; @@ -44,17 +43,9 @@ public void Verify_CreateAddClear() [Fact] public void Verify_AllocatedSize() { - int GetLastAllocatedSize(PooledList pooledList) - { - var value = typeof(PooledList) - .GetField("lastAllocatedSize", BindingFlags.NonPublic | BindingFlags.Static) - .GetValue(pooledList); - return (int)value; - } - var pooledList = PooledList.Create(); - var size = GetLastAllocatedSize(pooledList); + var size = PooledList.LastAllocatedSize; Assert.Equal(64, size); // The Add() method has a condition to double the size of the buffer @@ -65,7 +56,7 @@ int GetLastAllocatedSize(PooledList pooledList) PooledList.Add(ref pooledList, i); } - size = GetLastAllocatedSize(pooledList); + size = PooledList.LastAllocatedSize; Assert.Equal(128, size); } diff --git a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigParserTest.cs b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigParserTest.cs index 9249728690..2cb86dcbc1 100644 --- a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigParserTest.cs +++ b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigParserTest.cs @@ -69,7 +69,7 @@ public void SelfDiagnosticsConfigParser_TryParseLogLevel() ""FileSize"": 1024, ""LogLevel"": ""Error"" }"; - Assert.True(SelfDiagnosticsConfigParser.TryParseLogLevel(configJson, out string logLevelString)); + Assert.True(SelfDiagnosticsConfigParser.TryParseLogLevel(configJson, out string? logLevelString)); Assert.Equal("Error", logLevelString); } } diff --git a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigRefresherTest.cs b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigRefresherTest.cs index 10c0be18fa..939e64671f 100644 --- a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigRefresherTest.cs +++ b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigRefresherTest.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using System.Text; using OpenTelemetry.Tests; diff --git a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs index 58777971b5..0ee7d0dc71 100644 --- a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs +++ b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs @@ -21,7 +21,7 @@ public void SelfDiagnosticsEventListener_constructor_Invalid_Input() // no configRefresher object Assert.Throws(() => { - _ = new SelfDiagnosticsEventListener(EventLevel.Error, null); + _ = new SelfDiagnosticsEventListener(EventLevel.Error, null!); }); } @@ -124,7 +124,21 @@ public void SelfDiagnosticsEventListener_EmitEvent_OmitAsConfigured() using FileStream file = File.Open(LOGFILEPATH, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); var buffer = new byte[256]; - file.Read(buffer, 0, buffer.Length); + + int bytesRead = 0; + int totalBytesRead = 0; + + while (totalBytesRead < buffer.Length) + { + bytesRead = file.Read(buffer, totalBytesRead, buffer.Length - totalBytesRead); + if (bytesRead == 0) + { + break; + } + + totalBytesRead += bytesRead; + } + Assert.Equal('\0', (char)buffer[0]); } @@ -256,8 +270,22 @@ private static void AssertFileOutput(string filePath, string eventMessage) { using FileStream file = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); var buffer = new byte[256]; - file.Read(buffer, 0, buffer.Length); - string logLine = Encoding.UTF8.GetString(buffer); + + int bytesRead = 0; + int totalBytesRead = 0; + + while (totalBytesRead < buffer.Length) + { + bytesRead = file.Read(buffer, totalBytesRead, buffer.Length - totalBytesRead); + if (bytesRead == 0) + { + break; + } + + totalBytesRead += bytesRead; + } + + string logLine = Encoding.UTF8.GetString(buffer, 0, totalBytesRead); string logMessage = ParseLogMessage(logLine); Assert.StartsWith(eventMessage, logMessage); } diff --git a/test/OpenTelemetry.Tests/Internal/WildcardHelperTests.cs b/test/OpenTelemetry.Tests/Internal/WildcardHelperTests.cs index 9b079580b8..847f483fd1 100644 --- a/test/OpenTelemetry.Tests/Internal/WildcardHelperTests.cs +++ b/test/OpenTelemetry.Tests/Internal/WildcardHelperTests.cs @@ -30,7 +30,7 @@ public void WildcardRegex_ShouldMatch(string[] patterns, string matchWith, bool [InlineData("a", false)] [InlineData("a.*", true)] [InlineData("a.?", true)] - public void Verify_ContainsWildcard(string pattern, bool expected) + public void Verify_ContainsWildcard(string? pattern, bool expected) { Assert.Equal(expected, WildcardHelper.ContainsWildcard(pattern)); } diff --git a/test/OpenTelemetry.Tests/Logs/BatchExportLogRecordProcessorOptionsTest.cs b/test/OpenTelemetry.Tests/Logs/BatchExportLogRecordProcessorOptionsTest.cs index 2030e402c2..d6ebbc74e0 100644 --- a/test/OpenTelemetry.Tests/Logs/BatchExportLogRecordProcessorOptionsTest.cs +++ b/test/OpenTelemetry.Tests/Logs/BatchExportLogRecordProcessorOptionsTest.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using Microsoft.Extensions.Configuration; using Xunit; diff --git a/test/OpenTelemetry.Tests/Logs/BatchLogRecordExportProcessorTests.cs b/test/OpenTelemetry.Tests/Logs/BatchLogRecordExportProcessorTests.cs index d285302fcf..02835e80e0 100644 --- a/test/OpenTelemetry.Tests/Logs/BatchLogRecordExportProcessorTests.cs +++ b/test/OpenTelemetry.Tests/Logs/BatchLogRecordExportProcessorTests.cs @@ -43,20 +43,23 @@ public void StateValuesAndScopeBufferingTest() Assert.NotNull(logRecord.AttributeStorage); Assert.NotNull(logRecord.ILoggerData.BufferedScopes); - KeyValuePair actualState = logRecord.StateValues[0]; + KeyValuePair actualState = logRecord.StateValues[0]; Assert.Same("Value", actualState.Key); Assert.Same("Hello world", actualState.Value); + int scopeCount = 0; bool foundScope = false; - logRecord.ForEachScope( + logRecord.ForEachScope( (s, o) => { foundScope = ReferenceEquals(s.Scope, exportedItems); + scopeCount++; }, null); + Assert.Equal(1, scopeCount); Assert.True(foundScope); processor.Shutdown(); diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs b/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs index d4be69397b..d38c82d1a0 100644 --- a/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs +++ b/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using Xunit; namespace OpenTelemetry.Logs.Tests; diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordStateProcessorTests.cs b/test/OpenTelemetry.Tests/Logs/LogRecordStateProcessorTests.cs index 24acd8869a..8b7cb43a4e 100644 --- a/test/OpenTelemetry.Tests/Logs/LogRecordStateProcessorTests.cs +++ b/test/OpenTelemetry.Tests/Logs/LogRecordStateProcessorTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; using Xunit; diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs b/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs index 138af7ac69..d20a822f06 100644 --- a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs +++ b/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs @@ -25,7 +25,7 @@ private enum Field [Fact] public void CheckCategoryNameForLog() { - using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); + using var loggerFactory = InitializeLoggerFactory(out List exportedItems); var logger = loggerFactory.CreateLogger(); logger.LogInformation("Log"); @@ -179,9 +179,10 @@ public void CheckStateForStructuredLogWithStrongType(bool includeFormattedMessag // Check if state has food Assert.Contains(attributes, item => item.Key == "food"); - var foodParameter = (Food)attributes.First(item => item.Key == "food").Value; - Assert.Equal(food.Name, foodParameter.Name); - Assert.Equal(food.Price, foodParameter.Price); + 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}"); @@ -221,8 +222,9 @@ public void CheckStateForStructuredLogWithAnonymousType(bool includeFormattedMes Assert.Contains(attributes, item => item.Key == "food"); var foodParameter = attributes.First(item => item.Key == "food").Value as dynamic; - Assert.Equal(anonymousType.Name, foodParameter.Name); - Assert.Equal(anonymousType.Price, foodParameter.Price); + 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}"); @@ -266,6 +268,7 @@ public void CheckStateForStructuredLogWithGeneralType(bool includeFormattedMessa Assert.Contains(attributes, item => item.Key == "food"); 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 @@ -306,8 +309,9 @@ public void CheckStateForExceptionLogged() Assert.NotNull(exportedItems[0].State); - var state = exportedItems[0].State; - var itemCount = state.GetType().GetProperty("Count").GetValue(state); + var state = exportedItems[0].State as IReadOnlyList>; + Assert.NotNull(state); + var itemCount = state.Count; // state only has {OriginalFormat} Assert.Equal(1, itemCount); @@ -351,12 +355,12 @@ public void CheckStateValuesCanBeSet() logger.Log( LogLevel.Information, 0, - new List> { new KeyValuePair("Key1", "Value1") }, + new List> { new KeyValuePair("Key1", "Value1") }, null, (s, e) => "OpenTelemetry!"); var logRecord = exportedItems[0]; - var expectedStateValues = new List> { new KeyValuePair("Key2", "Value2") }; + var expectedStateValues = new List> { new KeyValuePair("Key2", "Value2") }; logRecord.StateValues = expectedStateValues; Assert.Equal(expectedStateValues, logRecord.StateValues); @@ -394,6 +398,7 @@ public void CheckStateCanBeSetByProcessor() logger.LogInformation($"This does not matter."); var state = exportedItems[0].State as IReadOnlyList>; + Assert.NotNull(state); Assert.Equal("newStateKey", state[0].Key.ToString()); Assert.Equal("newStateValue", state[0].Value.ToString()); } @@ -417,7 +422,9 @@ public void CheckStateValuesCanBeSetByProcessor() logger.LogInformation("This does not matter."); var stateValue = exportedItems[0]; - Assert.Equal(new KeyValuePair("newStateValueKey", "newStateValueValue"), stateValue.StateValues[0]); + Assert.NotNull(stateValue.StateValues); + Assert.NotEmpty(stateValue.StateValues); + Assert.Equal(new KeyValuePair("newStateValueKey", "newStateValueValue"), stateValue.StateValues[0]); } [Fact] @@ -476,13 +483,14 @@ public void CheckTraceIdForLogWithinActivityMarkedAsRecordOnly(bool includeTrace .Build(); using var activity = activitySource.StartActivity("Activity"); + Assert.NotNull(activity); activity.TraceStateString = "key1=value1"; logger.LogInformation("Log within activity marked as RecordOnly"); var logRecord = exportedItems[0]; var currentActivity = Activity.Current; - Assert.NotNull(Activity.Current); + Assert.NotNull(currentActivity); Assert.Equal(currentActivity.TraceId, logRecord.TraceId); Assert.Equal(currentActivity.SpanId, logRecord.SpanId); Assert.Equal(currentActivity.ActivityTraceFlags, logRecord.TraceFlags); @@ -519,7 +527,7 @@ public void CheckTraceIdForLogWithinActivityMarkedAsRecordAndSample() var logRecord = exportedItems[0]; var currentActivity = Activity.Current; - Assert.NotNull(Activity.Current); + Assert.NotNull(currentActivity); Assert.Equal(currentActivity.TraceId, logRecord.TraceId); Assert.Equal(currentActivity.SpanId, logRecord.SpanId); Assert.Equal(currentActivity.ActivityTraceFlags, logRecord.TraceFlags); @@ -557,12 +565,12 @@ public void IncludeFormattedMessageTestWhenFormatterNull() using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeFormattedMessage = true); var logger = loggerFactory.CreateLogger(); - logger.Log(LogLevel.Information, default, "Hello World!", null, null); + logger.Log(LogLevel.Information, default, "Hello World!", null, null!); var logRecord = exportedItems[0]; Assert.Equal("Hello World!", logRecord.FormattedMessage); Assert.Equal("Hello World!", logRecord.Body); - logger.Log(LogLevel.Information, default, new CustomState(), null, null); + logger.Log(LogLevel.Information, default, new CustomState(), null, null!); logRecord = exportedItems[1]; Assert.Equal(CustomState.ToStringValue, logRecord.FormattedMessage); Assert.Equal(CustomState.ToStringValue, logRecord.Body); @@ -585,8 +593,8 @@ public void VerifyIncludeScopes_False() logger.LogInformation("OpenTelemetry!"); var logRecord = exportedItems[0]; - List scopes = new List(); - logRecord.ForEachScope((scope, state) => scopes.Add(scope.Scope), null); + List scopes = []; + logRecord.ForEachScope((scope, state) => scopes.Add(scope.Scope), null); Assert.Empty(scopes); } @@ -601,18 +609,18 @@ public void VerifyIncludeScopes_True() logger.LogInformation("OpenTelemetry!"); var logRecord = exportedItems[0]; - List scopes = new List(); + List scopes = []; logger.LogInformation("OpenTelemetry!"); logRecord = exportedItems[1]; int reachedDepth = -1; - logRecord.ForEachScope( + logRecord.ForEachScope( (scope, state) => { reachedDepth++; scopes.Add(scope.Scope); - foreach (KeyValuePair item in scope) + foreach (KeyValuePair item in scope) { Assert.Equal(string.Empty, item.Key); Assert.Equal("string_scope", item.Value); @@ -625,24 +633,24 @@ public void VerifyIncludeScopes_True() scopes.Clear(); - List> expectedScope2 = new List> - { - new KeyValuePair("item1", "value1"), - new KeyValuePair("item2", "value2"), - }; + List> expectedScope2 = + [ + new KeyValuePair("item1", "value1"), + new KeyValuePair("item2", "value2"), + ]; using var scope2 = logger.BeginScope(expectedScope2); logger.LogInformation("OpenTelemetry!"); logRecord = exportedItems[2]; reachedDepth = -1; - logRecord.ForEachScope( + logRecord.ForEachScope( (scope, state) => { scopes.Add(scope.Scope); if (reachedDepth++ == 1) { - foreach (KeyValuePair item in scope) + foreach (KeyValuePair item in scope) { Assert.Contains(item, expectedScope2); } @@ -656,24 +664,24 @@ public void VerifyIncludeScopes_True() scopes.Clear(); - KeyValuePair[] expectedScope3 = new KeyValuePair[] - { - new KeyValuePair("item3", "value3"), - new KeyValuePair("item4", "value4"), - }; + KeyValuePair[] expectedScope3 = + [ + new KeyValuePair("item3", "value3"), + new KeyValuePair("item4", "value4"), + ]; using var scope3 = logger.BeginScope(expectedScope3); logger.LogInformation("OpenTelemetry!"); logRecord = exportedItems[3]; reachedDepth = -1; - logRecord.ForEachScope( + logRecord.ForEachScope( (scope, state) => { scopes.Add(scope.Scope); if (reachedDepth++ == 2) { - foreach (KeyValuePair item in scope) + foreach (KeyValuePair item in scope) { Assert.Contains(item, expectedScope3); } @@ -712,9 +720,9 @@ public void VerifyParseStateValues_UsingStandardExtensions(bool parseStateValues Assert.NotNull(logRecord.StateValues); Assert.Equal(3, logRecord.StateValues.Count); - Assert.Equal(new KeyValuePair("Product", "OpenTelemetry"), logRecord.StateValues[0]); - Assert.Equal(new KeyValuePair("Year", 2021), logRecord.StateValues[1]); - Assert.Equal(new KeyValuePair("{OriginalFormat}", "{Product} {Year}!"), logRecord.StateValues[2]); + Assert.Equal(new KeyValuePair("Product", "OpenTelemetry"), logRecord.StateValues[0]); + Assert.Equal(new KeyValuePair("Year", 2021), logRecord.StateValues[1]); + Assert.Equal(new KeyValuePair("{OriginalFormat}", "{Product} {Year}!"), logRecord.StateValues[2]); var complex = new { Property = "Value" }; @@ -733,11 +741,11 @@ public void VerifyParseStateValues_UsingStandardExtensions(bool parseStateValues Assert.NotNull(logRecord.StateValues); Assert.Equal(4, logRecord.StateValues.Count); - Assert.Equal(new KeyValuePair("Product", "OpenTelemetry"), logRecord.StateValues[0]); - Assert.Equal(new KeyValuePair("Year", 2021), logRecord.StateValues[1]); - Assert.Equal(new KeyValuePair("{OriginalFormat}", "{Product} {Year} {Complex}!"), logRecord.StateValues[3]); + Assert.Equal(new KeyValuePair("Product", "OpenTelemetry"), logRecord.StateValues[0]); + Assert.Equal(new KeyValuePair("Year", 2021), logRecord.StateValues[1]); + Assert.Equal(new KeyValuePair("{OriginalFormat}", "{Product} {Year} {Complex}!"), logRecord.StateValues[3]); - KeyValuePair actualComplex = logRecord.StateValues[2]; + KeyValuePair actualComplex = logRecord.StateValues[2]; Assert.Equal("Complex", actualComplex.Key); Assert.Same(complex, actualComplex.Value); } @@ -761,7 +769,7 @@ public void ParseStateValuesUsingStructTest() Assert.Null(logRecord.State); Assert.NotNull(logRecord.StateValues); Assert.Single(logRecord.StateValues); - Assert.Equal(new KeyValuePair("Key1", "Value1"), logRecord.StateValues[0]); + Assert.Equal(new KeyValuePair("Key1", "Value1"), logRecord.StateValues[0]); } [Fact] @@ -783,7 +791,7 @@ public void ParseStateValuesUsingListTest() Assert.Null(logRecord.State); Assert.NotNull(logRecord.StateValues); Assert.Single(logRecord.StateValues); - Assert.Equal(new KeyValuePair("Key1", "Value1"), logRecord.StateValues[0]); + Assert.Equal(new KeyValuePair("Key1", "Value1"), logRecord.StateValues[0]); } [Fact] @@ -805,7 +813,7 @@ public void ParseStateValuesUsingIEnumerableTest() Assert.Null(logRecord.State); Assert.NotNull(logRecord.StateValues); Assert.Single(logRecord.StateValues); - Assert.Equal(new KeyValuePair("Key1", "Value1"), logRecord.StateValues[0]); + Assert.Equal(new KeyValuePair("Key1", "Value1"), logRecord.StateValues[0]); } [Fact] @@ -860,7 +868,7 @@ public void DisposingStateTest() Assert.NotNull(logRecord.StateValues); Assert.Single(logRecord.StateValues); - KeyValuePair actualState = logRecord.StateValues[0]; + KeyValuePair actualState = logRecord.StateValues[0]; Assert.Same("Value", actualState.Key); Assert.Same("Hello world", actualState.Value); @@ -1016,7 +1024,7 @@ public void LogRecordCategoryNameAliasForInstrumentationScopeTests() Assert.Equal(exportedItems[0].CategoryName, exportedItems[0].Logger.Name); } - private static ILoggerFactory InitializeLoggerFactory(out List exportedItems, Action configure = null) + private static ILoggerFactory InitializeLoggerFactory(out List exportedItems, Action? configure = null) { var items = exportedItems = new List(); @@ -1062,19 +1070,19 @@ IEnumerator IEnumerable.GetEnumerator() } } - internal sealed class DisposingState : IReadOnlyList>, IDisposable + internal sealed class DisposingState : IReadOnlyList>, IDisposable { - private string value; + private string? value; private bool disposed; - public DisposingState(string value) + public DisposingState(string? value) { this.Value = value; } public int Count => 1; - public string Value + public string? Value { get { @@ -1088,9 +1096,9 @@ public string Value private set => this.value = value; } - public KeyValuePair this[int index] => index switch + public KeyValuePair this[int index] => index switch { - 0 => new KeyValuePair(nameof(this.Value), this.Value), + 0 => new KeyValuePair(nameof(this.Value), this.Value), _ => throw new IndexOutOfRangeException(nameof(index)), }; @@ -1099,7 +1107,7 @@ public void Dispose() this.disposed = true; } - public IEnumerator> GetEnumerator() + public IEnumerator> GetEnumerator() { for (var i = 0; i < this.Count; i++) { @@ -1123,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 KeyValuePair("newStateKey", "newStateValue") }; } else if (this.fieldToUpdate == Field.StateValues) { - logRecord.StateValues = new List> { new KeyValuePair("newStateValueKey", "newStateValueValue") }; + logRecord.StateValues = new List> { new KeyValuePair("newStateValueKey", "newStateValueValue") }; } else { @@ -1160,7 +1168,7 @@ private class CustomState { public const string ToStringValue = "CustomState.ToString"; - public string Property { get; set; } + public string? Property { get; set; } public override string ToString() => ToStringValue; @@ -1175,11 +1183,11 @@ public ScopeProcessor(bool buffer) this.buffer = buffer; } - public List Scopes { get; } = new(); + public List Scopes { get; } = new(); public override void OnEnd(LogRecord data) { - data.ForEachScope( + data.ForEachScope( (scope, state) => { this.Scopes.Add(scope.Scope); diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordThreadStaticPoolTests.cs b/test/OpenTelemetry.Tests/Logs/LogRecordThreadStaticPoolTests.cs index 59c0b53454..4fd6f588c8 100644 --- a/test/OpenTelemetry.Tests/Logs/LogRecordThreadStaticPoolTests.cs +++ b/test/OpenTelemetry.Tests/Logs/LogRecordThreadStaticPoolTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using Xunit; namespace OpenTelemetry.Logs.Tests; diff --git a/test/OpenTelemetry.Tests/Logs/LoggerFactoryAndResourceBuilderTests.cs b/test/OpenTelemetry.Tests/Logs/LoggerFactoryAndResourceBuilderTests.cs index 073160b6e5..e955939aea 100644 --- a/test/OpenTelemetry.Tests/Logs/LoggerFactoryAndResourceBuilderTests.cs +++ b/test/OpenTelemetry.Tests/Logs/LoggerFactoryAndResourceBuilderTests.cs @@ -16,7 +16,7 @@ public void TestLogExporterCanAccessResource() VerifyResourceBuilder( assert: (Resource 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 && kvp.Value.ToString()!.Contains("unknown_service")); }); } diff --git a/test/OpenTelemetry.Tests/Logs/LoggerProviderBuilderExtensionsTests.cs b/test/OpenTelemetry.Tests/Logs/LoggerProviderBuilderExtensionsTests.cs index 79907a3d2c..ea25d74860 100644 --- a/test/OpenTelemetry.Tests/Logs/LoggerProviderBuilderExtensionsTests.cs +++ b/test/OpenTelemetry.Tests/Logs/LoggerProviderBuilderExtensionsTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using Microsoft.Extensions.DependencyInjection; using OpenTelemetry.Resources; using Xunit; diff --git a/test/OpenTelemetry.Tests/Logs/LoggerProviderExtensionsTests.cs b/test/OpenTelemetry.Tests/Logs/LoggerProviderExtensionsTests.cs index 0324027f7f..d059dfd486 100644 --- a/test/OpenTelemetry.Tests/Logs/LoggerProviderExtensionsTests.cs +++ b/test/OpenTelemetry.Tests/Logs/LoggerProviderExtensionsTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using OpenTelemetry.Exporter; using Xunit; diff --git a/test/OpenTelemetry.Tests/Logs/LoggerProviderSdkTests.cs b/test/OpenTelemetry.Tests/Logs/LoggerProviderSdkTests.cs index 7122dd089e..182490cbc8 100644 --- a/test/OpenTelemetry.Tests/Logs/LoggerProviderSdkTests.cs +++ b/test/OpenTelemetry.Tests/Logs/LoggerProviderSdkTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using OpenTelemetry.Exporter; diff --git a/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs index e0c59ca7bf..4839964c0e 100644 --- a/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs +++ b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Reflection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; diff --git a/test/OpenTelemetry.Tests/Metrics/AggregatorTestsBase.cs b/test/OpenTelemetry.Tests/Metrics/AggregatorTestsBase.cs index 6bd96046d3..b7edd7c583 100644 --- a/test/OpenTelemetry.Tests/Metrics/AggregatorTestsBase.cs +++ b/test/OpenTelemetry.Tests/Metrics/AggregatorTestsBase.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Diagnostics; using System.Diagnostics.Metrics; using Xunit; @@ -15,16 +16,14 @@ public abstract class AggregatorTestsBase private static readonly ExplicitBucketHistogramConfiguration HistogramConfiguration = new() { Boundaries = Metric.DefaultHistogramBounds }; private static readonly MetricStreamIdentity MetricStreamIdentity = new(Instrument, HistogramConfiguration); - private readonly bool emitOverflowAttribute; private readonly bool shouldReclaimUnusedMetricPoints; private readonly AggregatorStore aggregatorStore; - protected AggregatorTestsBase(bool emitOverflowAttribute, bool shouldReclaimUnusedMetricPoints) + protected AggregatorTestsBase(bool shouldReclaimUnusedMetricPoints) { - this.emitOverflowAttribute = emitOverflowAttribute; this.shouldReclaimUnusedMetricPoints = shouldReclaimUnusedMetricPoints; - this.aggregatorStore = new(MetricStreamIdentity, AggregationType.HistogramWithBuckets, AggregationTemporality.Cumulative, 1024, emitOverflowAttribute, this.shouldReclaimUnusedMetricPoints); + this.aggregatorStore = new(MetricStreamIdentity, AggregationType.HistogramWithBuckets, AggregationTemporality.Cumulative, 1024, this.shouldReclaimUnusedMetricPoints); } [Fact] @@ -193,11 +192,7 @@ public void MultiThreadedHistogramUpdateAndSnapShotTest() { var boundaries = Array.Empty(); var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.Histogram, null, boundaries, Metric.DefaultExponentialHistogramMaxBuckets, Metric.DefaultExponentialHistogramMaxScale); - var argsToThread = new ThreadArguments - { - HistogramPoint = histogramPoint, - MreToEnsureAllThreadsStart = new ManualResetEvent(false), - }; + var argsToThread = new ThreadArguments(histogramPoint, new ManualResetEvent(false)); var numberOfThreads = 2; var snapshotThread = new Thread(HistogramSnapshotThread); @@ -243,7 +238,7 @@ public void MultiThreadedHistogramUpdateAndSnapShotTest() [InlineData("System.Net.Http", "http.client.request.time_in_queue", "s", KnownHistogramBuckets.DefaultShortSeconds)] [InlineData("System.Net.NameResolution", "dns.lookup.duration", "s", KnownHistogramBuckets.DefaultShortSeconds)] [InlineData("General.App", "simple.alternative.counter", "s", KnownHistogramBuckets.Default)] - public void HistogramBucketsDefaultUpdatesForSecondsTest(string meterName, string instrumentName, string unit, KnownHistogramBuckets expectedHistogramBuckets) + public void HistogramBucketsDefaultUpdatesForSecondsTest(string meterName, string instrumentName, string? unit, KnownHistogramBuckets expectedHistogramBuckets) { using var meter = new Meter(meterName); @@ -256,7 +251,6 @@ public void HistogramBucketsDefaultUpdatesForSecondsTest(string meterName, strin AggregationType.Histogram, AggregationTemporality.Cumulative, cardinalityLimit: 1024, - this.emitOverflowAttribute, this.shouldReclaimUnusedMetricPoints); KnownHistogramBuckets actualHistogramBounds = KnownHistogramBuckets.Default; @@ -333,7 +327,6 @@ internal void ExponentialHistogramTests(AggregationType aggregationType, Aggrega aggregationType, aggregationTemporality, cardinalityLimit: 1024, - this.emitOverflowAttribute, this.shouldReclaimUnusedMetricPoints, exemplarsEnabled ? ExemplarFilterType.AlwaysOn : null); @@ -341,7 +334,7 @@ internal void ExponentialHistogramTests(AggregationType aggregationType, Aggrega foreach (var value in valuesToRecord) { - aggregatorStore.Update(value, Array.Empty>()); + aggregatorStore.Update(value, Array.Empty>()); if (value >= 0) { @@ -443,10 +436,9 @@ internal void ExponentialMaxScaleConfigWorks(int? maxScale) AggregationType.Base2ExponentialHistogram, AggregationTemporality.Cumulative, cardinalityLimit: 1024, - this.emitOverflowAttribute, this.shouldReclaimUnusedMetricPoints); - aggregatorStore.Update(10, Array.Empty>()); + aggregatorStore.Update(10, Array.Empty>()); aggregatorStore.Snapshot(); @@ -466,10 +458,11 @@ internal void ExponentialMaxScaleConfigWorks(int? maxScale) Assert.Equal(expectedScale, metricPoint.GetExponentialHistogramData().Scale); } - private static void HistogramSnapshotThread(object obj) + private static void HistogramSnapshotThread(object? obj) { var args = obj as ThreadArguments; - var mreToEnsureAllThreadsStart = args.MreToEnsureAllThreadsStart; + Debug.Assert(args != null, "args was null"); + var mreToEnsureAllThreadsStart = args!.MreToEnsureAllThreadsStart; if (Interlocked.Increment(ref args.ThreadStartedCount) == 3) { @@ -487,10 +480,11 @@ private static void HistogramSnapshotThread(object obj) } } - private static void HistogramUpdateThread(object obj) + private static void HistogramUpdateThread(object? obj) { var args = obj as ThreadArguments; - var mreToEnsureAllThreadsStart = args.MreToEnsureAllThreadsStart; + Debug.Assert(args != null, "args was null"); + var mreToEnsureAllThreadsStart = args!.MreToEnsureAllThreadsStart; if (Interlocked.Increment(ref args.ThreadStartedCount) == 3) { @@ -509,26 +503,24 @@ private static void HistogramUpdateThread(object obj) private class ThreadArguments { + public readonly ManualResetEvent MreToEnsureAllThreadsStart; public MetricPoint HistogramPoint; - public ManualResetEvent MreToEnsureAllThreadsStart; public int ThreadStartedCount; public long ThreadsFinishedAllUpdatesCount; public double SumOfDelta; + + public ThreadArguments(MetricPoint histogramPoint, ManualResetEvent mreToEnsureAllThreadsStart) + { + this.HistogramPoint = histogramPoint; + this.MreToEnsureAllThreadsStart = mreToEnsureAllThreadsStart; + } } } public class AggregatorTests : AggregatorTestsBase { public AggregatorTests() - : base(emitOverflowAttribute: false, shouldReclaimUnusedMetricPoints: false) - { - } -} - -public class AggregatorTestsWithOverflowAttribute : AggregatorTestsBase -{ - public AggregatorTestsWithOverflowAttribute() - : base(emitOverflowAttribute: true, shouldReclaimUnusedMetricPoints: false) + : base(shouldReclaimUnusedMetricPoints: false) { } } @@ -536,15 +528,7 @@ public AggregatorTestsWithOverflowAttribute() public class AggregatorTestsWithReclaimAttribute : AggregatorTestsBase { public AggregatorTestsWithReclaimAttribute() - : base(emitOverflowAttribute: false, shouldReclaimUnusedMetricPoints: true) - { - } -} - -public class AggregatorTestsWithBothReclaimAndOverflowAttributes : AggregatorTestsBase -{ - public AggregatorTestsWithBothReclaimAndOverflowAttributes() - : base(emitOverflowAttribute: true, shouldReclaimUnusedMetricPoints: true) + : base(shouldReclaimUnusedMetricPoints: true) { } } diff --git a/test/OpenTelemetry.Tests/Metrics/InMemoryExporterTests.cs b/test/OpenTelemetry.Tests/Metrics/InMemoryExporterTests.cs index bb44505bd9..54dbe28069 100644 --- a/test/OpenTelemetry.Tests/Metrics/InMemoryExporterTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/InMemoryExporterTests.cs @@ -28,7 +28,7 @@ public void InMemoryExporterShouldDeepCopyMetricPoints() var counter = meter.CreateCounter("meter"); // TEST 1: Emit 10 for the MetricPoint with a single key-vaue pair: ("tag1", "value1") - counter.Add(10, new KeyValuePair("tag1", "value1")); + counter.Add(10, new KeyValuePair("tag1", "value1")); meterProvider.ForceFlush(); @@ -38,7 +38,7 @@ public void InMemoryExporterShouldDeepCopyMetricPoints() Assert.Equal(10, metric1.MetricPoints[0].GetSumLong()); // TEST 2: Emit 25 for the MetricPoint with a single key-vaue pair: ("tag1", "value1") - counter.Add(25, new KeyValuePair("tag1", "value1")); + counter.Add(25, new KeyValuePair("tag1", "value1")); meterProvider.ForceFlush(); diff --git a/test/OpenTelemetry.Tests/Metrics/MemoryEfficiencyTests.cs b/test/OpenTelemetry.Tests/Metrics/MemoryEfficiencyTests.cs index 3965aa0599..6418be1626 100644 --- a/test/OpenTelemetry.Tests/Metrics/MemoryEfficiencyTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MemoryEfficiencyTests.cs @@ -28,7 +28,7 @@ public void ExportOnlyWhenPointChanged(MetricReaderTemporalityPreference tempora var counter = meter.CreateCounter("meter"); - counter.Add(10, new KeyValuePair("tag1", "value1")); + counter.Add(10, new KeyValuePair("tag1", "value1")); meterProvider.ForceFlush(); Assert.Single(exportedItems); diff --git a/test/OpenTelemetry.Tests/Metrics/MeterProviderBuilderExtensionsTests.cs b/test/OpenTelemetry.Tests/Metrics/MeterProviderBuilderExtensionsTests.cs index 5ddd39fee0..6b2a13f1c0 100644 --- a/test/OpenTelemetry.Tests/Metrics/MeterProviderBuilderExtensionsTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MeterProviderBuilderExtensionsTests.cs @@ -16,7 +16,7 @@ public void ServiceLifecycleAvailableToSDKBuilderTest() { var builder = Sdk.CreateMeterProviderBuilder(); - MyInstrumentation myInstrumentation = null; + MyInstrumentation? myInstrumentation = null; RunBuilderServiceLifecycleTest( builder, @@ -54,6 +54,7 @@ public void AddReaderUsingDependencyInjectionTest() using var provider = builder.Build() as MeterProviderSdk; Assert.NotNull(provider); + Assert.NotNull(provider.OwnedServiceProvider); var readers = ((IServiceProvider)provider.OwnedServiceProvider).GetServices(); @@ -72,13 +73,13 @@ public void AddReaderUsingDependencyInjectionTest() [Fact] public void AddInstrumentationTest() { - List instrumentation = null; + List? instrumentation = null; using (var provider = Sdk.CreateMeterProviderBuilder() .AddInstrumentation() .AddInstrumentation((sp, provider) => new MyInstrumentation() { Provider = provider }) .AddInstrumentation(new MyInstrumentation()) - .AddInstrumentation(() => (object)null) + .AddInstrumentation(() => (object?)null) .Build() as MeterProviderSdk) { Assert.NotNull(provider); @@ -144,6 +145,7 @@ public void SetAndConfigureResourceTest() Assert.True(serviceProviderTestExecuted); Assert.Equal(2, configureInvocations); + Assert.NotNull(provider); Assert.Single(provider.Resource.Attributes); Assert.Contains(provider.Resource.Attributes, kvp => kvp.Key == "key2" && (string)kvp.Value == "value2"); } @@ -162,7 +164,7 @@ public void ConfigureBuilderIConfigurationAvailableTest() configureBuilderCalled = true; - var testKeyValue = configuration.GetValue("TEST_KEY", null); + var testKeyValue = configuration.GetValue("TEST_KEY", null); Assert.Equal("TEST_KEY_VALUE", testKeyValue); }) @@ -182,7 +184,7 @@ public void ConfigureBuilderIConfigurationModifiableTest() .ConfigureServices(services => { var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary { ["TEST_KEY_2"] = "TEST_KEY_2_VALUE" }) + .AddInMemoryCollection(new Dictionary { ["TEST_KEY_2"] = "TEST_KEY_2_VALUE" }) .Build(); services.AddSingleton(configuration); @@ -193,7 +195,7 @@ public void ConfigureBuilderIConfigurationModifiableTest() configureBuilderCalled = true; - var testKey2Value = configuration.GetValue("TEST_KEY_2", null); + var testKey2Value = configuration.GetValue("TEST_KEY_2", null); Assert.Equal("TEST_KEY_2_VALUE", testKey2Value); }) @@ -358,7 +360,7 @@ private static void RunBuilderServiceLifecycleTest( private sealed class MyInstrumentation : IDisposable { - internal MeterProvider Provider; + internal MeterProvider? Provider; internal bool Disposed; public void Dispose() diff --git a/test/OpenTelemetry.Tests/Metrics/MeterProviderSdkTest.cs b/test/OpenTelemetry.Tests/Metrics/MeterProviderSdkTest.cs index bce959764f..83d5d4644c 100644 --- a/test/OpenTelemetry.Tests/Metrics/MeterProviderSdkTest.cs +++ b/test/OpenTelemetry.Tests/Metrics/MeterProviderSdkTest.cs @@ -83,7 +83,7 @@ public void TransientMeterExhaustsMetricStorageTest(bool withView, bool forceFlu Assert.Single(exportedItems); } - var metricInstrumentIgnoredEvents = inMemoryEventListener.Events.Where((e) => e.EventId == 33 && e.Payload[1] as string == meterName); + var metricInstrumentIgnoredEvents = inMemoryEventListener.Events.Where((e) => e.EventId == 33 && (e.Payload?.Count ?? 0) >= 2 && e.Payload![1] as string == meterName); Assert.Single(metricInstrumentIgnoredEvents); diff --git a/test/OpenTelemetry.Tests/Metrics/MeterProviderTests.cs b/test/OpenTelemetry.Tests/Metrics/MeterProviderTests.cs index d179363c03..df0cfec4b0 100644 --- a/test/OpenTelemetry.Tests/Metrics/MeterProviderTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MeterProviderTests.cs @@ -16,8 +16,8 @@ public void MeterProviderFindExporterTest() .AddInMemoryExporter(exportedItems) .Build(); - Assert.True(meterProvider.TryFindExporter(out InMemoryExporter inMemoryExporter)); - Assert.False(meterProvider.TryFindExporter(out MyExporter myExporter)); + Assert.True(meterProvider.TryFindExporter(out InMemoryExporter? inMemoryExporter)); + Assert.False(meterProvider.TryFindExporter(out MyExporter? myExporter)); } private class MyExporter : BaseExporter diff --git a/test/OpenTelemetry.Tests/Metrics/MetricApiTestsBase.cs b/test/OpenTelemetry.Tests/Metrics/MetricApiTestsBase.cs index cc5a34d7fd..9c6735f7bc 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricApiTestsBase.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricApiTestsBase.cs @@ -23,8 +23,8 @@ public abstract class MetricApiTestsBase : MetricTestsBase private static readonly int NumberOfMetricUpdateByEachThread = 100000; private readonly ITestOutputHelper output; - protected MetricApiTestsBase(ITestOutputHelper output, bool emitOverflowAttribute, bool shouldReclaimUnusedMetricPoints) - : base(BuildConfiguration(emitOverflowAttribute, shouldReclaimUnusedMetricPoints)) + protected MetricApiTestsBase(ITestOutputHelper output, bool shouldReclaimUnusedMetricPoints) + : base(BuildConfiguration(shouldReclaimUnusedMetricPoints)) { this.output = output; } @@ -40,7 +40,7 @@ public void MeasurementWithNullValuedTag() .AddInMemoryExporter(exportedItems)); var counter = meter.CreateCounter("myCounter"); - counter.Add(100, new KeyValuePair("tagWithNullValue", null)); + counter.Add(100, new KeyValuePair("tagWithNullValue", null)); meterProvider.ForceFlush(MaxTimeToAllowForFlush); Assert.Single(exportedItems); @@ -125,7 +125,7 @@ public void ObserverCallbackExceptionTest() [InlineData("unit")] [InlineData("")] [InlineData(null)] - public void MetricUnitIsExportedCorrectly(string unit) + public void MetricUnitIsExportedCorrectly(string? unit) { var exportedItems = new List(); @@ -147,7 +147,7 @@ public void MetricUnitIsExportedCorrectly(string unit) [InlineData("description")] [InlineData("")] [InlineData(null)] - public void MetricDescriptionIsExportedCorrectly(string description) + public void MetricDescriptionIsExportedCorrectly(string? description) { var exportedItems = new List(); @@ -171,7 +171,7 @@ public void MetricInstrumentationScopeIsExportedCorrectly() var exportedItems = new List(); var meterName = Utils.GetCurrentMethodName(); var meterVersion = "1.0"; - var meterTags = new List> + var meterTags = new List> { new( "MeterTagKey", @@ -190,6 +190,8 @@ public void MetricInstrumentationScopeIsExportedCorrectly() Assert.Equal(meterName, metric.MeterName); Assert.Equal(meterVersion, metric.MeterVersion); + Assert.NotNull(metric.MeterTags); + Assert.Single(metric.MeterTags.Where(kvp => kvp.Key == meterTags[0].Key && kvp.Value == meterTags[0].Value)); } @@ -202,13 +204,13 @@ public void MetricInstrumentationScopeAttributesAreNotTreatedAsIdentifyingProper var exportedItems = new List(); var meterName = "MyMeter"; var meterVersion = "1.0"; - var meterTags1 = new List> + var meterTags1 = new List> { new( "Key1", "Value1"), }; - var meterTags2 = new List> + var meterTags2 = new List> { new( "Key2", @@ -235,8 +237,10 @@ public void MetricInstrumentationScopeAttributesAreNotTreatedAsIdentifyingProper Assert.Equal(meterName, metric.MeterName); Assert.Equal(meterVersion, metric.MeterVersion); + Assert.NotNull(metric.MeterTags); + Assert.Single(metric.MeterTags.Where(kvp => kvp.Key == meterTags1[0].Key && kvp.Value == meterTags1[0].Value)); - Assert.Empty(metric.MeterTags.Where(kvp => kvp.Key == meterTags2[0].Key && kvp.Value == meterTags2[0].Value)); + Assert.DoesNotContain(metric.MeterTags, kvp => kvp.Key == meterTags2[0].Key && kvp.Value == meterTags2[0].Value); List metricPoints = new List(); foreach (ref readonly var mp in metric.GetMetricPoints()) @@ -777,19 +781,19 @@ public void ObservableCounterAggregationTest(bool exportDelta) public void ObservableCounterWithTagsAggregationTest(bool exportDelta) { var exportedItems = new List(); - var tags1 = new List> + var tags1 = new List> { new("statusCode", 200), new("verb", "get"), }; - var tags2 = new List> + var tags2 = new List> { new("statusCode", 200), new("verb", "post"), }; - var tags3 = new List> + var tags3 = new List> { new("statusCode", 500), new("verb", "get"), @@ -873,19 +877,19 @@ public void ObservableCounterWithTagsAggregationTest(bool exportDelta) public void ObservableCounterSpatialAggregationTest(bool exportDelta) { var exportedItems = new List(); - var tags1 = new List> + var tags1 = new List> { new("statusCode", 200), new("verb", "get"), }; - var tags2 = new List> + var tags2 = new List> { new("statusCode", 200), new("verb", "post"), }; - var tags3 = new List> + var tags3 = new List> { new("statusCode", 500), new("verb", "get"), @@ -924,7 +928,7 @@ public void ObservableCounterSpatialAggregationTest(bool exportDelta) Assert.Single(metricPoints); - var emptyTags = new List>(); + var emptyTags = new List>(); var metricPoint1 = metricPoints[0]; ValidateMetricPointTags(emptyTags, metricPoint1.Tags); @@ -1058,19 +1062,19 @@ public void ObservableUpDownCounterAggregationTest(bool exportDelta) public void ObservableUpDownCounterWithTagsAggregationTest(bool exportDelta) { var exportedItems = new List(); - var tags1 = new List> + var tags1 = new List> { new("statusCode", 200), new("verb", "get"), }; - var tags2 = new List> + var tags2 = new List> { new("statusCode", 200), new("verb", "post"), }; - var tags3 = new List> + var tags3 = new List> { new("statusCode", 500), new("verb", "get"), @@ -1177,33 +1181,33 @@ public void DimensionsAreOrderInsensitiveWithSortedKeysFirst(bool exportDelta) meterProvider.ForceFlush(MaxTimeToAllowForFlush); - List> expectedTagsForFirstMetricPoint = new List>() - { + List> expectedTagsForFirstMetricPoint = + [ new("Key1", "Value1"), new("Key2", "Value2"), new("Key3", "Value3"), - }; + ]; - List> expectedTagsForSecondMetricPoint = new List>() - { + List> expectedTagsForSecondMetricPoint = + [ new("Key1", "Value10"), new("Key2", "Value20"), new("Key3", "Value30"), - }; + ]; - List> expectedTagsForThirdMetricPoint = new List>() - { + List> expectedTagsForThirdMetricPoint = + [ new("Key4", "Value1"), new("Key5", "Value3"), new("Key6", "Value2"), - }; + ]; - List> expectedTagsForFourthMetricPoint = new List>() - { + List> expectedTagsForFourthMetricPoint = + [ new("Key4", "Value1"), new("Key5", "Value2"), new("Key6", "Value3"), - }; + ]; Assert.Equal(4, GetNumberOfMetricPoints(exportedItems)); CheckTagsForNthMetricPoint(exportedItems, expectedTagsForFirstMetricPoint, 1); @@ -1268,33 +1272,33 @@ public void DimensionsAreOrderInsensitiveWithUnsortedKeysFirst(bool exportDelta) meterProvider.ForceFlush(MaxTimeToAllowForFlush); - List> expectedTagsForFirstMetricPoint = new List>() - { + List> expectedTagsForFirstMetricPoint = + [ new("Key1", "Value1"), new("Key2", "Value2"), new("Key3", "Value3"), - }; + ]; - List> expectedTagsForSecondMetricPoint = new List>() - { + List> expectedTagsForSecondMetricPoint = + [ new("Key1", "Value10"), new("Key2", "Value20"), new("Key3", "Value30"), - }; + ]; - List> expectedTagsForThirdMetricPoint = new List>() - { + List> expectedTagsForThirdMetricPoint = + [ new("Key4", "Value1"), new("Key5", "Value3"), new("Key6", "Value2"), - }; + ]; - List> expectedTagsForFourthMetricPoint = new List>() - { + List> expectedTagsForFourthMetricPoint = + [ new("Key4", "Value1"), new("Key5", "Value2"), new("Key6", "Value3"), - }; + ]; Assert.Equal(4, GetNumberOfMetricPoints(exportedItems)); CheckTagsForNthMetricPoint(exportedItems, expectedTagsForFirstMetricPoint, 1); @@ -1351,37 +1355,37 @@ public void TestInstrumentDisposal(MetricReaderTemporalityPreference temporality metricReaderOptions.TemporalityPreference = temporality; })); - counter1.Add(10, new KeyValuePair("key", "value")); - counter2.Add(10, new KeyValuePair("key", "value")); + counter1.Add(10, new KeyValuePair("key", "value")); + counter2.Add(10, new KeyValuePair("key", "value")); meterProvider.ForceFlush(MaxTimeToAllowForFlush); Assert.Equal(2, exportedItems.Count); exportedItems.Clear(); - counter1.Add(10, new KeyValuePair("key", "value")); - counter2.Add(10, new KeyValuePair("key", "value")); + counter1.Add(10, new KeyValuePair("key", "value")); + counter2.Add(10, new KeyValuePair("key", "value")); meter1.Dispose(); meterProvider.ForceFlush(MaxTimeToAllowForFlush); Assert.Equal(2, exportedItems.Count); exportedItems.Clear(); - counter1.Add(10, new KeyValuePair("key", "value")); - counter2.Add(10, new KeyValuePair("key", "value")); + counter1.Add(10, new KeyValuePair("key", "value")); + counter2.Add(10, new KeyValuePair("key", "value")); meterProvider.ForceFlush(MaxTimeToAllowForFlush); Assert.Single(exportedItems); exportedItems.Clear(); - counter1.Add(10, new KeyValuePair("key", "value")); - counter2.Add(10, new KeyValuePair("key", "value")); + counter1.Add(10, new KeyValuePair("key", "value")); + counter2.Add(10, new KeyValuePair("key", "value")); meter2.Dispose(); meterProvider.ForceFlush(MaxTimeToAllowForFlush); Assert.Single(exportedItems); exportedItems.Clear(); - counter1.Add(10, new KeyValuePair("key", "value")); - counter2.Add(10, new KeyValuePair("key", "value")); + counter1.Add(10, new KeyValuePair("key", "value")); + counter2.Add(10, new KeyValuePair("key", "value")); meterProvider.ForceFlush(MaxTimeToAllowForFlush); Assert.Empty(exportedItems); } @@ -1406,7 +1410,6 @@ int MetricPointCount() enumerator.MoveNext(); // Second element reserved for overflow attribute. // Validate second element is overflow attribute. - // Overflow attribute is behind experimental flag. So, it is not guaranteed to be present. var tagEnumerator = enumerator.Current.Tags.GetEnumerator(); tagEnumerator.MoveNext(); if (!tagEnumerator.Current.Key.Contains("otel.metric.overflow")) @@ -1440,7 +1443,7 @@ int MetricPointCount() counterLong.Add(10); for (int i = 0; i < MeterProviderBuilderSdk.DefaultCardinalityLimit + 1; i++) { - counterLong.Add(10, new KeyValuePair("key", "value" + i)); + counterLong.Add(10, new KeyValuePair("key", "value" + i)); } meterProvider.ForceFlush(MaxTimeToAllowForFlush); @@ -1450,7 +1453,7 @@ int MetricPointCount() counterLong.Add(10); for (int i = 0; i < MeterProviderBuilderSdk.DefaultCardinalityLimit + 1; i++) { - counterLong.Add(10, new KeyValuePair("key", "value" + i)); + counterLong.Add(10, new KeyValuePair("key", "value" + i)); } meterProvider.ForceFlush(MaxTimeToAllowForFlush); @@ -1459,13 +1462,13 @@ int MetricPointCount() counterLong.Add(10); for (int i = 0; i < MeterProviderBuilderSdk.DefaultCardinalityLimit + 1; i++) { - counterLong.Add(10, new KeyValuePair("key", "value" + i)); + counterLong.Add(10, new KeyValuePair("key", "value" + i)); } // These updates would be dropped. - counterLong.Add(10, new KeyValuePair("key", "valueA")); - counterLong.Add(10, new KeyValuePair("key", "valueB")); - counterLong.Add(10, new KeyValuePair("key", "valueC")); + counterLong.Add(10, new KeyValuePair("key", "valueA")); + counterLong.Add(10, new KeyValuePair("key", "valueB")); + counterLong.Add(10, new KeyValuePair("key", "valueC")); exportedItems.Clear(); meterProvider.ForceFlush(MaxTimeToAllowForFlush); Assert.Equal(MeterProviderBuilderSdk.DefaultCardinalityLimit, MetricPointCount()); @@ -1577,7 +1580,7 @@ public void SetupSdkProviderWithNoReader(bool hasViews) var counter = meter.CreateCounter("counter"); - counter.Add(10, new KeyValuePair("key", "value")); + counter.Add(10, new KeyValuePair("key", "value")); } [Fact] @@ -1613,15 +1616,97 @@ public void UnsupportedMetricInstrument() Assert.Empty(exportedItems); } - internal static IConfiguration BuildConfiguration(bool emitOverflowAttribute, bool shouldReclaimUnusedMetricPoints) + [Fact] + public void GaugeIsExportedCorrectly() { - var configurationData = new Dictionary(); + var exportedItems = new List(); + + using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + + using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + .AddMeter(meter.Name) + .AddInMemoryExporter(exportedItems)); + + var gauge = meter.CreateGauge(name: "NoiseLevel", unit: "dB", description: "Background Noise Level"); + gauge.Record(10); + meterProvider.ForceFlush(MaxTimeToAllowForFlush); + Assert.Single(exportedItems); + var metric = exportedItems[0]; + Assert.Equal("Background Noise Level", metric.Description); + List metricPoints = new List(); + foreach (ref readonly var mp in metric.GetMetricPoints()) + { + metricPoints.Add(mp); + } + + var lastValue = metricPoints[0].GetGaugeLastValueLong(); + Assert.Equal(10, lastValue); + } + + [Theory] + [InlineData(MetricReaderTemporalityPreference.Cumulative)] + [InlineData(MetricReaderTemporalityPreference.Delta)] + public void GaugeHandlesNoNewMeasurementsCorrectlyWithTemporality(MetricReaderTemporalityPreference temporalityPreference) + { + var exportedMetrics = new List(); + + using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + .AddMeter(meter.Name) + .AddInMemoryExporter(exportedMetrics, metricReaderOptions => + { + metricReaderOptions.TemporalityPreference = temporalityPreference; + })); + + var noiseLevelGauge = meter.CreateGauge(name: "NoiseLevel", unit: "dB", description: "Background Noise Level"); + noiseLevelGauge.Record(10); + + // Force a flush to export the recorded data + meterProvider.ForceFlush(MaxTimeToAllowForFlush); - if (emitOverflowAttribute) + // Validate first export / flush + var firstMetric = exportedMetrics[0]; + var firstMetricPoints = new List(); + foreach (ref readonly var metricPoint in firstMetric.GetMetricPoints()) { - configurationData[EmitOverFlowAttributeConfigKey] = "true"; + firstMetricPoints.Add(metricPoint); } + Assert.Single(firstMetricPoints); + var firstMetricPoint = firstMetricPoints[0]; + Assert.Equal(10, firstMetricPoint.GetGaugeLastValueLong()); + + // Flush the metrics again without recording any new measurements + meterProvider.ForceFlush(MaxTimeToAllowForFlush); + + // Validate second export / flush + if (temporalityPreference == MetricReaderTemporalityPreference.Cumulative) + { + // For cumulative temporality, data points should still be collected + // without any new measurements + Assert.Equal(2, exportedMetrics.Count); + var secondMetric = exportedMetrics[1]; + var secondMetricPoints = new List(); + foreach (ref readonly var metricPoint in secondMetric.GetMetricPoints()) + { + secondMetricPoints.Add(metricPoint); + } + + Assert.Single(secondMetricPoints); + var secondMetricPoint = secondMetricPoints[0]; + Assert.Equal(10, secondMetricPoint.GetGaugeLastValueLong()); + } + else if (temporalityPreference == MetricReaderTemporalityPreference.Delta) + { + // For delta temporality, no new metric should be collected + Assert.Single(exportedMetrics); + } + } + + internal static IConfiguration BuildConfiguration(bool shouldReclaimUnusedMetricPoints) + { + var configurationData = new Dictionary(); + if (shouldReclaimUnusedMetricPoints) { configurationData[ReclaimUnusedMetricPointsConfigKey] = "true"; @@ -1632,18 +1717,19 @@ internal static IConfiguration BuildConfiguration(bool emitOverflowAttribute, bo .Build(); } - private static void CounterUpdateThread(object obj) + private static void CounterUpdateThread(object? obj) where T : struct, IComparable { - if (obj is not UpdateThreadArguments arguments) - { - throw new Exception("Invalid args"); - } + var arguments = obj as UpdateThreadArguments; + Debug.Assert(arguments != null, "arguments was null"); - var mre = arguments.MreToBlockUpdateThread; + var mre = arguments!.MreToBlockUpdateThread; var mreToEnsureAllThreadsStart = arguments.MreToEnsureAllThreadsStart; - var counter = arguments.Instrument as Counter; var valueToUpdate = arguments.ValuesToRecord[0]; + + var counter = arguments.Instrument as Counter; + Debug.Assert(counter != null, "counter was null"); + if (Interlocked.Increment(ref arguments.ThreadsStartedCount) == NumberOfThreads) { mreToEnsureAllThreadsStart.Set(); @@ -1654,21 +1740,20 @@ private static void CounterUpdateThread(object obj) for (int i = 0; i < NumberOfMetricUpdateByEachThread; i++) { - counter.Add(valueToUpdate, new KeyValuePair("verb", "GET")); + counter!.Add(valueToUpdate, new KeyValuePair("verb", "GET")); } } - private static void HistogramUpdateThread(object obj) + private static void HistogramUpdateThread(object? obj) where T : struct, IComparable { - if (obj is not UpdateThreadArguments arguments) - { - throw new Exception("Invalid args"); - } + var arguments = obj as UpdateThreadArguments; + Debug.Assert(arguments != null, "arguments was null"); - var mre = arguments.MreToBlockUpdateThread; + var mre = arguments!.MreToBlockUpdateThread; var mreToEnsureAllThreadsStart = arguments.MreToEnsureAllThreadsStart; var histogram = arguments.Instrument as Histogram; + Debug.Assert(histogram != null, "histogram was null"); if (Interlocked.Increment(ref arguments.ThreadsStartedCount) == NumberOfThreads) { @@ -1682,7 +1767,7 @@ private static void HistogramUpdateThread(object obj) { for (int j = 0; j < arguments.ValuesToRecord.Length; j++) { - histogram.Record(arguments.ValuesToRecord[j]); + histogram!.Record(arguments.ValuesToRecord[j]); } } } @@ -1698,13 +1783,7 @@ private void MultithreadedCounterTest(T deltaValueUpdatedByEachCall) .AddMeter(meter.Name) .AddInMemoryExporter(metricItems)); - var argToThread = new UpdateThreadArguments - { - ValuesToRecord = new T[] { deltaValueUpdatedByEachCall }, - Instrument = meter.CreateCounter("counter"), - MreToBlockUpdateThread = new ManualResetEvent(false), - MreToEnsureAllThreadsStart = new ManualResetEvent(false), - }; + var argToThread = new UpdateThreadArguments(new ManualResetEvent(false), new ManualResetEvent(false), meter.CreateCounter("counter"), [deltaValueUpdatedByEachCall]); Thread[] t = new Thread[NumberOfThreads]; for (int i = 0; i < NumberOfThreads; i++) @@ -1754,13 +1833,7 @@ private void MultithreadedHistogramTest(long[] expected, T[] values) .AddMeter(meter.Name) .AddReader(metricReader)); - var argsToThread = new UpdateThreadArguments - { - Instrument = meter.CreateHistogram("histogram"), - MreToBlockUpdateThread = new ManualResetEvent(false), - MreToEnsureAllThreadsStart = new ManualResetEvent(false), - ValuesToRecord = values, - }; + var argsToThread = new UpdateThreadArguments(new ManualResetEvent(false), new ManualResetEvent(false), meter.CreateHistogram("histogram"), values); Thread[] t = new Thread[NumberOfThreads]; for (int i = 0; i < NumberOfThreads; i++) @@ -1801,21 +1874,21 @@ private class UpdateThreadArguments public int ThreadsStartedCount; public Instrument Instrument; public T[] ValuesToRecord; + + public UpdateThreadArguments(ManualResetEvent mreToBlockUpdateThread, ManualResetEvent mreToEnsureAllThreadsStart, Instrument instrument, T[] valuesToRecord) + { + this.MreToBlockUpdateThread = mreToBlockUpdateThread; + this.MreToEnsureAllThreadsStart = mreToEnsureAllThreadsStart; + this.Instrument = instrument; + this.ValuesToRecord = valuesToRecord; + } } } public class MetricApiTest : MetricApiTestsBase { public MetricApiTest(ITestOutputHelper output) - : base(output, emitOverflowAttribute: false, shouldReclaimUnusedMetricPoints: false) - { - } -} - -public class MetricApiTestWithOverflowAttribute : MetricApiTestsBase -{ - public MetricApiTestWithOverflowAttribute(ITestOutputHelper output) - : base(output, emitOverflowAttribute: true, shouldReclaimUnusedMetricPoints: false) + : base(output, shouldReclaimUnusedMetricPoints: false) { } } @@ -1823,15 +1896,7 @@ public MetricApiTestWithOverflowAttribute(ITestOutputHelper output) public class MetricApiTestWithReclaimAttribute : MetricApiTestsBase { public MetricApiTestWithReclaimAttribute(ITestOutputHelper output) - : base(output, emitOverflowAttribute: false, shouldReclaimUnusedMetricPoints: true) - { - } -} - -public class MetricApiTestWithBothOverflowAndReclaimAttributes : MetricApiTestsBase -{ - public MetricApiTestWithBothOverflowAndReclaimAttributes(ITestOutputHelper output) - : base(output, emitOverflowAttribute: true, shouldReclaimUnusedMetricPoints: true) + : base(output, shouldReclaimUnusedMetricPoints: true) { } } diff --git a/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs index fe7fb5244d..7ed01e46a7 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using System.Diagnostics; using System.Diagnostics.Metrics; using Microsoft.Extensions.Configuration; diff --git a/test/OpenTelemetry.Tests/Metrics/MetricExporterTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricExporterTests.cs index 185679acac..8c1d10cf48 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricExporterTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricExporterTests.cs @@ -13,7 +13,7 @@ public class MetricExporterTests [InlineData(ExportModes.Pull | ExportModes.Push)] public void FlushMetricExporterTest(ExportModes mode) { - BaseExporter exporter = null; + BaseExporter? exporter = null; switch (mode) { @@ -26,6 +26,8 @@ public void FlushMetricExporterTest(ExportModes mode) case ExportModes.Pull | ExportModes.Push: exporter = new PushPullMetricExporter(); break; + default: + throw new NotSupportedException($"Export mode '{mode}' is not supported"); } var reader = new BaseExportingMetricReader(exporter); @@ -42,7 +44,7 @@ public void FlushMetricExporterTest(ExportModes mode) case ExportModes.Pull: Assert.False(reader.Collect()); Assert.False(meterProvider.ForceFlush()); - Assert.True((exporter as IPullMetricExporter).Collect(-1)); + Assert.True((exporter as IPullMetricExporter)?.Collect?.Invoke(-1) ?? false); break; case ExportModes.Pull | ExportModes.Push: Assert.True(reader.Collect()); @@ -63,13 +65,7 @@ public override ExportResult Export(in Batch batch) [ExportModes(ExportModes.Pull)] private class PullOnlyMetricExporter : BaseExporter, IPullMetricExporter { - private Func funcCollect; - - public Func Collect - { - get => this.funcCollect; - set { this.funcCollect = value; } - } + public Func? Collect { get; set; } public override ExportResult Export(in Batch batch) { diff --git a/test/OpenTelemetry.Tests/Metrics/MetricOverflowAttributeTestsBase.cs b/test/OpenTelemetry.Tests/Metrics/MetricOverflowAttributeTestsBase.cs index 6e929d1468..6145de4d98 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricOverflowAttributeTestsBase.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricOverflowAttributeTestsBase.cs @@ -14,9 +14,8 @@ namespace OpenTelemetry.Metrics.Tests; public abstract class MetricOverflowAttributeTestsBase { private readonly bool shouldReclaimUnusedMetricPoints; - private readonly Dictionary configurationData = new() + private readonly Dictionary configurationData = new() { - [MetricTestsBase.EmitOverFlowAttributeConfigKey] = "true", }; private readonly IConfiguration configuration; @@ -35,103 +34,6 @@ public MetricOverflowAttributeTestsBase(bool shouldReclaimUnusedMetricPoints) .Build(); } - [Theory] - [InlineData("false", false)] - [InlineData("False", false)] - [InlineData("FALSE", false)] - [InlineData("true", true)] - [InlineData("True", true)] - [InlineData("TRUE", true)] - public void TestEmitOverflowAttributeConfigWithEnvVar(string value, bool isEmitOverflowAttributeKeySet) - { - // Clear the environment variable value first - Environment.SetEnvironmentVariable(MetricTestsBase.EmitOverFlowAttributeConfigKey, null); - - // Set the environment variable to the value provided in the test input - Environment.SetEnvironmentVariable(MetricTestsBase.EmitOverFlowAttributeConfigKey, value); - - var exportedItems = new List(); - - var meter = new Meter(Utils.GetCurrentMethodName()); - var counter = meter.CreateCounter("TestCounter"); - - using var meterProvider = Sdk.CreateMeterProviderBuilder() - .AddMeter(meter.Name) - .AddInMemoryExporter(exportedItems) - .Build(); - - counter.Add(10); - - meterProvider.ForceFlush(); - - Assert.Single(exportedItems); - Assert.Equal(isEmitOverflowAttributeKeySet, exportedItems[0].AggregatorStore.EmitOverflowAttribute); - } - - [Theory] - [InlineData("false", false)] - [InlineData("False", false)] - [InlineData("FALSE", false)] - [InlineData("true", true)] - [InlineData("True", true)] - [InlineData("TRUE", true)] - public void TestEmitOverflowAttributeConfigWithOtherConfigProvider(string value, bool isEmitOverflowAttributeKeySet) - { - var exportedItems = new List(); - - var meter = new Meter(Utils.GetCurrentMethodName()); - var counter = meter.CreateCounter("TestCounter"); - - using var meterProvider = Sdk.CreateMeterProviderBuilder() - .ConfigureServices(services => - { - var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary { [MetricTestsBase.EmitOverFlowAttributeConfigKey] = value }) - .Build(); - - services.AddSingleton(configuration); - }) - .AddMeter(meter.Name) - .AddInMemoryExporter(exportedItems) - .Build(); - - counter.Add(10); - - meterProvider.ForceFlush(); - - Assert.Single(exportedItems); - Assert.Equal(isEmitOverflowAttributeKeySet, exportedItems[0].AggregatorStore.EmitOverflowAttribute); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(10)] - public void EmitOverflowAttributeIsNotDependentOnMaxMetricPoints(int maxMetricPoints) - { - var exportedItems = new List(); - - var meter = new Meter(Utils.GetCurrentMethodName()); - var counter = meter.CreateCounter("TestCounter"); - - using var meterProvider = Sdk.CreateMeterProviderBuilder() - .ConfigureServices(services => - { - services.AddSingleton(this.configuration); - }) - .SetMaxMetricPointsPerMetricStream(maxMetricPoints) - .AddMeter(meter.Name) - .AddInMemoryExporter(exportedItems) - .Build(); - - counter.Add(10); - - meterProvider.ForceFlush(); - - Assert.Single(exportedItems); - Assert.True(exportedItems[0].AggregatorStore.EmitOverflowAttribute); - } - [Theory] [InlineData(MetricReaderTemporalityPreference.Delta)] [InlineData(MetricReaderTemporalityPreference.Cumulative)] @@ -164,7 +66,7 @@ public void MetricOverflowAttributeIsRecordedCorrectlyForCounter(MetricReaderTem { // Emit unique key-value pairs to use up the available MetricPoints // Once this loop is run, we have used up all available MetricPoints for metrics emitted with tags - counter.Add(10, new KeyValuePair("Key", i)); + counter.Add(10, new KeyValuePair("Key", i)); } meterProvider.ForceFlush(); @@ -186,7 +88,7 @@ public void MetricOverflowAttributeIsRecordedCorrectlyForCounter(MetricReaderTem exportedItems.Clear(); metricPoints.Clear(); - counter.Add(5, new KeyValuePair("Key", 2000)); // Emit a metric to exceed the max MetricPoint limit + counter.Add(5, new KeyValuePair("Key", 2000)); // Emit a metric to exceed the max MetricPoint limit meterProvider.ForceFlush(); metric = exportedItems[0]; @@ -217,7 +119,7 @@ public void MetricOverflowAttributeIsRecordedCorrectlyForCounter(MetricReaderTem // Emit 2500 more newer MetricPoints with distinct dimension combinations for (int i = 2001; i < 4501; i++) { - counter.Add(5, new KeyValuePair("Key", i)); + counter.Add(5, new KeyValuePair("Key", i)); } meterProvider.ForceFlush(); @@ -315,7 +217,7 @@ public void MetricOverflowAttributeIsRecordedCorrectlyForHistogram(MetricReaderT { // Emit unique key-value pairs to use up the available MetricPoints // Once this loop is run, we have used up all available MetricPoints for metrics emitted with tags - histogram.Record(10, new KeyValuePair("Key", i)); + histogram.Record(10, new KeyValuePair("Key", i)); } meterProvider.ForceFlush(); @@ -337,7 +239,7 @@ public void MetricOverflowAttributeIsRecordedCorrectlyForHistogram(MetricReaderT exportedItems.Clear(); metricPoints.Clear(); - histogram.Record(5, new KeyValuePair("Key", 2000)); // Emit a metric to exceed the max MetricPoint limit + histogram.Record(5, new KeyValuePair("Key", 2000)); // Emit a metric to exceed the max MetricPoint limit meterProvider.ForceFlush(); metric = exportedItems[0]; @@ -368,7 +270,7 @@ public void MetricOverflowAttributeIsRecordedCorrectlyForHistogram(MetricReaderT // Emit 2500 more newer MetricPoints with distinct dimension combinations for (int i = 2001; i < 4501; i++) { - histogram.Record(5, new KeyValuePair("Key", i)); + histogram.Record(5, new KeyValuePair("Key", i)); } meterProvider.ForceFlush(); diff --git a/test/OpenTelemetry.Tests/Metrics/MetricPointReclaimTestsBase.cs b/test/OpenTelemetry.Tests/Metrics/MetricPointReclaimTestsBase.cs index fa33298643..effe208eda 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricPointReclaimTestsBase.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricPointReclaimTestsBase.cs @@ -13,20 +13,15 @@ namespace OpenTelemetry.Metrics.Tests; public abstract class MetricPointReclaimTestsBase { - private readonly Dictionary configurationData = new() + private readonly Dictionary configurationData = new() { [MetricTestsBase.ReclaimUnusedMetricPointsConfigKey] = "true", }; private readonly IConfiguration configuration; - protected MetricPointReclaimTestsBase(bool emitOverflowAttribute) + protected MetricPointReclaimTestsBase() { - if (emitOverflowAttribute) - { - this.configurationData[MetricTestsBase.EmitOverFlowAttributeConfigKey] = "true"; - } - this.configuration = new ConfigurationBuilder() .AddInMemoryCollection(this.configurationData) .Build(); @@ -57,6 +52,7 @@ public void TestReclaimAttributeConfigWithEnvVar(string value, bool isReclaimAtt .Build(); var meterProviderSdk = meterProvider as MeterProviderSdk; + Assert.NotNull(meterProviderSdk); Assert.Equal(isReclaimAttributeKeySet, meterProviderSdk.ReclaimUnusedMetricPoints); } @@ -77,7 +73,7 @@ public void TestReclaimAttributeConfigWithOtherConfigProvider(string value, bool .ConfigureServices(services => { var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary { [MetricTestsBase.ReclaimUnusedMetricPointsConfigKey] = value }) + .AddInMemoryCollection(new Dictionary { [MetricTestsBase.ReclaimUnusedMetricPointsConfigKey] = value }) .Build(); services.AddSingleton(configuration); @@ -87,6 +83,7 @@ public void TestReclaimAttributeConfigWithOtherConfigProvider(string value, bool .Build(); var meterProviderSdk = meterProvider as MeterProviderSdk; + Assert.NotNull(meterProviderSdk); Assert.Equal(isReclaimAttributeKeySet, meterProviderSdk.ReclaimUnusedMetricPoints); } @@ -134,11 +131,11 @@ void EmitMetric(object obj) // There are separate code paths for single dimension vs multiple dimensions if (random.Next(2) == 0) { - counter.Add(100, new KeyValuePair("key", $"value{i}")); + counter.Add(100, new KeyValuePair("key", $"value{i}")); } else { - counter.Add(100, new KeyValuePair("key", $"value{i}"), new KeyValuePair("dimensionKey", "dimensionValue")); + counter.Add(100, new KeyValuePair("key", $"value{i}"), new KeyValuePair("dimensionKey", "dimensionValue")); } Thread.Sleep(25); @@ -211,13 +208,12 @@ public void MeasurementsAreAggregatedEvenAfterTheyAreDropped(bool emitMetricWith .Build(); // Add 10 distinct combinations of dimensions to surpass the max metric points limit of 10. - // Note that one MetricPoint is reserved for zero tags and one MetricPoint is optionally - // reserved for the overflow tag depending on the user's input. + // Note that one MetricPoint is reserved for zero tags and one MetricPoint is reserved for the overflow tag. // This would lead to dropping a few measurements. We want to make sure that they can still be // aggregated later on when there are free MetricPoints available. for (int i = 0; i < 10; i++) { - counter.Add(100, new KeyValuePair("key", $"value{i}")); + counter.Add(100, new KeyValuePair("key", $"value{i}")); } meterProvider.ForceFlush(); @@ -242,7 +238,7 @@ void EmitMetric() var index = random.Next(measurementValues.Length); var measurement = measurementValues[index]; - counter.Add(measurement, new KeyValuePair("key", $"value{index}")); + counter.Add(measurement, new KeyValuePair("key", $"value{index}")); Interlocked.Add(ref sum, measurement); numberOfMeasurements++; @@ -303,6 +299,7 @@ public override ExportResult Export(in Batch batch) } // This is to ensure that the lookup dictionary does not have unbounded growth + Assert.NotNull(metricPointLookupDictionary); Assert.True(metricPointLookupDictionary.Count <= (MeterProviderBuilderSdk.DefaultCardinalityLimit * 2)); foreach (ref readonly var metricPoint in metric.GetMetricPoints()) @@ -330,15 +327,7 @@ public override ExportResult Export(in Batch batch) public class MetricPointReclaimTests : MetricPointReclaimTestsBase { public MetricPointReclaimTests() - : base(emitOverflowAttribute: false) - { - } -} - -public class MetricPointReclaimTestsWithEmitOverflowAttribute : MetricPointReclaimTestsBase -{ - public MetricPointReclaimTestsWithEmitOverflowAttribute() - : base(emitOverflowAttribute: true) + : base() { } } diff --git a/test/OpenTelemetry.Tests/Metrics/MetricSnapshotTestsBase.cs b/test/OpenTelemetry.Tests/Metrics/MetricSnapshotTestsBase.cs index 08ce90ffe4..54b8ed9586 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricSnapshotTestsBase.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricSnapshotTestsBase.cs @@ -16,10 +16,9 @@ public abstract class MetricSnapshotTestsBase { private readonly IConfiguration configuration; - protected MetricSnapshotTestsBase(bool emitOverflowAttribute, bool shouldReclaimUnusedMetricPoints) + protected MetricSnapshotTestsBase(bool shouldReclaimUnusedMetricPoints) { this.configuration = MetricApiTestsBase.BuildConfiguration( - emitOverflowAttribute, shouldReclaimUnusedMetricPoints); } @@ -298,15 +297,7 @@ public void VerifySnapshot_ExponentialHistogram() public class MetricSnapshotTests : MetricSnapshotTestsBase { public MetricSnapshotTests() - : base(emitOverflowAttribute: false, shouldReclaimUnusedMetricPoints: false) - { - } -} - -public class MetricSnapshotTestsWithOverflowAttribute : MetricSnapshotTestsBase -{ - public MetricSnapshotTestsWithOverflowAttribute() - : base(emitOverflowAttribute: true, shouldReclaimUnusedMetricPoints: false) + : base(shouldReclaimUnusedMetricPoints: false) { } } @@ -314,15 +305,7 @@ public MetricSnapshotTestsWithOverflowAttribute() public class MetricSnapshotTestsWithReclaimAttribute : MetricSnapshotTestsBase { public MetricSnapshotTestsWithReclaimAttribute() - : base(emitOverflowAttribute: false, shouldReclaimUnusedMetricPoints: true) - { - } -} - -public class MetricSnapshotTestsWithBothAttributes : MetricSnapshotTestsBase -{ - public MetricSnapshotTestsWithBothAttributes() - : base(emitOverflowAttribute: true, shouldReclaimUnusedMetricPoints: true) + : base(shouldReclaimUnusedMetricPoints: true) { } } diff --git a/test/OpenTelemetry.Tests/Metrics/MetricTestsBase.cs b/test/OpenTelemetry.Tests/Metrics/MetricTestsBase.cs index 7d72b773ea..746c3b6aeb 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricTestsBase.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricTestsBase.cs @@ -16,10 +16,9 @@ namespace OpenTelemetry.Metrics.Tests; public class MetricTestsBase { - public const string EmitOverFlowAttributeConfigKey = "OTEL_DOTNET_EXPERIMENTAL_METRICS_EMIT_OVERFLOW_ATTRIBUTE"; public const string ReclaimUnusedMetricPointsConfigKey = "OTEL_DOTNET_EXPERIMENTAL_METRICS_RECLAIM_UNUSED_METRIC_POINTS"; - protected readonly IConfiguration configuration; + protected readonly IConfiguration? configuration; protected MetricTestsBase() { @@ -33,10 +32,10 @@ protected MetricTestsBase(IConfiguration configuration) #if BUILDING_HOSTING_TESTS public static IHost BuildHost( bool useWithMetricsStyle, - Action configureAppConfiguration = null, - Action configureServices = null, - Action configureMetricsBuilder = null, - Action configureMeterProviderBuilder = null) + Action? configureAppConfiguration = null, + Action? configureServices = null, + Action? configureMetricsBuilder = null, + Action? configureMeterProviderBuilder = null) { var hostBuilder = new HostBuilder() .ConfigureDefaults(null) @@ -74,15 +73,15 @@ public static IHost BuildHost( return host; - static void ConfigureBuilder(MeterProviderBuilder builder, Action configureMeterProviderBuilder) + static void ConfigureBuilder(MeterProviderBuilder builder, Action? configureMeterProviderBuilder) { - IServiceCollection localServices = null; + IServiceCollection? localServices = null; builder.ConfigureServices(services => localServices = services); Debug.Assert(localServices != null, "localServices was null"); - var testBuilder = new HostingMeterProviderBuilder(localServices); + var testBuilder = new HostingMeterProviderBuilder(localServices!); configureMeterProviderBuilder?.Invoke(testBuilder); } } @@ -90,7 +89,7 @@ static void ConfigureBuilder(MeterProviderBuilder builder, Action> expectedTags, ReadOnlyTagCollection actualTags) + public static void ValidateMetricPointTags(List> expectedTags, ReadOnlyTagCollection actualTags) { int tagIndex = 0; foreach (var tag in actualTags) @@ -175,7 +174,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) + public static void CheckTagsForNthMetricPoint(List metrics, List> tags, int n) { var metric = metrics[0]; var metricPointEnumerator = metric.GetMetricPoints().GetEnumerator(); @@ -216,7 +215,7 @@ public IDisposable BuildMeterProvider( } }); - meterProvider = host.Services.GetService(); + meterProvider = host.Services.GetRequiredService(); return host; #else @@ -277,8 +276,8 @@ private sealed class MetricsSubscriptionManagerCleanupHostedService : IHostedSer public MetricsSubscriptionManagerCleanupHostedService(IServiceProvider serviceProvider) { - this.metricsSubscriptionManager = serviceProvider.GetService( - typeof(ConsoleMetrics).Assembly.GetType("Microsoft.Extensions.Diagnostics.Metrics.MetricsSubscriptionManager")); + this.metricsSubscriptionManager = serviceProvider.GetRequiredService( + typeof(ConsoleMetrics).Assembly.GetType("Microsoft.Extensions.Diagnostics.Metrics.MetricsSubscriptionManager")!); if (this.metricsSubscriptionManager == null) { @@ -292,7 +291,7 @@ public void Dispose() // be bugged in that it doesn't implement IDisposable. This hack // manually invokes Dispose so that tests don't clobber each other. // See: https://github.com/dotnet/runtime/issues/94434. - this.metricsSubscriptionManager.GetType().GetMethod("Dispose").Invoke(this.metricsSubscriptionManager, null); + this.metricsSubscriptionManager.GetType().GetMethod("Dispose")!.Invoke(this.metricsSubscriptionManager, null); } public Task StartAsync(CancellationToken cancellationToken) diff --git a/test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs index c1a0fca281..50c9be30d3 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs @@ -56,7 +56,7 @@ public void AddViewWithInvalidNameThrowsArgumentException(string viewNewName) } [Fact] - public void AddViewWithNullMetricStreamConfigurationThrowsArgumentnullException() + public void AddViewWithNullMetricStreamConfigurationThrowsArgumentNullException() { var exportedItems = new List(); @@ -64,7 +64,7 @@ public void AddViewWithNullMetricStreamConfigurationThrowsArgumentnullException( Assert.Throws(() => this.BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter1.Name) - .AddView("name1", (MetricStreamConfiguration)null) + .AddView("name1", (MetricStreamConfiguration)null!) .AddInMemoryExporter(exportedItems))); } @@ -570,6 +570,177 @@ public void ViewToProduceCustomHistogramBound() Assert.Equal(boundaries.Length + 1, actualCount); } + [Fact] + public void HistogramWithAdviceBoundaries_HandlesAllTypes() + { + using var meter = new Meter(Utils.GetCurrentMethodName()); + var exportedItems = new List(); + int counter = 0; + + using var container = this.BuildMeterProvider(out var meterProvider, builder => + { + builder.AddMeter(meter.Name); + builder.AddInMemoryExporter(exportedItems); + }); + + // 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 } }), + }; + + foreach (var histogram in histograms) + { + exportedItems.Clear(); + + if (histogram is Histogram longHistogram) + { + longHistogram.Record(-10); + longHistogram.Record(9); + longHistogram.Record(19); + } + else if (histogram is Histogram intHistogram) + { + intHistogram.Record(-10); + intHistogram.Record(9); + intHistogram.Record(19); + counter++; + } + else if (histogram is Histogram shortHistogram) + { + shortHistogram.Record(-10); + shortHistogram.Record(9); + shortHistogram.Record(19); + counter++; + } + else if (histogram is Histogram floatHistogram) + { + floatHistogram.Record(-10.0F); + floatHistogram.Record(9.0F); + floatHistogram.Record(19.0F); + counter++; + } + else if (histogram is Histogram doubleHistogram) + { + doubleHistogram.Record(-10.0); + doubleHistogram.Record(9.0); + doubleHistogram.Record(19.0); + counter++; + } + + meterProvider.ForceFlush(MaxTimeToAllowForFlush); + var metricCustom = exportedItems[counter]; + + List metricPointsCustom = new List(); + foreach (ref readonly var mp in metricCustom.GetMetricPoints()) + { + metricPointsCustom.Add(mp); + } + + Assert.Single(metricPointsCustom); + var histogramPoint = metricPointsCustom[0]; + + var count = histogramPoint.GetHistogramCount(); + var sum = histogramPoint.GetHistogramSum(); + + Assert.Equal(18, sum); + Assert.Equal(3, count); + + var index = 0; + var actualCount = 0; + long[] expectedBucketCounts = new long[] { 2, 1, 0 }; + + foreach (var histogramMeasurement in histogramPoint.GetHistogramBuckets()) + { + Assert.Equal(expectedBucketCounts[index], histogramMeasurement.BucketCount); + index++; + actualCount++; + } + + Assert.Equal(3, actualCount); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + 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 }; + + using var container = this.BuildMeterProvider(out var meterProvider, builder => + { + builder.AddMeter(meter.Name); + + if (useViewToOverride) + { + builder.AddView("MyHistogram", new ExplicitBucketHistogramConfiguration { Boundaries = viewBoundaries }); + } + + builder.AddInMemoryExporter(exportedItems); + }); + + var histogram = meter.CreateHistogram( + "MyHistogram", + unit: null, + description: null, + tags: null, + new() + { + HistogramBucketBoundaries = adviceBoundaries, + }); + + histogram.Record(-10); + histogram.Record(0); + histogram.Record(1); + histogram.Record(9); + histogram.Record(10); + histogram.Record(11); + histogram.Record(19); + histogram.Record(22); + + meterProvider.ForceFlush(MaxTimeToAllowForFlush); + Assert.Single(exportedItems); + var metricCustom = exportedItems[0]; + + Assert.Equal("MyHistogram", metricCustom.Name); + + List metricPointsCustom = new List(); + foreach (ref readonly var mp in metricCustom.GetMetricPoints()) + { + metricPointsCustom.Add(mp); + } + + Assert.Single(metricPointsCustom); + var histogramPoint = metricPointsCustom[0]; + + var count = histogramPoint.GetHistogramCount(); + var sum = histogramPoint.GetHistogramSum(); + + Assert.Equal(62, sum); + Assert.Equal(8, count); + + var index = 0; + var actualCount = 0; + long[] expectedBucketCounts = useViewToOverride ? new long[] { 5, 2, 1 } : new long[] { 3, 2, 2, 1 }; + + foreach (var histogramMeasurement in histogramPoint.GetHistogramBuckets()) + { + Assert.Equal(expectedBucketCounts[index], histogramMeasurement.BucketCount); + index++; + actualCount++; + } + + Assert.Equal(useViewToOverride ? viewBoundaries.Length + 1 : adviceBoundaries.Count + 1, actualCount); + } + [Fact] public void ViewToProduceExponentialHistogram() { @@ -922,7 +1093,7 @@ public void ViewConflict_OneInstrument_DifferentDescription() [Theory] [InlineData(true)] [InlineData(false)] - public void CardinalityLimitofMatchingViewTakesPrecedenceOverMeterProvider(bool setDefault) + public void CardinalityLimitOfMatchingViewTakesPrecedenceOverMeterProvider(bool setDefault) { using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); @@ -1062,7 +1233,7 @@ public void ViewConflict_TwoIdenticalInstruments_TwoViews_DifferentTags() var instrument1 = meter.CreateCounter("name"); var instrument2 = meter.CreateCounter("name"); - var tags = new KeyValuePair[] + var tags = new KeyValuePair[] { new("key1", "value"), new("key2", "value"), @@ -1076,8 +1247,8 @@ public void ViewConflict_TwoIdenticalInstruments_TwoViews_DifferentTags() Assert.Equal(2, exportedItems.Count); var metric1 = new List() { exportedItems[0] }; var metric2 = new List() { exportedItems[1] }; - var tag1 = new List> { tags[0] }; - var tag2 = new List> { tags[1] }; + var tag1 = new List> { tags[0] }; + var tag2 = new List> { tags[1] }; Assert.Equal("name", exportedItems[0].Name); Assert.Equal("name", exportedItems[1].Name); @@ -1109,7 +1280,7 @@ public void ViewConflict_TwoIdenticalInstruments_TwoViews_SameTags() var instrument1 = meter.CreateCounter("name"); var instrument2 = meter.CreateCounter("name"); - var tags = new KeyValuePair[] + var tags = new KeyValuePair[] { new("key1", "value"), new("key2", "value"), @@ -1123,13 +1294,13 @@ public void ViewConflict_TwoIdenticalInstruments_TwoViews_SameTags() Assert.Equal(2, exportedItems.Count); var metric1 = new List() { exportedItems[0] }; - var tag1 = new List> { tags[0] }; + var tag1 = new List> { tags[0] }; Assert.Equal("name", exportedItems[0].Name); Assert.Equal(20, GetLongSum(metric1)); CheckTagsForNthMetricPoint(metric1, tag1, 1); var metric2 = new List() { exportedItems[1] }; - var tag2 = new List> { tags[0] }; + var tag2 = new List> { tags[0] }; Assert.Equal("name", exportedItems[1].Name); Assert.Equal(20, GetLongSum(metric2)); CheckTagsForNthMetricPoint(metric2, tag2, 1); @@ -1237,7 +1408,7 @@ public void ViewConflict_TwoInstruments_OneMatchesView() var instrument1 = meter.CreateCounter("name"); var instrument2 = meter.CreateCounter("othername"); - var tags = new KeyValuePair[] + var tags = new KeyValuePair[] { new("key1", "value"), new("key2", "value"), @@ -1252,8 +1423,8 @@ public void ViewConflict_TwoInstruments_OneMatchesView() var metric1 = new List() { exportedItems[0] }; var metric2 = new List() { exportedItems[1] }; - var tags1 = new List> { tags[0] }; - var tags2 = new List> { tags[0], tags[1] }; + var tags1 = new List> { tags[0] }; + var tags2 = new List> { tags[0], tags[1] }; Assert.Equal("othername", exportedItems[0].Name); Assert.Equal("othername", exportedItems[1].Name); diff --git a/test/OpenTelemetry.Tests/Metrics/MultipleReadersTests.cs b/test/OpenTelemetry.Tests/Metrics/MultipleReadersTests.cs index 1630921965..c2a1ca5ccb 100644 --- a/test/OpenTelemetry.Tests/Metrics/MultipleReadersTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MultipleReadersTests.cs @@ -71,7 +71,7 @@ public void SdkSupportsMultipleReaders(MetricReaderTemporalityPreference aggrega Assert.True(defaultNamedOptionsConfigureCalled); Assert.True(namedOptionsConfigureCalled); - counter.Add(10, new KeyValuePair("key", "value")); + counter.Add(10, new KeyValuePair("key", "value")); meterProvider.ForceFlush(); @@ -123,7 +123,7 @@ public void SdkSupportsMultipleReaders(MetricReaderTemporalityPreference aggrega exportedItems1.Clear(); exportedItems2.Clear(); - counter.Add(15, new KeyValuePair("key", "value")); + counter.Add(15, new KeyValuePair("key", "value")); meterProvider.ForceFlush(); diff --git a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj index 2a2532c07e..2b66cc258d 100644 --- a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj +++ b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj @@ -1,11 +1,10 @@ + Unit test project for OpenTelemetry $(TargetFrameworksForTests) $(NoWarn),CS0618 - - - disable + true @@ -15,7 +14,6 @@ - @@ -28,9 +26,7 @@ - - runtime; build; native; contentfiles; analyzers - - + + diff --git a/test/OpenTelemetry.Tests/OpenTelemetrySdkTests.cs b/test/OpenTelemetry.Tests/OpenTelemetrySdkTests.cs new file mode 100644 index 0000000000..ba456cbc4e --- /dev/null +++ b/test/OpenTelemetry.Tests/OpenTelemetrySdkTests.cs @@ -0,0 +1,68 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.Logging.Abstractions; +using OpenTelemetry.Logs; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; +using Xunit; + +namespace OpenTelemetry.Tests; + +public class OpenTelemetrySdkTests +{ + [Fact] + public void BuilderDelegateRequiredTest() + { + Assert.Throws(() => OpenTelemetrySdk.Create(null!)); + } + + [Fact] + public void NoopProvidersReturnedTest() + { + bool builderDelegateInvoked = false; + + using var sdk = OpenTelemetrySdk.Create(builder => + { + builderDelegateInvoked = true; + Assert.NotNull(builder.Services); + }); + + Assert.True(builderDelegateInvoked); + + Assert.NotNull(sdk); + Assert.NotNull(sdk.Services); + Assert.True(sdk.LoggerProvider is OpenTelemetrySdk.NoopLoggerProvider); + Assert.True(sdk.MeterProvider is OpenTelemetrySdk.NoopMeterProvider); + Assert.True(sdk.TracerProvider is OpenTelemetrySdk.NoopTracerProvider); + Assert.True(sdk.GetLoggerFactory() is NullLoggerFactory); + } + + [Fact] + public void ProvidersCreatedAndDisposedTest() + { + var sdk = OpenTelemetrySdk.Create(builder => + { + builder + .WithLogging() + .WithMetrics() + .WithTracing(); + }); + + var loggerProvider = sdk.LoggerProvider as LoggerProviderSdk; + var meterProvider = sdk.MeterProvider as MeterProviderSdk; + var tracerProvider = sdk.TracerProvider as TracerProviderSdk; + + Assert.NotNull(loggerProvider); + Assert.NotNull(meterProvider); + Assert.NotNull(tracerProvider); + + Assert.True(sdk.GetLoggerFactory() is not NullLoggerFactory); + + sdk.Dispose(); + + Assert.True(loggerProvider.Disposed); + Assert.True(meterProvider.Disposed); + Assert.True(tracerProvider.Disposed); + } +} diff --git a/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTest.cs b/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTest.cs index a6180b7a4c..ae9df7faaf 100644 --- a/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTest.cs +++ b/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTest.cs @@ -71,7 +71,7 @@ public void OtelEnvResource_WithEnvVar_2() [Fact] public void OtelEnvResource_UsingIConfiguration() { - var values = new Dictionary() + var values = new Dictionary() { [OtelEnvResourceDetector.EnvVarKey] = "Key1=Val1,Key2=Val2", }; diff --git a/test/OpenTelemetry.Tests/Resources/OtelServiceNameEnvVarDetectorTests.cs b/test/OpenTelemetry.Tests/Resources/OtelServiceNameEnvVarDetectorTests.cs index 9b3bf437cc..305c1d403b 100644 --- a/test/OpenTelemetry.Tests/Resources/OtelServiceNameEnvVarDetectorTests.cs +++ b/test/OpenTelemetry.Tests/Resources/OtelServiceNameEnvVarDetectorTests.cs @@ -57,7 +57,7 @@ public void OtelServiceNameEnvVar_WithValue() [Fact] public void OtelServiceNameEnvVar_UsingIConfiguration() { - var values = new Dictionary() + var values = new Dictionary() { [OtelServiceNameEnvVarDetector.EnvVarKey] = "my-service", }; diff --git a/test/OpenTelemetry.Tests/Resources/ResourceTest.cs b/test/OpenTelemetry.Tests/Resources/ResourceTest.cs index 337870a8a1..9d5ab40a78 100644 --- a/test/OpenTelemetry.Tests/Resources/ResourceTest.cs +++ b/test/OpenTelemetry.Tests/Resources/ResourceTest.cs @@ -26,7 +26,7 @@ public void Dispose() public void CreateResource_NullAttributeCollection() { // Act and Assert - var resource = new Resource(null); + var resource = new Resource(null!); Assert.Empty(resource.Attributes); } @@ -34,10 +34,10 @@ public void CreateResource_NullAttributeCollection() public void CreateResource_NullAttributeValue() { // Arrange - var attributes = new Dictionary { { "NullValue", null } }; + var attributes = new Dictionary { { "NullValue", null } }; // Act and Assert - Assert.Throws(() => new Resource(attributes)); + Assert.Throws(() => new Resource(attributes!)); } [Fact] diff --git a/test/OpenTelemetry.Tests/Shared/CustomTextMapPropagator.cs b/test/OpenTelemetry.Tests/Shared/CustomTextMapPropagator.cs deleted file mode 100644 index a3524f17cb..0000000000 --- a/test/OpenTelemetry.Tests/Shared/CustomTextMapPropagator.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Diagnostics; -using OpenTelemetry.Context.Propagation; - -namespace OpenTelemetry.Tests; - -internal sealed class CustomTextMapPropagator : TextMapPropagator -{ - private static readonly PropagationContext DefaultPropagationContext = default; - - public ActivityTraceId TraceId { get; set; } - - public ActivitySpanId SpanId { get; set; } - - public Action Injected { get; set; } - - public override ISet Fields => null; - -#pragma warning disable SA1201 // Elements should appear in the correct order -#pragma warning disable SA1010 // Opening square brackets should be spaced correctly - public Dictionary> InjectValues = []; -#pragma warning restore SA1010 // Opening square brackets should be spaced correctly -#pragma warning restore SA1201 // Elements should appear in the correct order - - public override PropagationContext Extract(PropagationContext context, T carrier, Func> getter) - { - if (this.TraceId != default && this.SpanId != default) - { - return new PropagationContext( - new ActivityContext( - this.TraceId, - this.SpanId, - ActivityTraceFlags.Recorded), - default); - } - - return DefaultPropagationContext; - } - - public override void Inject(PropagationContext context, T carrier, Action setter) - { - foreach (var kv in this.InjectValues) - { - setter(carrier, kv.Key, kv.Value.Invoke(context)); - } - - this.Injected?.Invoke(context); - } -} diff --git a/test/OpenTelemetry.Tests/Shared/EventSourceTestHelper.cs b/test/OpenTelemetry.Tests/Shared/EventSourceTestHelper.cs index c0f5055d49..deb638e655 100644 --- a/test/OpenTelemetry.Tests/Shared/EventSourceTestHelper.cs +++ b/test/OpenTelemetry.Tests/Shared/EventSourceTestHelper.cs @@ -26,7 +26,7 @@ private static void VerifyMethodImplementation(EventSource eventSource, MethodIn object[] eventArguments = GenerateEventArguments(eventMethod); eventMethod.Invoke(eventSource, eventArguments); - EventWrittenEventArgs actualEvent = listener.Messages.FirstOrDefault(x => x.EventName == eventMethod.Name); + EventWrittenEventArgs? actualEvent = listener.Messages.FirstOrDefault(x => x.EventName == eventMethod.Name); if (actualEvent == null) { @@ -47,7 +47,7 @@ private static void VerifyMethodImplementation(EventSource eventSource, MethodIn } catch (Exception e) { - var name = eventMethod.DeclaringType.Name + "." + eventMethod.Name; + var name = eventMethod.DeclaringType?.Name + "." + eventMethod.Name; throw new Exception("Method '" + name + "' is implemented incorrectly.", e); } @@ -78,7 +78,7 @@ private static object GenerateArgument(ParameterInfo parameter) if (parameter.ParameterType.IsValueType) { - return Activator.CreateInstance(parameter.ParameterType); + return Activator.CreateInstance(parameter.ParameterType)!; } throw new NotSupportedException("Complex types are not supported"); @@ -99,13 +99,14 @@ private static void VerifyEventLevel(MethodInfo eventMethod, EventWrittenEventAr private static void VerifyEventMessage(MethodInfo eventMethod, EventWrittenEventArgs actualEvent, object[] eventArguments) { string expectedMessage = eventArguments.Length == 0 - ? GetEventAttribute(eventMethod).Message - : string.Format(CultureInfo.InvariantCulture, GetEventAttribute(eventMethod).Message, eventArguments); - string actualMessage = string.Format(CultureInfo.InvariantCulture, actualEvent.Message, actualEvent.Payload.ToArray()); + ? GetEventAttribute(eventMethod).Message! + : string.Format(CultureInfo.InvariantCulture, GetEventAttribute(eventMethod).Message!, eventArguments)!; + string actualMessage = string.Format(CultureInfo.InvariantCulture, actualEvent.Message!, actualEvent.Payload!.ToArray()); AssertEqual(nameof(VerifyEventMessage), expectedMessage, actualMessage); } private static void AssertEqual(string methodName, T expected, T actual) + where T : notnull { if (!expected.Equals(actual)) { diff --git a/test/OpenTelemetry.Tests/Shared/MathHelper.cs b/test/OpenTelemetry.Tests/Shared/MathHelper.cs index 11a7573700..0248af0a70 100644 --- a/test/OpenTelemetry.Tests/Shared/MathHelper.cs +++ b/test/OpenTelemetry.Tests/Shared/MathHelper.cs @@ -12,7 +12,7 @@ internal static class MathHelper // https://github.com/dotnet/runtime/blob/v7.0.0/src/libraries/System.Private.CoreLib/src/System/Math.cs#L259 public static double BitIncrement(double x) { -#if NET6_0_OR_GREATER +#if NET return Math.BitIncrement(x); #else long bits = BitConverter.DoubleToInt64Bits(x); @@ -42,7 +42,7 @@ public static double BitIncrement(double x) public static double BitDecrement(double x) { -#if NET6_0_OR_GREATER +#if NET return Math.BitDecrement(x); #else long bits = BitConverter.DoubleToInt64Bits(x); diff --git a/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundFactAttribute.cs b/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundFactAttribute.cs index adfebcaf18..a465db2ee6 100644 --- a/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundFactAttribute.cs +++ b/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundFactAttribute.cs @@ -15,9 +15,9 @@ public SkipUnlessEnvVarFoundFactAttribute(string environmentVariable) } } - public static string GetEnvironmentVariable(string environmentVariableName) + public static string? GetEnvironmentVariable(string environmentVariableName) { - string environmentVariableValue = Environment.GetEnvironmentVariable(environmentVariableName, EnvironmentVariableTarget.Process); + string? environmentVariableValue = Environment.GetEnvironmentVariable(environmentVariableName, EnvironmentVariableTarget.Process); if (string.IsNullOrEmpty(environmentVariableValue)) { diff --git a/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundTheoryAttribute.cs b/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundTheoryAttribute.cs index dc5ce2ca66..0e955e7dd2 100644 --- a/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundTheoryAttribute.cs +++ b/test/OpenTelemetry.Tests/Shared/SkipUnlessEnvVarFoundTheoryAttribute.cs @@ -15,9 +15,9 @@ public SkipUnlessEnvVarFoundTheoryAttribute(string environmentVariable) } } - public static string GetEnvironmentVariable(string environmentVariableName) + public static string? GetEnvironmentVariable(string environmentVariableName) { - string environmentVariableValue = Environment.GetEnvironmentVariable(environmentVariableName, EnvironmentVariableTarget.Process); + string? environmentVariableValue = Environment.GetEnvironmentVariable(environmentVariableName, EnvironmentVariableTarget.Process); if (string.IsNullOrEmpty(environmentVariableValue)) { diff --git a/test/OpenTelemetry.Tests/Shared/SkipUnlessTrueTheoryAttribute.cs b/test/OpenTelemetry.Tests/Shared/SkipUnlessTrueTheoryAttribute.cs index 087bff4366..e1bb798391 100644 --- a/test/OpenTelemetry.Tests/Shared/SkipUnlessTrueTheoryAttribute.cs +++ b/test/OpenTelemetry.Tests/Shared/SkipUnlessTrueTheoryAttribute.cs @@ -23,7 +23,7 @@ public SkipUnlessTrueTheoryAttribute(Type typeContainingTest, string testFieldNa throw new InvalidOperationException($"Field '{testFieldName}' on '{typeContainingTest}' type should be defined as '{typeof(Func)}'."); } - var testFunc = (Func)field.GetValue(null); + var testFunc = (Func)field.GetValue(null)!; if (!testFunc()) { diff --git a/test/OpenTelemetry.Tests/Shared/TestActivityProcessor.cs b/test/OpenTelemetry.Tests/Shared/TestActivityProcessor.cs index 36d47b7b8f..a85511da51 100644 --- a/test/OpenTelemetry.Tests/Shared/TestActivityProcessor.cs +++ b/test/OpenTelemetry.Tests/Shared/TestActivityProcessor.cs @@ -7,14 +7,14 @@ namespace OpenTelemetry.Tests; internal class TestActivityProcessor : BaseProcessor { - public Action StartAction; - public Action EndAction; + public Action? StartAction; + public Action? EndAction; public TestActivityProcessor() { } - public TestActivityProcessor(Action onStart, Action onEnd) + public TestActivityProcessor(Action? onStart, Action? onEnd) { this.StartAction = onStart; this.EndAction = onEnd; diff --git a/test/OpenTelemetry.Tests/Shared/TestEventListener.cs b/test/OpenTelemetry.Tests/Shared/TestEventListener.cs index b0cf1d0b12..fb497190f5 100644 --- a/test/OpenTelemetry.Tests/Shared/TestEventListener.cs +++ b/test/OpenTelemetry.Tests/Shared/TestEventListener.cs @@ -39,7 +39,7 @@ public TestEventListener() } /// Gets or sets the handler for event source creation. - public Action OnOnEventSourceCreated { get; set; } + public Action? OnOnEventSourceCreated { get; set; } /// Gets or sets the handler for event source writes. public Action OnOnEventWritten { get; set; } @@ -81,7 +81,7 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) protected override void OnEventSourceCreated(EventSource eventSource) { // Check for null because this method is called by the base class constructor before we can initialize it - Action callback = this.OnOnEventSourceCreated; + Action? callback = this.OnOnEventSourceCreated; callback?.Invoke(eventSource); } } diff --git a/test/OpenTelemetry.Tests/Shared/TestHttpServer.cs b/test/OpenTelemetry.Tests/Shared/TestHttpServer.cs index 5965e5fecf..bcfcc7d9fd 100644 --- a/test/OpenTelemetry.Tests/Shared/TestHttpServer.cs +++ b/test/OpenTelemetry.Tests/Shared/TestHttpServer.cs @@ -13,7 +13,7 @@ public static IDisposable RunServer(Action action, out stri { host = "localhost"; port = 0; - RunningServer server = null; + RunningServer? server = null; var retryCount = 5; while (retryCount > 0) diff --git a/test/OpenTelemetry.Tests/Shared/TestSampler.cs b/test/OpenTelemetry.Tests/Shared/TestSampler.cs index 7bc39ba454..972985bfad 100644 --- a/test/OpenTelemetry.Tests/Shared/TestSampler.cs +++ b/test/OpenTelemetry.Tests/Shared/TestSampler.cs @@ -7,7 +7,7 @@ namespace OpenTelemetry.Tests; internal class TestSampler : Sampler { - public Func SamplingAction { get; set; } + public Func? SamplingAction { get; set; } public SamplingParameters LatestSamplingParameters { get; private set; } diff --git a/test/OpenTelemetry.Tests/SuppressInstrumentationTest.cs b/test/OpenTelemetry.Tests/SuppressInstrumentationTest.cs index d562699fec..d6ccb414dd 100644 --- a/test/OpenTelemetry.Tests/SuppressInstrumentationTest.cs +++ b/test/OpenTelemetry.Tests/SuppressInstrumentationTest.cs @@ -51,7 +51,7 @@ public void SuppressInstrumentationBeginTest(bool? shouldBegin) } [Fact] - public async void SuppressInstrumentationScopeEnterIsLocalToAsyncFlow() + public async Task SuppressInstrumentationScopeEnterIsLocalToAsyncFlow() { Assert.False(Sdk.SuppressInstrumentation); diff --git a/test/OpenTelemetry.Tests/TestSelfDiagnosticsConfigRefresher.cs b/test/OpenTelemetry.Tests/TestSelfDiagnosticsConfigRefresher.cs index faee6e8f28..9038cb0008 100644 --- a/test/OpenTelemetry.Tests/TestSelfDiagnosticsConfigRefresher.cs +++ b/test/OpenTelemetry.Tests/TestSelfDiagnosticsConfigRefresher.cs @@ -6,17 +6,17 @@ namespace OpenTelemetry.Tests; -internal class TestSelfDiagnosticsConfigRefresher(Stream stream = null) : SelfDiagnosticsConfigRefresher +internal class TestSelfDiagnosticsConfigRefresher(Stream? stream = null) : SelfDiagnosticsConfigRefresher { - private readonly Stream stream = stream; + private readonly Stream? stream = stream; public bool TryGetLogStreamCalled { get; private set; } - public override bool TryGetLogStream(int byteCount, [NotNullWhen(true)] out Stream stream, out int availableByteCount) + public override bool TryGetLogStream(int byteCount, [NotNullWhen(true)] out Stream? stream, out int availableByteCount) { this.TryGetLogStreamCalled = true; stream = this.stream; availableByteCount = 0; - return true; + return this.stream != null; } } diff --git a/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorOptionsTest.cs b/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorOptionsTest.cs index 27f1e3de92..a741928fa2 100644 --- a/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorOptionsTest.cs +++ b/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorOptionsTest.cs @@ -49,7 +49,7 @@ public void BatchExportProcessorOptions_EnvironmentVariableOverride() [Fact] public void BatchExportProcessorOptions_UsingIConfiguration() { - var values = new Dictionary() + var values = new Dictionary() { [BatchExportActivityProcessorOptions.MaxQueueSizeEnvVarKey] = "1", [BatchExportActivityProcessorOptions.MaxExportBatchSizeEnvVarKey] = "2", diff --git a/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorTest.cs b/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorTest.cs index bce5033835..02a5d86ed9 100644 --- a/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorTest.cs +++ b/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorTest.cs @@ -12,7 +12,7 @@ public class BatchExportActivityProcessorTest [Fact] public void CheckNullExporter() { - Assert.Throws(() => new BatchActivityExportProcessor(null)); + Assert.Throws(() => new BatchActivityExportProcessor(null!)); } [Fact] diff --git a/test/OpenTelemetry.Tests/Trace/BatchTest.cs b/test/OpenTelemetry.Tests/Trace/BatchTest.cs index d64c697492..c5445a3bac 100644 --- a/test/OpenTelemetry.Tests/Trace/BatchTest.cs +++ b/test/OpenTelemetry.Tests/Trace/BatchTest.cs @@ -11,7 +11,7 @@ public class BatchTest [Fact] public void CheckConstructorExceptions() { - Assert.Throws(() => new Batch((string[])null, 0)); + Assert.Throws(() => new Batch((string[]?)null!, 0)); Assert.Throws(() => new Batch(Array.Empty(), -1)); Assert.Throws(() => new Batch(Array.Empty(), 1)); diff --git a/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs b/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs index 8f8bccb8e2..343bfa6eec 100644 --- a/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs +++ b/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs @@ -12,12 +12,12 @@ public class CompositeActivityProcessorTests [Fact] public void CompositeActivityProcessor_BadArgs() { - Assert.Throws(() => new CompositeProcessor(null)); + Assert.Throws(() => new CompositeProcessor(null!)); Assert.Throws(() => new CompositeProcessor(Array.Empty>())); using var p1 = new TestActivityProcessor(null, null); using var processor = new CompositeProcessor(new[] { p1 }); - Assert.Throws(() => processor.AddProcessor(null)); + Assert.Throws(() => processor.AddProcessor(null!)); } [Fact] diff --git a/test/OpenTelemetry.Tests/Trace/CurrentSpanTests.cs b/test/OpenTelemetry.Tests/Trace/CurrentSpanTests.cs index e9b7679462..080242299e 100644 --- a/test/OpenTelemetry.Tests/Trace/CurrentSpanTests.cs +++ b/test/OpenTelemetry.Tests/Trace/CurrentSpanTests.cs @@ -15,7 +15,7 @@ public CurrentSpanTests() Activity.DefaultIdFormat = ActivityIdFormat.W3C; Activity.ForceDefaultIdFormat = true; - this.tracer = TracerProvider.Default.GetTracer(null); + this.tracer = TracerProvider.Default.GetTracer(null!); } [Fact] diff --git a/test/OpenTelemetry.Tests/Trace/ExceptionProcessorTest.cs b/test/OpenTelemetry.Tests/Trace/ExceptionProcessorTest.cs index 98d8d01a03..c597cd7c5f 100644 --- a/test/OpenTelemetry.Tests/Trace/ExceptionProcessorTest.cs +++ b/test/OpenTelemetry.Tests/Trace/ExceptionProcessorTest.cs @@ -20,11 +20,11 @@ public void ActivityStatusSetToErrorWhenExceptionProcessorEnabled() .AddProcessor(new ExceptionProcessor()) .Build(); - Activity activity1 = null; - Activity activity2 = null; - Activity activity3 = null; - Activity activity4 = null; - Activity activity5 = null; + Activity? activity1 = null; + Activity? activity2 = null; + Activity? activity3 = null; + Activity? activity4 = null; + Activity? activity5 = null; try { @@ -69,17 +69,27 @@ Marshal.GetExceptionPointers returns non-zero. } finally { - activity5.Dispose(); + activity5?.Dispose(); } + Assert.NotNull(activity1); Assert.Equal(StatusCode.Error, activity1.GetStatus().StatusCode); Assert.Null(GetTagValue(activity1, "otel.exception_pointers")); + + Assert.NotNull(activity2); Assert.Equal(StatusCode.Error, activity2.GetStatus().StatusCode); Assert.Null(GetTagValue(activity2, "otel.exception_pointers")); + + Assert.NotNull(activity3); Assert.Equal(StatusCode.Unset, activity3.GetStatus().StatusCode); Assert.Null(GetTagValue(activity3, "otel.exception_pointers")); + + Assert.NotNull(activity4); Assert.Equal(StatusCode.Unset, activity4.GetStatus().StatusCode); + Assert.Null(GetTagValue(activity4, "otel.exception_pointers")); + + Assert.NotNull(activity5); Assert.Equal(StatusCode.Unset, activity5.GetStatus().StatusCode); #if !NETFRAMEWORK if (Environment.Is64BitProcess) @@ -104,7 +114,7 @@ public void ActivityStatusNotSetWhenExceptionProcessorNotEnabled() .SetSampler(new AlwaysOnSampler()) .Build(); - Activity activity = null; + Activity? activity = null; try { @@ -120,11 +130,11 @@ public void ActivityStatusNotSetWhenExceptionProcessorNotEnabled() Assert.Equal(StatusCode.Unset, activity.GetStatus().StatusCode); } - private static object GetTagValue(Activity activity, string tagName) + private static object? GetTagValue(Activity activity, string tagName) { Debug.Assert(activity != null, "Activity should not be null"); - foreach (ref readonly var tag in activity.EnumerateTagObjects()) + foreach (ref readonly var tag in activity!.EnumerateTagObjects()) { if (tag.Key == tagName) { diff --git a/test/OpenTelemetry.Tests/Trace/ExportProcessorTest.cs b/test/OpenTelemetry.Tests/Trace/ExportProcessorTest.cs index 9c4768bc25..a696d7a2cd 100644 --- a/test/OpenTelemetry.Tests/Trace/ExportProcessorTest.cs +++ b/test/OpenTelemetry.Tests/Trace/ExportProcessorTest.cs @@ -26,6 +26,7 @@ public void ExportProcessorIgnoresActivityWhenDropped() using (var activity = activitySource.StartActivity("Activity")) { + Assert.NotNull(activity); Assert.False(activity.IsAllDataRequested); Assert.Equal(ActivityTraceFlags.None, activity.ActivityTraceFlags); } @@ -49,6 +50,7 @@ public void ExportProcessorIgnoresActivityMarkedAsRecordOnly() using (var activity = activitySource.StartActivity("Activity")) { + Assert.NotNull(activity); Assert.True(activity.IsAllDataRequested); Assert.Equal(ActivityTraceFlags.None, activity.ActivityTraceFlags); } @@ -72,6 +74,7 @@ public void ExportProcessorExportsActivityMarkedAsRecordAndSample() using (var activity = activitySource.StartActivity("Activity")) { + Assert.NotNull(activity); Assert.True(activity.IsAllDataRequested); Assert.Equal(ActivityTraceFlags.Recorded, activity.ActivityTraceFlags); } diff --git a/test/OpenTelemetry.Tests/Trace/LinkTest.cs b/test/OpenTelemetry.Tests/Trace/LinkTest.cs index fbfd543da1..b0a6479359 100644 --- a/test/OpenTelemetry.Tests/Trace/LinkTest.cs +++ b/test/OpenTelemetry.Tests/Trace/LinkTest.cs @@ -50,9 +50,10 @@ public void FromSpanContext_WithAttributes() Assert.Equal(this.spanContext.TraceId, link.Context.TraceId); Assert.Equal(this.spanContext.SpanId, link.Context.SpanId); + Assert.NotNull(link.Attributes); foreach (var attributemap in this.attributesMap) { - Assert.Equal(attributemap.Value, link.Attributes.FirstOrDefault(a => a.Key == attributemap.Key).Value); + Assert.Equal(attributemap.Value, link.Attributes!.FirstOrDefault(a => a.Key == attributemap.Key).Value); } } diff --git a/test/OpenTelemetry.Tests/Trace/ParentBasedSamplerTests.cs b/test/OpenTelemetry.Tests/Trace/ParentBasedSamplerTests.cs index 0861a224ca..7f141da4de 100644 --- a/test/OpenTelemetry.Tests/Trace/ParentBasedSamplerTests.cs +++ b/test/OpenTelemetry.Tests/Trace/ParentBasedSamplerTests.cs @@ -119,7 +119,7 @@ public void CustomSamplers(bool parentIsRemote, bool parentIsSampled) [Fact] public void DisallowNullRootSampler() { - Assert.Throws(() => new ParentBasedSampler(null)); + Assert.Throws(() => new ParentBasedSampler(null!)); } private static SamplingParameters MakeTestParameters(bool parentIsRemote, bool parentIsSampled) diff --git a/test/OpenTelemetry.Tests/Trace/Propagation/TraceContextPropagatorTest.cs b/test/OpenTelemetry.Tests/Trace/Propagation/TraceContextPropagatorTest.cs index 728d3884e4..9c3f566b38 100644 --- a/test/OpenTelemetry.Tests/Trace/Propagation/TraceContextPropagatorTest.cs +++ b/test/OpenTelemetry.Tests/Trace/Propagation/TraceContextPropagatorTest.cs @@ -13,12 +13,12 @@ public class TraceContextPropagatorTest private const string TraceId = "0af7651916cd43dd8448eb211c80319c"; private const string SpanId = "b9c7c989f97918e1"; - private static readonly string[] Empty = Array.Empty(); + private static readonly string[] Empty = []; private static readonly Func, string, IEnumerable> Getter = (headers, name) => { if (headers.TryGetValue(name, out var value)) { - return new[] { value }; + return [value]; } return Empty; @@ -31,7 +31,7 @@ public class TraceContextPropagatorTest return value; } - return Array.Empty(); + return []; }; private static readonly Action, string, string> Setter = (carrier, name, value) => @@ -183,8 +183,18 @@ public void DuplicateKeys() // test_tracestate_duplicated_keys Assert.Empty(CallTraceContextPropagator("foo=1,foo=1")); Assert.Empty(CallTraceContextPropagator("foo=1,foo=2")); - Assert.Empty(CallTraceContextPropagator(new[] { "foo=1", "foo=1" })); - Assert.Empty(CallTraceContextPropagator(new[] { "foo=1", "foo=2" })); + Assert.Empty(CallTraceContextPropagator(["foo=1", "foo=1"])); + Assert.Empty(CallTraceContextPropagator(["foo=1", "foo=2"])); + Assert.Empty(CallTraceContextPropagator("foo=1,bar=2,baz=3,foo=4")); + } + + [Fact] + public void NoDuplicateKeys() + { + Assert.Equal("foo=1,bar=foo,baz=2", CallTraceContextPropagator("foo=1,bar=foo,baz=2")); + Assert.Equal("foo=1,bar=2,baz=foo", CallTraceContextPropagator("foo=1,bar=2,baz=foo")); + Assert.Equal("foo=1,foo@tenant=2", CallTraceContextPropagator("foo=1,foo@tenant=2")); + Assert.Equal("foo=1,tenant@foo=2", CallTraceContextPropagator("foo=1,tenant@foo=2")); } [Fact] @@ -303,7 +313,10 @@ private static string CallTraceContextPropagator(string tracestate) }; var f = new TraceContextPropagator(); var ctx = f.Extract(default, headers, Getter); - return ctx.ActivityContext.TraceState; + + var traceState = ctx.ActivityContext.TraceState; + Assert.NotNull(traceState); + return traceState; } private static string CallTraceContextPropagator(string[] tracestate) @@ -315,6 +328,9 @@ private static string CallTraceContextPropagator(string[] tracestate) }; var f = new TraceContextPropagator(); var ctx = f.Extract(default, headers, ArrayGetter); - return ctx.ActivityContext.TraceState; + + var traceState = ctx.ActivityContext.TraceState; + Assert.NotNull(traceState); + return traceState; } } diff --git a/test/OpenTelemetry.Tests/Trace/SamplersTest.cs b/test/OpenTelemetry.Tests/Trace/SamplersTest.cs index 94f158e910..2c4b85fe52 100644 --- a/test/OpenTelemetry.Tests/Trace/SamplersTest.cs +++ b/test/OpenTelemetry.Tests/Trace/SamplersTest.cs @@ -88,7 +88,7 @@ public void TracerProviderSdkSamplerAttributesAreAppliedToLegacyActivity(Samplin Assert.NotNull(activity); if (samplingDecision != SamplingDecision.Drop) { - Assert.Contains(new KeyValuePair("tagkeybysampler", "tagvalueaddedbysampler"), activity.TagObjects); + Assert.Contains(new KeyValuePair("tagkeybysampler", "tagvalueaddedbysampler"), activity.TagObjects); } activity.Stop(); @@ -200,6 +200,7 @@ public void SamplersCanModifyTraceState(SamplingDecision sampling) using var activity = activitySource.StartActivity("root", ActivityKind.Server, parentContext); if (sampling != SamplingDecision.Drop) { + Assert.NotNull(activity); Assert.Equal(newTraceState, activity.TraceStateString); } } @@ -237,6 +238,7 @@ public void SamplersDoesNotImpactTraceStateWhenUsingNull(SamplingDecision sampli using var activity = activitySource.StartActivity("root", ActivityKind.Server, parentContext); if (sampling != SamplingDecision.Drop) { + Assert.NotNull(activity); Assert.Equal(parentTraceState, activity.TraceStateString); } } diff --git a/test/OpenTelemetry.Tests/Trace/SimpleExportActivityProcessorTest.cs b/test/OpenTelemetry.Tests/Trace/SimpleExportActivityProcessorTest.cs index 5cc52c6eee..2743c48ca8 100644 --- a/test/OpenTelemetry.Tests/Trace/SimpleExportActivityProcessorTest.cs +++ b/test/OpenTelemetry.Tests/Trace/SimpleExportActivityProcessorTest.cs @@ -12,7 +12,7 @@ public class SimpleExportActivityProcessorTest [Fact] public void CheckNullExporter() { - Assert.Throws(() => new SimpleActivityExportProcessor(null)); + Assert.Throws(() => new SimpleActivityExportProcessor(null!)); } [Fact] diff --git a/test/OpenTelemetry.Tests/Trace/TracerProviderBuilderBaseTests.cs b/test/OpenTelemetry.Tests/Trace/TracerProviderBuilderBaseTests.cs index 6c0da445af..ed9a337c6b 100644 --- a/test/OpenTelemetry.Tests/Trace/TracerProviderBuilderBaseTests.cs +++ b/test/OpenTelemetry.Tests/Trace/TracerProviderBuilderBaseTests.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#nullable enable - using Xunit; namespace OpenTelemetry.Trace.Tests; diff --git a/test/OpenTelemetry.Tests/Trace/TracerProviderBuilderExtensionsTest.cs b/test/OpenTelemetry.Tests/Trace/TracerProviderBuilderExtensionsTest.cs index e7b2e7bfc4..c07e05fcb2 100644 --- a/test/OpenTelemetry.Tests/Trace/TracerProviderBuilderExtensionsTest.cs +++ b/test/OpenTelemetry.Tests/Trace/TracerProviderBuilderExtensionsTest.cs @@ -29,7 +29,7 @@ public void SetErrorStatusOnExceptionEnabled() .SetErrorStatusOnException() .Build(); - Activity activity = null; + Activity? activity = null; try { @@ -42,6 +42,7 @@ public void SetErrorStatusOnExceptionEnabled() { } + Assert.NotNull(activity); Assert.Equal(StatusCode.Error, activity.GetStatus().StatusCode); Assert.Equal(ActivityStatusCode.Error, activity.Status); } @@ -58,7 +59,7 @@ public void SetErrorStatusOnExceptionDisabled() .SetErrorStatusOnException(false) .Build(); - Activity activity = null; + Activity? activity = null; try { @@ -71,6 +72,7 @@ public void SetErrorStatusOnExceptionDisabled() { } + Assert.NotNull(activity); Assert.Equal(StatusCode.Unset, activity.GetStatus().StatusCode); Assert.Equal(ActivityStatusCode.Unset, activity.Status); } @@ -85,7 +87,7 @@ public void SetErrorStatusOnExceptionDefault() .SetSampler(new AlwaysOnSampler()) .Build(); - Activity activity = null; + Activity? activity = null; try { @@ -98,6 +100,7 @@ public void SetErrorStatusOnExceptionDefault() { } + Assert.NotNull(activity); Assert.Equal(StatusCode.Unset, activity.GetStatus().StatusCode); } @@ -106,7 +109,7 @@ public void ServiceLifecycleAvailableToSDKBuilderTest() { var builder = Sdk.CreateTracerProviderBuilder(); - MyInstrumentation myInstrumentation = null; + MyInstrumentation? myInstrumentation = null; RunBuilderServiceLifecycleTest( builder, @@ -310,6 +313,7 @@ public void AddProcessorUsingDependencyInjectionTest() using var provider = builder.Build() as TracerProviderSdk; Assert.NotNull(provider); + Assert.NotNull(provider.OwnedServiceProvider); var processors = ((IServiceProvider)provider.OwnedServiceProvider).GetServices(); @@ -328,13 +332,13 @@ public void AddProcessorUsingDependencyInjectionTest() [Fact] public void AddInstrumentationTest() { - List instrumentation = null; + List? instrumentation = null; using (var provider = Sdk.CreateTracerProviderBuilder() .AddInstrumentation() .AddInstrumentation((sp, provider) => new MyInstrumentation() { Provider = provider }) .AddInstrumentation(new MyInstrumentation()) - .AddInstrumentation(() => (object)null) + .AddInstrumentation(() => (object?)null) .Build() as TracerProviderSdk) { Assert.NotNull(provider); @@ -399,7 +403,7 @@ public void SetAndConfigureResourceTest() Assert.True(serviceProviderTestExecuted); Assert.Equal(2, configureInvocations); - + Assert.NotNull(provider); Assert.Single(provider.Resource.Attributes); Assert.Contains(provider.Resource.Attributes, kvp => kvp.Key == "key2" && (string)kvp.Value == "value2"); } @@ -418,7 +422,7 @@ public void ConfigureBuilderIConfigurationAvailableTest() configureBuilderCalled = true; - var testKeyValue = configuration.GetValue("TEST_KEY", null); + var testKeyValue = configuration.GetValue("TEST_KEY", null); Assert.Equal("TEST_KEY_VALUE", testKeyValue); }) @@ -438,7 +442,7 @@ public void ConfigureBuilderIConfigurationModifiableTest() .ConfigureServices(services => { var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary { ["TEST_KEY_2"] = "TEST_KEY_2_VALUE" }) + .AddInMemoryCollection(new Dictionary { ["TEST_KEY_2"] = "TEST_KEY_2_VALUE" }) .Build(); services.AddSingleton(configuration); @@ -449,7 +453,7 @@ public void ConfigureBuilderIConfigurationModifiableTest() configureBuilderCalled = true; - var testKey2Value = configuration.GetValue("TEST_KEY_2", null); + var testKey2Value = configuration.GetValue("TEST_KEY_2", null); Assert.Equal("TEST_KEY_2_VALUE", testKey2Value); }) @@ -652,7 +656,7 @@ public override SamplingResult ShouldSample(in SamplingParameters samplingParame private sealed class MyInstrumentation : IDisposable { - internal TracerProvider Provider; + internal TracerProvider? Provider; internal bool Disposed; public void Dispose() @@ -663,7 +667,7 @@ public void Dispose() private sealed class MyProcessor : BaseProcessor { - public string Name; + public string? Name; public bool Disposed; protected override void Dispose(bool disposing) diff --git a/test/OpenTelemetry.Tests/Trace/TracerProviderSdkTest.cs b/test/OpenTelemetry.Tests/Trace/TracerProviderSdkTest.cs index 044d40c1c6..a94ebfeee1 100644 --- a/test/OpenTelemetry.Tests/Trace/TracerProviderSdkTest.cs +++ b/test/OpenTelemetry.Tests/Trace/TracerProviderSdkTest.cs @@ -128,8 +128,10 @@ public void TracerProviderSdkInvokesSamplingWithCorrectParameters() using (var parent = activitySource.StartActivity("parent", ActivityKind.Client)) { + Assert.NotNull(parent); Assert.Equal(parent.TraceId, testSampler.LatestSamplingParameters.TraceId); using var child = activitySource.StartActivity("child"); + Assert.NotNull(child); Assert.Equal(child.TraceId, testSampler.LatestSamplingParameters.TraceId); Assert.Null(testSampler.LatestSamplingParameters.Tags); Assert.Null(testSampler.LatestSamplingParameters.Links); @@ -145,6 +147,7 @@ public void TracerProviderSdkInvokesSamplingWithCorrectParameters() using (var fromCustomContext = activitySource.StartActivity("customContext", ActivityKind.Client, customContext)) { + Assert.NotNull(fromCustomContext); Assert.Equal(fromCustomContext.TraceId, testSampler.LatestSamplingParameters.TraceId); Assert.Null(testSampler.LatestSamplingParameters.Tags); Assert.Null(testSampler.LatestSamplingParameters.Links); @@ -158,6 +161,7 @@ public void TracerProviderSdkInvokesSamplingWithCorrectParameters() initialTags["tagA"] = "tagAValue"; using (var withInitialTags = activitySource.StartActivity("withInitialTags", ActivityKind.Client, default(ActivityContext), initialTags)) { + Assert.NotNull(withInitialTags); Assert.Equal(withInitialTags.TraceId, testSampler.LatestSamplingParameters.TraceId); Assert.Equal(initialTags, testSampler.LatestSamplingParameters.Tags); } @@ -173,6 +177,7 @@ public void TracerProviderSdkInvokesSamplingWithCorrectParameters() using (var withInitialTags = activitySource.StartActivity("withLinks", ActivityKind.Client, default(ActivityContext), links: links)) { + Assert.NotNull(withInitialTags); Assert.Equal(withInitialTags.TraceId, testSampler.LatestSamplingParameters.TraceId); Assert.Null(testSampler.LatestSamplingParameters.Tags); Assert.Equal(links, testSampler.LatestSamplingParameters.Links); @@ -189,6 +194,7 @@ public void TracerProviderSdkInvokesSamplingWithCorrectParameters() using (var fromCustomContextAsString = activitySource.StartActivity("customContext", ActivityKind.Client, customContextAsString)) { + Assert.NotNull(fromCustomContextAsString); Assert.Equal(fromCustomContextAsString.TraceId, testSampler.LatestSamplingParameters.TraceId); Assert.Equal(expectedTraceId, fromCustomContextAsString.TraceId); Assert.Equal(expectedParentSpanId, fromCustomContextAsString.ParentSpanId); @@ -237,7 +243,7 @@ public void TracerProviderSdkSamplerAttributesAreAppliedToActivity(SamplingDecis Assert.Equal(rootActivity.TraceId, testSampler.LatestSamplingParameters.TraceId); if (sampling != SamplingDecision.Drop) { - Assert.Contains(new KeyValuePair("tagkeybysampler", "tagvalueaddedbysampler"), rootActivity.TagObjects); + Assert.Contains(new KeyValuePair("tagkeybysampler", "tagvalueaddedbysampler"), rootActivity.TagObjects); } } @@ -421,6 +427,8 @@ public void ProcessorDoesNotReceiveNotRecordDecisionSpan() using ActivitySource source = new ActivitySource(activitySourceName); using var activity = source.StartActivity("somename"); + + Assert.NotNull(activity); activity.Stop(); Assert.False(activity.IsAllDataRequested); @@ -1054,11 +1062,11 @@ public void SdkPopulatesSamplingParamsCorrectlyForLegacyActivityWithInProcParent [InlineData("parentbased_traceidratio", "0.111", "ParentBased{TraceIdRatioBasedSampler{0.111000}}")] [InlineData("parentbased_traceidratio", "not_a_double", "ParentBased{TraceIdRatioBasedSampler{1.000000}}")] [InlineData("ParentBased_TraceIdRatio", "0.000001", "ParentBased{TraceIdRatioBasedSampler{0.000001}}")] - public void TestSamplerSetFromConfiguration(string configValue, string argValue, string samplerDescription) + public void TestSamplerSetFromConfiguration(string? configValue, string? argValue, string samplerDescription) { var configBuilder = new ConfigurationBuilder(); - configBuilder.AddInMemoryCollection(new Dictionary + configBuilder.AddInMemoryCollection(new Dictionary { [TracerProviderSdk.TracesSamplerConfigKey] = configValue, [TracerProviderSdk.TracesSamplerArgConfigKey] = argValue, @@ -1078,7 +1086,7 @@ public void TestSamplerSetFromConfiguration(string configValue, string argValue, public void TestSamplerConfigurationIgnoredWhenSetProgrammatically() { var configBuilder = new ConfigurationBuilder(); - configBuilder.AddInMemoryCollection(new Dictionary + configBuilder.AddInMemoryCollection(new Dictionary { [TracerProviderSdk.TracesSamplerConfigKey] = "always_off", }); @@ -1098,7 +1106,7 @@ public void TestSamplerConfigurationIgnoredWhenSetProgrammatically() [Fact] public void TracerProvideSdkCreatesAndDiposesInstrumentation() { - TestInstrumentation testInstrumentation = null; + TestInstrumentation? testInstrumentation = null; var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddInstrumentation(() => { @@ -1132,10 +1140,10 @@ public void TracerProviderSdkBuildsWithDefaultResource() [InlineData(null)] [InlineData("")] [InlineData(" ")] - public void AddLegacyOperationName_BadArgs(string operationName) + public void AddLegacyOperationName_BadArgs(string? operationName) { var builder = Sdk.CreateTracerProviderBuilder(); - Assert.Throws(() => builder.AddLegacySource(operationName)); + Assert.Throws(() => builder.AddLegacySource(operationName!)); } [Fact] @@ -1254,6 +1262,8 @@ public void SdkSamplesAndProcessesLegacySourceWhenAddLegacySourceIsCalledWithWil // Ensure we can still process "normal" activities when in legacy wildcard mode. using var nonLegacyActivity = activitySource.StartActivity("TestActivity"); + + Assert.NotNull(nonLegacyActivity); nonLegacyActivity.Start(); nonLegacyActivity.Stop(); @@ -1300,8 +1310,10 @@ public void Dispose() private static Action CreateActivitySourceSetter() { - return (Action)typeof(Activity).GetProperty("Source") - .SetMethod.CreateDelegate(typeof(Action)); + var setMethod = typeof(Activity).GetProperty("Source")?.SetMethod + ?? throw new InvalidOperationException("Could not build Activity.Source setter delegate"); + + return (Action)setMethod.CreateDelegate(typeof(Action)); } private sealed class TestTracerProviderBuilder : TracerProviderBuilderBase