diff --git a/.github/workflows/ci-concurrency-md.yml b/.github/workflows/ci-concurrency-md.yml
new file mode 100644
index 00000000000..f6898c7f9b2
--- /dev/null
+++ b/.github/workflows/ci-concurrency-md.yml
@@ -0,0 +1,33 @@
+# Syntax: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
+# See also: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks
+
+# Description: This workflow exists to unblock documentation-only PRs.
+
+# IMPORTANT: This workflow MUST use the same 'name' and 'matrix' as the non -md workflow.
+
+
+name: Coyote Concurrency Tests
+
+on:
+ push:
+ branches: [ 'main*' ]
+ paths-ignore:
+ - '**.md'
+ pull_request:
+ branches: [ 'main*' ]
+ paths:
+ - '**.md'
+
+jobs:
+ coyote-concurrency-tests:
+
+ strategy:
+ fail-fast: false # ensures the entire test matrix is run, even if one permutation fails
+ matrix:
+ os: [ windows-latest, ubuntu-latest ]
+ version: [ net8.0 ]
+ project: [ OpenTelemetry.Tests, OpenTelemetry.Api.Tests ]
+
+ runs-on: ${{ matrix.os }}
+ steps:
+ - run: 'echo "No build required"'
diff --git a/.github/workflows/ci-concurrency.yml b/.github/workflows/ci-concurrency.yml
new file mode 100644
index 00000000000..82060ae39f2
--- /dev/null
+++ b/.github/workflows/ci-concurrency.yml
@@ -0,0 +1,41 @@
+name: Coyote Concurrency Tests
+
+on:
+ push:
+ branches: [ 'main*' ]
+ paths-ignore:
+ - '**.md'
+ pull_request:
+ branches: [ 'main*' ]
+ paths-ignore:
+ - '**.md'
+
+jobs:
+ coyote-concurrency-tests:
+
+ strategy:
+ fail-fast: false # ensures the entire test matrix is run, even if one permutation fails
+ matrix:
+ os: [ windows-latest, ubuntu-latest ]
+ version: [ net8.0 ]
+ project: [ OpenTelemetry.Tests, OpenTelemetry.Api.Tests ]
+
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # fetching all
+
+ - name: Setup dotnet
+ uses: actions/setup-dotnet@v3
+
+ - name: Run Coyote Tests
+ shell: pwsh
+ run: .\build\test-threadSafety.ps1 -testProjectName ${{ matrix.project }} -targetFramework ${{ matrix.version }}
+
+ - name: Publish Artifacts
+ if: always() && !cancelled()
+ uses: actions/upload-artifact@v3
+ with:
+ name: ${{ matrix.os }}-${{ matrix.project }}-${{ matrix.version }}-coyoteoutput
+ path: '**/*_CoyoteOutput.*'
diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml
deleted file mode 100644
index 4abdb8d86a3..00000000000
--- a/.github/workflows/examples.yml
+++ /dev/null
@@ -1,62 +0,0 @@
-name: Build examples
-
-on:
- push:
- branches: [ 'main*' ]
- paths-ignore:
- - '**.md'
- pull_request:
- branches: [ 'main*' ]
- paths-ignore:
- - '**.md'
-
-jobs:
- build-test-stable:
- strategy:
- fail-fast: false # ensures the entire test matrix is run, even if one permutation fails
- matrix:
- os: [ windows-latest, ubuntu-latest ]
- version: [ net462, net6.0, net7.0, net8.0 ]
- exclude:
- - os: ubuntu-latest
- version: net462
-
- runs-on: ${{ matrix.os }}
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0 # fetching all
-
- - name: Setup dotnet
- uses: actions/setup-dotnet@v3
-
- - name: Install dependencies
- run: dotnet restore examples/Examples.sln
-
- - name: Build
- run: dotnet build examples/Examples.sln --configuration Release --no-restore --property:ExposeExperimentalFeatures=false
-
- build-test-experimental:
- strategy:
- fail-fast: false # ensures the entire test matrix is run, even if one permutation fails
- matrix:
- os: [ windows-latest, ubuntu-latest ]
- version: [ net462, net6.0, net7.0, net8.0 ]
- exclude:
- - os: ubuntu-latest
- version: net462
-
- runs-on: ${{ matrix.os }}
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0 # fetching all
-
- - name: Setup dotnet
- uses: actions/setup-dotnet@v3
-
- - name: Install dependencies
- run: dotnet restore examples/Examples.sln
-
- - name: Build
- run: dotnet build examples/Examples.sln --configuration Release --no-restore --property:ExposeExperimentalFeatures=true
diff --git a/.gitignore b/.gitignore
index af409279810..e06229e460f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -348,3 +348,6 @@ ASALocalRun/
# Tempo files
tempo-data/
+
+# Coyote Rewrite Files
+rewrite.coyote.json
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 6ca67d71ca7..13fb63b9f08 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -27,12 +27,13 @@
these packages even during major version bumps, so compatibility is not a concern here.
-->
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -51,7 +52,7 @@
3) The .NET runtime team provides extra backward compatibility guarantee to System.Diagnostics.DiagnosticSource
even during major version bumps, so compatibility is not a concern here.
-->
-
+
@@ -67,32 +68,32 @@
This section covers packages that are **not** directly referenced by the NuGet packages published from this repository.
For example, these packages are used in the tests, examples or referenced as "PrivateAssets", but not in the NuGet packages themselves.
-->
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
@@ -106,8 +107,8 @@
-
-
-
+
+
+
diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln
index b963dced2be..337b5953028 100644
--- a/OpenTelemetry.sln
+++ b/OpenTelemetry.sln
@@ -11,11 +11,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.dockerignore = .dockerignore
.editorconfig = .editorconfig
+ .gitignore = .gitignore
+ .github\workflows\ci-concurrency.yml = .github\workflows\ci-concurrency.yml
CONTRIBUTING.md = CONTRIBUTING.md
- Directory.Packages.props = Directory.Packages.props
- test\Directory.Packages.props = test\Directory.Packages.props
- examples\Directory.Packages.props = examples\Directory.Packages.props
- docs\Directory.Packages.props = docs\Directory.Packages.props
global.json = global.json
LICENSE = LICENSE
NuGet.config = NuGet.config
@@ -34,6 +32,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{7CB2F02E
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\finalize-publicapi.ps1 = build\finalize-publicapi.ps1
build\GlobalAttrExclusions.txt = build\GlobalAttrExclusions.txt
build\opentelemetry-icon-color.png = build\opentelemetry-icon-color.png
@@ -44,6 +43,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{7CB2F02E
build\RELEASING.md = build\RELEASING.md
build\stylecop.json = build\stylecop.json
build\test-aot-compatibility.ps1 = build\test-aot-compatibility.ps1
+ build\test-threadSafety.ps1 = build\test-threadSafety.ps1
build\xunit.runner.json = build\xunit.runner.json
EndProjectSection
EndProject
@@ -57,6 +57,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentati
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testdata", "testdata", "{77C7929A-2EED-4AA6-8705-B5C443C8AA0F}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{E359BB2B-9AEC-497D-B321-7DF2450C3B8E}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Shims.OpenTracing", "src\OpenTelemetry.Shims.OpenTracing\OpenTelemetry.Shims.OpenTracing.csproj", "{AAC408FE-40EF-4479-97D9-697F2C1A0B28}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Shims.OpenTracing.Tests", "test\OpenTelemetry.Shims.OpenTracing.Tests\OpenTelemetry.Shims.OpenTracing.Tests.csproj", "{49A7853F-5B6F-4B65-A781-7D29A1C92164}"
@@ -90,6 +92,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
ProjectSection(SolutionItems) = preProject
.github\workflows\ci-aot-md.yml = .github\workflows\ci-aot-md.yml
.github\workflows\ci-aot.yml = .github\workflows\ci-aot.yml
+ .github\workflows\ci-concurrency.yml = .github\workflows\ci-concurrency.yml
+ .github\workflows\ci-concurrency-md.yml = .github\workflows\ci-concurrency-md.yml
.github\workflows\ci-instrumentation-libraries-md.yml = .github\workflows\ci-instrumentation-libraries-md.yml
.github\workflows\ci-instrumentation-libraries.yml = .github\workflows\ci-instrumentation-libraries.yml
.github\workflows\ci-md.yml = .github\workflows\ci-md.yml
@@ -118,10 +122,15 @@ 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}") = "OpenTelemetry.Instrumentation.Grpc.Tests", "test\OpenTelemetry.Instrumentation.Grpc.Tests\OpenTelemetry.Instrumentation.Grpc.Tests.csproj", "{305E9DFD-E73B-4A28-8769-795C25551020}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.Console", "examples\Console\Examples.Console.csproj", "{FF3E6E08-E8E4-4523-B526-847CD989279F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.AspNetCore", "examples\AspNetCore\Examples.AspNetCore.csproj", "{0935622B-9377-4056-8343-AE6ECDC274CF}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "test\Benchmarks\Benchmarks.csproj", "{DE9130A4-F30A-49D7-8834-41DE3021218B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.SqlClient.Tests", "test\OpenTelemetry.Instrumentation.SqlClient.Tests\OpenTelemetry.Instrumentation.SqlClient.Tests.csproj", "{0C606039-BE0A-4EE6-B8F7-F75B41E52CB8}"
@@ -139,6 +148,7 @@ 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
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "trace", "trace", "{5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}"
@@ -153,9 +163,23 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "logs", "logs", "{3862190B-E
docs\logs\getting-started-console\README.md = docs\logs\getting-started-console\README.md
EndProjectSection
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MicroserviceExample", "MicroserviceExample", "{4D492D62-5150-45F9-817F-C99562E364E2}"
+ ProjectSection(SolutionItems) = preProject
+ examples\MicroserviceExample\.dockerignore = examples\MicroserviceExample\.dockerignore
+ examples\MicroserviceExample\docker-compose.yml = examples\MicroserviceExample\docker-compose.yml
+ examples\MicroserviceExample\README.md = examples\MicroserviceExample\README.md
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi", "examples\MicroserviceExample\WebApi\WebApi.csproj", "{07336602-860B-4975-95DD-405D19C00901}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkerService", "examples\MicroserviceExample\WorkerService\WorkerService.csproj", "{FA7A6F67-1F2F-4855-890D-51B5829578A9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utils", "examples\MicroserviceExample\Utils\Utils.csproj", "{5435517C-AEC5-4182-87AE-14E13D31525F}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{CB401DF1-FF5C-4055-886E-1183E832B2D6}"
ProjectSection(SolutionItems) = preProject
docs\Directory.Build.props = docs\Directory.Build.props
+ docs\Directory.Packages.props = docs\Directory.Packages.props
docs\docfx.json = docs\docfx.json
docs\toc.yml = docs\toc.yml
EndProjectSection
@@ -164,6 +188,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "extending-the-sdk", "docs\t
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.W3cTraceContext.Tests", "test\OpenTelemetry.Instrumentation.W3cTraceContext.Tests\OpenTelemetry.Instrumentation.W3cTraceContext.Tests.csproj", "{EAAC5A3C-708A-4609-A21F-8E5221AB58F2}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.GrpcService", "examples\GrpcService\Examples.GrpcService.csproj", "{DB942F5A-D571-4DEA-B1A7-B6BE0E24E6ED}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "getting-started-console", "docs\logs\getting-started-console\getting-started-console.csproj", "{B3F03725-23A0-4582-9526-F6A7E38F35CC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.InMemory", "src\OpenTelemetry.Exporter.InMemory\OpenTelemetry.Exporter.InMemory.csproj", "{9BCEA68B-50E2-4A3A-93E6-B51AF612BCC1}"
@@ -363,6 +389,14 @@ Global
{305E9DFD-E73B-4A28-8769-795C25551020}.Debug|Any CPU.Build.0 = Debug|Any CPU
{305E9DFD-E73B-4A28-8769-795C25551020}.Release|Any CPU.ActiveCfg = Release|Any CPU
{305E9DFD-E73B-4A28-8769-795C25551020}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FF3E6E08-E8E4-4523-B526-847CD989279F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FF3E6E08-E8E4-4523-B526-847CD989279F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FF3E6E08-E8E4-4523-B526-847CD989279F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FF3E6E08-E8E4-4523-B526-847CD989279F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0935622B-9377-4056-8343-AE6ECDC274CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0935622B-9377-4056-8343-AE6ECDC274CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0935622B-9377-4056-8343-AE6ECDC274CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0935622B-9377-4056-8343-AE6ECDC274CF}.Release|Any CPU.Build.0 = Release|Any CPU
{DE9130A4-F30A-49D7-8834-41DE3021218B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE9130A4-F30A-49D7-8834-41DE3021218B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE9130A4-F30A-49D7-8834-41DE3021218B}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -387,6 +421,18 @@ Global
{0246BFC4-8AAF-45E1-A127-DB43D6E345BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0246BFC4-8AAF-45E1-A127-DB43D6E345BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0246BFC4-8AAF-45E1-A127-DB43D6E345BB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {07336602-860B-4975-95DD-405D19C00901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {07336602-860B-4975-95DD-405D19C00901}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {07336602-860B-4975-95DD-405D19C00901}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {07336602-860B-4975-95DD-405D19C00901}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FA7A6F67-1F2F-4855-890D-51B5829578A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FA7A6F67-1F2F-4855-890D-51B5829578A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FA7A6F67-1F2F-4855-890D-51B5829578A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FA7A6F67-1F2F-4855-890D-51B5829578A9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5435517C-AEC5-4182-87AE-14E13D31525F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5435517C-AEC5-4182-87AE-14E13D31525F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5435517C-AEC5-4182-87AE-14E13D31525F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5435517C-AEC5-4182-87AE-14E13D31525F}.Release|Any CPU.Build.0 = Release|Any CPU
{FCDCF532-A163-40DA-80B7-7530AA1182C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FCDCF532-A163-40DA-80B7-7530AA1182C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FCDCF532-A163-40DA-80B7-7530AA1182C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -395,6 +441,10 @@ Global
{EAAC5A3C-708A-4609-A21F-8E5221AB58F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EAAC5A3C-708A-4609-A21F-8E5221AB58F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EAAC5A3C-708A-4609-A21F-8E5221AB58F2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DB942F5A-D571-4DEA-B1A7-B6BE0E24E6ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DB942F5A-D571-4DEA-B1A7-B6BE0E24E6ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DB942F5A-D571-4DEA-B1A7-B6BE0E24E6ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DB942F5A-D571-4DEA-B1A7-B6BE0E24E6ED}.Release|Any CPU.Build.0 = Release|Any CPU
{B3F03725-23A0-4582-9526-F6A7E38F35CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B3F03725-23A0-4582-9526-F6A7E38F35CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3F03725-23A0-4582-9526-F6A7E38F35CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -560,12 +610,19 @@ Global
{E69578EB-B456-4062-A645-877CD964528B} = {F1D0972B-38CF-49C2-9F4B-4C5DE02FB71D}
{C1542297-8763-4DF4-957C-489ED771C21D} = {7CB2F02E-03FA-4FFF-89A5-C51F107623FD}
{D2E73927-5966-445C-94E9-EFE6F269C8D5} = {7CB2F02E-03FA-4FFF-89A5-C51F107623FD}
+ {FF3E6E08-E8E4-4523-B526-847CD989279F} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E}
+ {0935622B-9377-4056-8343-AE6ECDC274CF} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E}
{2C7DD1DA-C229-4D9E-9AF0-BCD5CD3E4948} = {7CB2F02E-03FA-4FFF-89A5-C51F107623FD}
{5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818} = {7C87CAF9-79D7-4C26-9FFB-F3F1FB6911F1}
{3277B1C0-BDFE-4460-9B0D-D9A661FB48DB} = {7C87CAF9-79D7-4C26-9FFB-F3F1FB6911F1}
{3862190B-E2C5-418E-AFDC-DB281FB5C705} = {7C87CAF9-79D7-4C26-9FFB-F3F1FB6911F1}
+ {4D492D62-5150-45F9-817F-C99562E364E2} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E}
+ {07336602-860B-4975-95DD-405D19C00901} = {4D492D62-5150-45F9-817F-C99562E364E2}
+ {FA7A6F67-1F2F-4855-890D-51B5829578A9} = {4D492D62-5150-45F9-817F-C99562E364E2}
+ {5435517C-AEC5-4182-87AE-14E13D31525F} = {4D492D62-5150-45F9-817F-C99562E364E2}
{CB401DF1-FF5C-4055-886E-1183E832B2D6} = {7CB2F02E-03FA-4FFF-89A5-C51F107623FD}
{FCDCF532-A163-40DA-80B7-7530AA1182C4} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
+ {DB942F5A-D571-4DEA-B1A7-B6BE0E24E6ED} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E}
{B3F03725-23A0-4582-9526-F6A7E38F35CC} = {3862190B-E2C5-418E-AFDC-DB281FB5C705}
{13C10C9A-07E8-43EB-91F5-C2B116FBE0FC} = {3862190B-E2C5-418E-AFDC-DB281FB5C705}
{08D29501-F0A3-468F-B18D-BD1821A72383} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
diff --git a/build/Common.nonprod.props b/build/Common.nonprod.props
index 4b4123472a6..d84e98e61e5 100644
--- a/build/Common.nonprod.props
+++ b/build/Common.nonprod.props
@@ -8,7 +8,7 @@
- net7.0
+ net8.0
diff --git a/build/test-threadSafety.ps1 b/build/test-threadSafety.ps1
new file mode 100644
index 00000000000..6694870b6b4
--- /dev/null
+++ b/build/test-threadSafety.ps1
@@ -0,0 +1,34 @@
+param(
+ [Parameter()][string]$coyoteVersion="1.7.10",
+ [Parameter(Mandatory=$true)][string]$testProjectName,
+ [Parameter(Mandatory=$true)][string]$targetFramework,
+ [Parameter()][string]$categoryName="CoyoteConcurrencyTests",
+ [Parameter()][string]$configuration="Release"
+)
+
+$env:OTEL_RUN_COYOTE_TESTS = 'true'
+
+$rootDirectory = Split-Path $PSScriptRoot -Parent
+
+Write-Host "Install Coyote CLI."
+dotnet tool install --global Microsoft.Coyote.CLI
+
+Write-Host "Build $testProjectName project."
+dotnet build "$rootDirectory/test/$testProjectName/$testProjectName.csproj" --configuration $configuration
+
+$artifactsPath = Join-Path $rootDirectory "test/$testProjectName/bin/$configuration/$targetFramework"
+
+Write-Host "Generate Coyote rewriting options JSON file."
+$assemblies = Get-ChildItem $artifactsPath -Filter OpenTelemetry*.dll | ForEach-Object {$_.Name}
+
+$RewriteOptionsJson = @{}
+[void]$RewriteOptionsJson.Add("AssembliesPath", $artifactsPath)
+[void]$RewriteOptionsJson.Add("Assemblies", $assemblies)
+$RewriteOptionsJson | ConvertTo-Json -Compress | Set-Content -Path "$rootDirectory/test/$testProjectName/rewrite.coyote.json"
+
+Write-Host "Run Coyote rewrite."
+coyote rewrite "$rootDirectory/test/$testProjectName/rewrite.coyote.json"
+
+Write-Host "Execute re-written binary."
+dotnet test "$artifactsPath/$testProjectName.dll" --framework $targetFramework --filter CategoryName=$categoryName
+
diff --git a/docs/logs/customizing-the-sdk/README.md b/docs/logs/customizing-the-sdk/README.md
index e8612859127..1c2b20b9ed5 100644
--- a/docs/logs/customizing-the-sdk/README.md
+++ b/docs/logs/customizing-the-sdk/README.md
@@ -56,8 +56,11 @@ For more information on Processors, please review [Extending the SDK](../extendi
[Resource](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md)
is the immutable representation of the entity producing the telemetry.
-If no `Resource` is explicitly configured, the default is to use a resource
-indicating this [Telemetry
+If no `Resource` is explicitly configured, the
+[default](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#semantic-attributes-with-sdk-provided-default-value)
+is to use a resource indicating this
+[Service](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#service)
+and [Telemetry
SDK](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#telemetry-sdk).
The `SetResourceBuilder` method on `OpenTelemetryLoggerOptions` can be used to
set a single `ResourceBuilder`. If `SetResourceBuilder` is called multiple
diff --git a/docs/metrics/customizing-the-sdk/README.md b/docs/metrics/customizing-the-sdk/README.md
index 92b627103e1..fb41c28cdd9 100644
--- a/docs/metrics/customizing-the-sdk/README.md
+++ b/docs/metrics/customizing-the-sdk/README.md
@@ -574,7 +574,9 @@ is the immutable representation of the entity producing the telemetry. If no
`Resource` is explicitly configured, the
[default](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#semantic-attributes-with-sdk-provided-default-value)
is to use a resource indicating this
-[Service](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#service).
+[Service](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#service)
+and [Telemetry
+SDK](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#telemetry-sdk).
The `ConfigureResource` method on `MeterProviderBuilder` can be used to set a
configure the resource on the provider. When the provider is built, it
automatically builds the final `Resource` from the configured `ResourceBuilder`.
diff --git a/docs/trace/customizing-the-sdk/README.md b/docs/trace/customizing-the-sdk/README.md
index d1db1d0c821..fd16648c820 100644
--- a/docs/trace/customizing-the-sdk/README.md
+++ b/docs/trace/customizing-the-sdk/README.md
@@ -289,8 +289,10 @@ writing custom exporters.
is the immutable representation of the entity producing the telemetry. If no
`Resource` is explicitly configured, the
[default](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#semantic-attributes-with-sdk-provided-default-value)
-resource is used to indicate the
-[Service](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#service).
+is to use a resource indicating this
+[Service](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#service)
+and [Telemetry
+SDK](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#telemetry-sdk).
The `ConfigureResource` method on `TracerProviderBuilder` can be used to
configure the resource on the provider. `ConfigureResource` accepts an `Action`
to configure the `ResourceBuilder`. Multiple calls to `ConfigureResource` can be
diff --git a/examples/Examples.sln b/examples/Examples.sln
deleted file mode 100644
index 7773293d014..00000000000
--- a/examples/Examples.sln
+++ /dev/null
@@ -1,67 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.8.34302.71
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MicroserviceExample", "MicroserviceExample", "{196FD231-A91E-499B-AC97-7756752FFF2B}"
- ProjectSection(SolutionItems) = preProject
- ..\MicroserviceExample\.dockerignore = ..\MicroserviceExample\.dockerignore
- ..\MicroserviceExample\docker-compose.yml = ..\MicroserviceExample\docker-compose.yml
- ..\MicroserviceExample\README.md = ..\MicroserviceExample\README.md
- EndProjectSection
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utils", "MicroserviceExample\Utils\Utils.csproj", "{8C276A35-FD69-4B87-84FF-3BAF238B127E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi", "MicroserviceExample\WebApi\WebApi.csproj", "{FD0FE601-113D-47D6-9220-3B7EDBBC3E24}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkerService", "MicroserviceExample\WorkerService\WorkerService.csproj", "{4AA3DDA3-765D-4F19-815B-5A8969AC5389}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.AspNetCore", "AspNetCore\Examples.AspNetCore.csproj", "{CE7324F3-E14C-4EEC-B0D5-9EB9173A459F}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.Console", "Console\Examples.Console.csproj", "{AB07B644-A164-416E-AA66-556DBE94956E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.GrpcService", "GrpcService\Examples.GrpcService.csproj", "{EC5BBA57-A9FC-4656-9455-83D3E2B54BDC}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {8C276A35-FD69-4B87-84FF-3BAF238B127E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8C276A35-FD69-4B87-84FF-3BAF238B127E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8C276A35-FD69-4B87-84FF-3BAF238B127E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8C276A35-FD69-4B87-84FF-3BAF238B127E}.Release|Any CPU.Build.0 = Release|Any CPU
- {FD0FE601-113D-47D6-9220-3B7EDBBC3E24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {FD0FE601-113D-47D6-9220-3B7EDBBC3E24}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FD0FE601-113D-47D6-9220-3B7EDBBC3E24}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {FD0FE601-113D-47D6-9220-3B7EDBBC3E24}.Release|Any CPU.Build.0 = Release|Any CPU
- {4AA3DDA3-765D-4F19-815B-5A8969AC5389}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4AA3DDA3-765D-4F19-815B-5A8969AC5389}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4AA3DDA3-765D-4F19-815B-5A8969AC5389}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4AA3DDA3-765D-4F19-815B-5A8969AC5389}.Release|Any CPU.Build.0 = Release|Any CPU
- {CE7324F3-E14C-4EEC-B0D5-9EB9173A459F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CE7324F3-E14C-4EEC-B0D5-9EB9173A459F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CE7324F3-E14C-4EEC-B0D5-9EB9173A459F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CE7324F3-E14C-4EEC-B0D5-9EB9173A459F}.Release|Any CPU.Build.0 = Release|Any CPU
- {AB07B644-A164-416E-AA66-556DBE94956E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {AB07B644-A164-416E-AA66-556DBE94956E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {AB07B644-A164-416E-AA66-556DBE94956E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AB07B644-A164-416E-AA66-556DBE94956E}.Release|Any CPU.Build.0 = Release|Any CPU
- {EC5BBA57-A9FC-4656-9455-83D3E2B54BDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {EC5BBA57-A9FC-4656-9455-83D3E2B54BDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {EC5BBA57-A9FC-4656-9455-83D3E2B54BDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {EC5BBA57-A9FC-4656-9455-83D3E2B54BDC}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {8C276A35-FD69-4B87-84FF-3BAF238B127E} = {196FD231-A91E-499B-AC97-7756752FFF2B}
- {FD0FE601-113D-47D6-9220-3B7EDBBC3E24} = {196FD231-A91E-499B-AC97-7756752FFF2B}
- {4AA3DDA3-765D-4F19-815B-5A8969AC5389} = {196FD231-A91E-499B-AC97-7756752FFF2B}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {565701CA-CAF6-459D-90F3-255A431B6047}
- EndGlobalSection
-EndGlobal
diff --git a/examples/MicroserviceExample/WebApi/Dockerfile b/examples/MicroserviceExample/WebApi/Dockerfile
index adb1443b70a..d74077a0a87 100644
--- a/examples/MicroserviceExample/WebApi/Dockerfile
+++ b/examples/MicroserviceExample/WebApi/Dockerfile
@@ -1,7 +1,7 @@
-ARG SDK_VERSION=7.0
+ARG SDK_VERSION=8.0
FROM mcr.microsoft.com/dotnet/sdk:${SDK_VERSION} AS build
ARG PUBLISH_CONFIGURATION=Release
-ARG PUBLISH_FRAMEWORK=net7.0
+ARG PUBLISH_FRAMEWORK=net8.0
WORKDIR /app
COPY . ./
RUN dotnet publish ./examples/MicroserviceExample/WebApi -c "${PUBLISH_CONFIGURATION}" -f "${PUBLISH_FRAMEWORK}" -o /out -p:IntegrationBuild=true
diff --git a/examples/MicroserviceExample/WorkerService/Dockerfile b/examples/MicroserviceExample/WorkerService/Dockerfile
index 42de22a9e31..dafc0049b46 100644
--- a/examples/MicroserviceExample/WorkerService/Dockerfile
+++ b/examples/MicroserviceExample/WorkerService/Dockerfile
@@ -1,7 +1,7 @@
-ARG SDK_VERSION=7.0
+ARG SDK_VERSION=8.0
FROM mcr.microsoft.com/dotnet/sdk:${SDK_VERSION} AS build
ARG PUBLISH_CONFIGURATION=Release
-ARG PUBLISH_FRAMEWORK=net7.0
+ARG PUBLISH_FRAMEWORK=net8.0
WORKDIR /app
COPY . ./
RUN dotnet publish ./examples/MicroserviceExample/WorkerService -c "${PUBLISH_CONFIGURATION}" -f "${PUBLISH_FRAMEWORK}" -o /out -p:IntegrationBuild=true
diff --git a/global.json b/global.json
index 16c870f7f47..0aca8b12938 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
"rollForward": "latestFeature",
- "version": "8.0.100-rc.2.23502.2"
+ "version": "8.0.100"
}
}
diff --git a/src/OpenTelemetry.Api.ProviderBuilderExtensions/CHANGELOG.md b/src/OpenTelemetry.Api.ProviderBuilderExtensions/CHANGELOG.md
index c4abeb17c12..7feacfdf7e3 100644
--- a/src/OpenTelemetry.Api.ProviderBuilderExtensions/CHANGELOG.md
+++ b/src/OpenTelemetry.Api.ProviderBuilderExtensions/CHANGELOG.md
@@ -3,8 +3,8 @@
## Unreleased
* Updated `Microsoft.Extensions.DependencyInjection.Abstractions` package
- version to `8.0.0-rc.2.23479.6`.
- ([#5015](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5015))
+ version to `8.0.0`.
+ ([#5051](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5051))
## 1.7.0-alpha.1
diff --git a/src/OpenTelemetry.Api/CHANGELOG.md b/src/OpenTelemetry.Api/CHANGELOG.md
index 67cf8f478ff..94718f8c190 100644
--- a/src/OpenTelemetry.Api/CHANGELOG.md
+++ b/src/OpenTelemetry.Api/CHANGELOG.md
@@ -3,8 +3,8 @@
## Unreleased
* Updated `System.Diagnostics.DiagnosticSource` package version to
- `8.0.0-rc.2.23479.6`.
- ([#4959](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4959))
+ `8.0.0`.
+ ([#5051](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5051))
## 1.7.0-alpha.1
diff --git a/src/OpenTelemetry.Api/Trace/TracerProvider.cs b/src/OpenTelemetry.Api/Trace/TracerProvider.cs
index 50d033cae51..f293b843c62 100644
--- a/src/OpenTelemetry.Api/Trace/TracerProvider.cs
+++ b/src/OpenTelemetry.Api/Trace/TracerProvider.cs
@@ -28,7 +28,7 @@ namespace OpenTelemetry.Trace;
///
public class TracerProvider : BaseProvider
{
- private ConcurrentDictionary? tracers = new();
+ internal ConcurrentDictionary? Tracers = new();
///
/// Initializes a new instance of the class.
@@ -55,7 +55,7 @@ public Tracer GetTracer(
string name,
string? version = null)
{
- var tracers = this.tracers;
+ var tracers = this.Tracers;
if (tracers == null)
{
// Note: Returns a no-op Tracer once dispose has been called.
@@ -68,7 +68,7 @@ public Tracer GetTracer(
{
lock (tracers)
{
- if (this.tracers == null)
+ if (this.Tracers == null)
{
// Note: We check here for a race with Dispose and return a
// no-op Tracer in that case.
@@ -93,7 +93,7 @@ protected override void Dispose(bool disposing)
{
if (disposing)
{
- var tracers = Interlocked.CompareExchange(ref this.tracers, null, this.tracers);
+ var tracers = Interlocked.CompareExchange(ref this.Tracers, null, this.Tracers);
if (tracers != null)
{
lock (tracers)
@@ -114,7 +114,7 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}
- private readonly record struct TracerKey
+ internal readonly record struct TracerKey
{
public readonly string Name;
public readonly string? Version;
diff --git a/src/OpenTelemetry.Extensions.Hosting/.publicApi/Stable/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions.Hosting/.publicApi/Stable/PublicAPI.Unshipped.txt
index e69de29bb2d..8b137891791 100644
--- a/src/OpenTelemetry.Extensions.Hosting/.publicApi/Stable/PublicAPI.Unshipped.txt
+++ b/src/OpenTelemetry.Extensions.Hosting/.publicApi/Stable/PublicAPI.Unshipped.txt
@@ -0,0 +1 @@
+
diff --git a/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md b/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md
index 3032509ec82..6b801fc615c 100644
--- a/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md
+++ b/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md
@@ -3,8 +3,14 @@
## Unreleased
* Updated `Microsoft.Extensions.Hosting.Abstractions` package
- version to `8.0.0-rc.2.23479.6`.
- ([#5015](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5015))
+ version to `8.0.0`.
+ ([#5051](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5051))
+
+* The `OpenTelemetryBuilder.WithMetrics` method will now register an
+ `IMetricsListener` named 'OpenTelemetry' into the `IServiceCollection` to
+ enable metric management via the new `Microsoft.Extensions.Diagnostics` .NET 8
+ APIs.
+ ([#4958](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4958))
## 1.7.0-alpha.1
diff --git a/src/OpenTelemetry.Extensions.Hosting/Implementation/OpenTelemetryMetricsListener.cs b/src/OpenTelemetry.Extensions.Hosting/Implementation/OpenTelemetryMetricsListener.cs
new file mode 100644
index 00000000000..51c8c0d1826
--- /dev/null
+++ b/src/OpenTelemetry.Extensions.Hosting/Implementation/OpenTelemetryMetricsListener.cs
@@ -0,0 +1,120 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System.Diagnostics;
+using System.Diagnostics.Metrics;
+using Microsoft.Extensions.Diagnostics.Metrics;
+
+namespace OpenTelemetry.Metrics;
+
+internal sealed class OpenTelemetryMetricsListener : IMetricsListener, IDisposable
+{
+ private readonly MeterProviderSdk meterProviderSdk;
+ private IObservableInstrumentsSource? observableInstrumentsSource;
+
+ public OpenTelemetryMetricsListener(MeterProvider meterProvider)
+ {
+ var meterProviderSdk = meterProvider as MeterProviderSdk;
+
+ Debug.Assert(meterProviderSdk != null, "meterProvider was not MeterProviderSdk");
+
+ this.meterProviderSdk = meterProviderSdk!;
+
+ this.meterProviderSdk.OnCollectObservableInstruments += this.OnCollectObservableInstruments;
+ }
+
+ public string Name => "OpenTelemetry";
+
+ public void Dispose()
+ {
+ this.meterProviderSdk.OnCollectObservableInstruments -= this.OnCollectObservableInstruments;
+ }
+
+ public MeasurementHandlers GetMeasurementHandlers()
+ {
+ return new MeasurementHandlers()
+ {
+ ByteHandler = (instrument, value, tags, state)
+ => this.MeasurementRecordedLong(instrument, value, tags, state),
+ ShortHandler = (instrument, value, tags, state)
+ => this.MeasurementRecordedLong(instrument, value, tags, state),
+ IntHandler = (instrument, value, tags, state)
+ => this.MeasurementRecordedLong(instrument, value, tags, state),
+ LongHandler = this.MeasurementRecordedLong,
+ FloatHandler = (instrument, value, tags, state)
+ => this.MeasurementRecordedDouble(instrument, value, tags, state),
+ DoubleHandler = this.MeasurementRecordedDouble,
+ };
+ }
+
+ public bool InstrumentPublished(Instrument instrument, out object? userState)
+ {
+ userState = this.meterProviderSdk.InstrumentPublished(instrument, listeningIsManagedExternally: true);
+ return userState != null;
+ }
+
+ public void MeasurementsCompleted(Instrument instrument, object? userState)
+ {
+ var meterProvider = this.meterProviderSdk;
+
+ if (meterProvider.ViewCount > 0)
+ {
+ meterProvider.MeasurementsCompleted(instrument, userState);
+ }
+ else
+ {
+ meterProvider.MeasurementsCompletedSingleStream(instrument, userState);
+ }
+ }
+
+ public void Initialize(IObservableInstrumentsSource source)
+ {
+ this.observableInstrumentsSource = source;
+ }
+
+ private void OnCollectObservableInstruments()
+ {
+ this.observableInstrumentsSource?.RecordObservableInstruments();
+ }
+
+ private void MeasurementRecordedDouble(Instrument instrument, double value, ReadOnlySpan> tagsRos, object? userState)
+ {
+ var meterProvider = this.meterProviderSdk;
+
+ if (meterProvider.ViewCount > 0)
+ {
+ meterProvider.MeasurementRecordedDouble(instrument, value, tagsRos, userState);
+ }
+ else
+ {
+ meterProvider.MeasurementRecordedDoubleSingleStream(instrument, value, tagsRos, userState);
+ }
+ }
+
+ private void MeasurementRecordedLong(Instrument instrument, long value, ReadOnlySpan> tagsRos, object? userState)
+ {
+ var meterProvider = this.meterProviderSdk;
+
+ if (meterProvider.ViewCount > 0)
+ {
+ meterProvider.MeasurementRecordedLong(instrument, value, tagsRos, userState);
+ }
+ else
+ {
+ meterProvider.MeasurementRecordedLongSingleStream(instrument, value, tagsRos, userState);
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetry.Extensions.Hosting.csproj b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetry.Extensions.Hosting.csproj
index 0d589aae7da..cc6b1b078a1 100644
--- a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetry.Extensions.Hosting.csproj
+++ b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetry.Extensions.Hosting.csproj
@@ -10,6 +10,7 @@
+
diff --git a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilder.cs b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilder.cs
index 805808488a1..00af83db167 100644
--- a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilder.cs
+++ b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryBuilder.cs
@@ -15,6 +15,7 @@
//
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.Metrics;
using OpenTelemetry.Internal;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
@@ -61,13 +62,13 @@ public OpenTelemetryBuilder ConfigureResource(
Guard.ThrowIfNull(configure);
this.Services.ConfigureOpenTelemetryMeterProvider(
- (sp, builder) => builder.ConfigureResource(configure));
+ builder => builder.ConfigureResource(configure));
this.Services.ConfigureOpenTelemetryTracerProvider(
- (sp, builder) => builder.ConfigureResource(configure));
+ builder => builder.ConfigureResource(configure));
this.Services.ConfigureOpenTelemetryLoggerProvider(
- (sp, builder) => builder.ConfigureResource(configure));
+ builder => builder.ConfigureResource(configure));
return this;
}
@@ -76,9 +77,15 @@ public OpenTelemetryBuilder ConfigureResource(
/// Adds metric services into the builder.
///
///
- /// Note: This is safe to be called multiple times and by library authors.
+ /// 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.
@@ -95,11 +102,9 @@ public OpenTelemetryBuilder WithMetrics()
/// calls.
public OpenTelemetryBuilder WithMetrics(Action configure)
{
- Guard.ThrowIfNull(configure);
-
- var builder = new MeterProviderBuilderBase(this.Services);
-
- configure(builder);
+ OpenTelemetryMetricsBuilderExtensions.RegisterMetricsListener(
+ this.Services,
+ configure);
return this;
}
diff --git a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryMetricsBuilderExtensions.cs b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryMetricsBuilderExtensions.cs
new file mode 100644
index 00000000000..a34cea3ea3e
--- /dev/null
+++ b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryMetricsBuilderExtensions.cs
@@ -0,0 +1,81 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System.Diagnostics;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using OpenTelemetry.Internal;
+using OpenTelemetry.Metrics;
+
+namespace Microsoft.Extensions.Diagnostics.Metrics;
+
+///
+/// Contains extension methods for registering OpenTelemetry metrics with an
+/// instance.
+///
+internal static class OpenTelemetryMetricsBuilderExtensions
+{
+ ///
+ /// Adds an OpenTelemetry 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 supplied for chaining
+ /// calls.
+ public static IMetricsBuilder UseOpenTelemetry(
+ this IMetricsBuilder metricsBuilder)
+ => UseOpenTelemetry(metricsBuilder, b => { });
+
+ ///
+ /// Adds an OpenTelemetry named 'OpenTelemetry' to the .
+ ///
+ ///
+ /// .
+ ///
+ /// configuration callback.
+ /// The supplied for chaining
+ /// calls.
+ public static IMetricsBuilder UseOpenTelemetry(
+ this IMetricsBuilder metricsBuilder,
+ Action configure)
+ {
+ Guard.ThrowIfNull(metricsBuilder);
+
+ RegisterMetricsListener(metricsBuilder.Services, configure);
+
+ return metricsBuilder;
+ }
+
+ internal static void RegisterMetricsListener(
+ IServiceCollection services,
+ Action configure)
+ {
+ Debug.Assert(services != null, "services was null");
+
+ Guard.ThrowIfNull(configure);
+
+ var builder = new MeterProviderBuilderBase(services!);
+
+ services!.TryAddEnumerable(
+ ServiceDescriptor.Singleton());
+
+ configure(builder);
+ }
+}
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentation.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentation.cs
index dff3081e9e3..d8254871576 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentation.cs
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentation.cs
@@ -27,7 +27,6 @@ internal sealed class AspNetCoreInstrumentation : IDisposable
"Microsoft.AspNetCore.Hosting.HttpRequestIn",
"Microsoft.AspNetCore.Hosting.HttpRequestIn.Start",
"Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop",
- "Microsoft.AspNetCore.Mvc.BeforeAction",
"Microsoft.AspNetCore.Diagnostics.UnhandledException",
"Microsoft.AspNetCore.Hosting.UnhandledException",
};
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetrics.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetrics.cs
index 7c2879b29d3..53e4f8f17ec 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetrics.cs
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetrics.cs
@@ -18,7 +18,6 @@
using System.Diagnostics.Metrics;
using System.Reflection;
using OpenTelemetry.Instrumentation.AspNetCore.Implementation;
-using OpenTelemetry.Internal;
namespace OpenTelemetry.Instrumentation.AspNetCore;
@@ -46,11 +45,10 @@ internal sealed class AspNetCoreMetrics : IDisposable
private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber;
private readonly Meter meter;
- internal AspNetCoreMetrics(AspNetCoreMetricsInstrumentationOptions options)
+ internal AspNetCoreMetrics()
{
- Guard.ThrowIfNull(options);
this.meter = new Meter(InstrumentationName, InstrumentationVersion);
- var metricsListener = new HttpInMetricsListener("Microsoft.AspNetCore", this.meter, options);
+ var metricsListener = new HttpInMetricsListener("Microsoft.AspNetCore", this.meter);
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(metricsListener, this.isEnabled, AspNetCoreInstrumentationEventSource.Log.UnknownErrorProcessingEvent);
this.diagnosticSourceSubscriber.Subscribe();
}
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetricsInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetricsInstrumentationOptions.cs
deleted file mode 100644
index 2ec3ff8a92c..00000000000
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetricsInstrumentationOptions.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-// Copyright The OpenTelemetry Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-using System.Diagnostics;
-using Microsoft.Extensions.Configuration;
-using static OpenTelemetry.Internal.HttpSemanticConventionHelper;
-
-namespace OpenTelemetry.Instrumentation.AspNetCore;
-
-///
-/// Options for metrics requests instrumentation.
-///
-internal sealed class AspNetCoreMetricsInstrumentationOptions
-{
- internal readonly HttpSemanticConvention HttpSemanticConvention;
-
- ///
- /// Initializes a new instance of the class.
- ///
- public AspNetCoreMetricsInstrumentationOptions()
- : this(new ConfigurationBuilder().AddEnvironmentVariables().Build())
- {
- }
-
- internal AspNetCoreMetricsInstrumentationOptions(IConfiguration configuration)
- {
- Debug.Assert(configuration != null, "configuration was null");
-
- this.HttpSemanticConvention = GetSemanticConventionOptIn(configuration);
- }
-}
-
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md
index 15e4180b1b3..67fa080bf8c 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md
@@ -2,6 +2,16 @@
## Unreleased
+* Removed support for `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable. The
+ library will now emit only the
+ [stable](https://github.com/open-telemetry/semantic-conventions/tree/v1.23.0/docs/http)
+ semantic conventions.
+ ([#5066](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5066))
+
+## 1.6.0-beta.3
+
+Released 2023-Nov-17
+
* Removed the Activity Status Description that was being set during
exceptions. Activity Status will continue to be reported as `Error`.
This is a **breaking change**. `EnrichWithException` can be leveraged
@@ -35,6 +45,27 @@ exception. The attribute value will be set to full name of exception type.
* Fixed `network.protocol.version` attribute values to match the specification.
([#5007](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5007))
+* Calls to `/metrics` will now be included in the `http.server.request.duration`
+ metric. This change may affect Prometheus pull scenario if the Prometheus
+ server sends request to the scraping endpoint that contains `/metrics` in
+ path.
+ ([#5044](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5044))
+
+* Fixes the `http.route` attribute for scenarios in which it was
+ previously missing or incorrect. Additionally, the `http.route` attribute
+ is now the same for both the metric and `Activity` emitted for a request.
+ Lastly, the `Activity.DisplayName` has been adjusted to have the format
+ `{http.request.method} {http.route}` to conform with [the specification](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#name).
+ There remain scenarios when using conventional routing or Razor pages where
+ `http.route` is still incorrect. See [#5056](https://github.com/open-telemetry/opentelemetry-dotnet/issues/5056)
+ and [#5057](https://github.com/open-telemetry/opentelemetry-dotnet/issues/5057)
+ for more details.
+ ([#5026](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5026))
+
+* Removed `network.protocol.name` from `http.server.request.duration` metric as
+ per spec.
+ ([#5049](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5049))
+
## 1.6.0-beta.2
Released 2023-Oct-26
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs
index 9b141e938a8..f0ab3611c4a 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs
@@ -23,8 +23,8 @@
using System.Runtime.CompilerServices;
#endif
using Microsoft.AspNetCore.Http;
-#if NET6_0_OR_GREATER
-using Microsoft.AspNetCore.Mvc.Diagnostics;
+#if !NETSTANDARD
+using Microsoft.AspNetCore.Routing;
#endif
using OpenTelemetry.Context.Propagation;
#if !NETSTANDARD2_0
@@ -32,7 +32,6 @@
#endif
using OpenTelemetry.Internal;
using OpenTelemetry.Trace;
-using static OpenTelemetry.Internal.HttpSemanticConventionHelper;
namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation;
@@ -41,7 +40,6 @@ internal class HttpInListener : ListenerHandler
internal const string ActivityOperationName = "Microsoft.AspNetCore.Hosting.HttpRequestIn";
internal const string OnStartEvent = "Microsoft.AspNetCore.Hosting.HttpRequestIn.Start";
internal const string OnStopEvent = "Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop";
- internal const string OnMvcBeforeActionEvent = "Microsoft.AspNetCore.Mvc.BeforeAction";
internal const string OnUnhandledHostingExceptionEvent = "Microsoft.AspNetCore.Hosting.UnhandledException";
internal const string OnUnHandledDiagnosticsExceptionEvent = "Microsoft.AspNetCore.Diagnostics.UnhandledException";
@@ -67,8 +65,6 @@ internal class HttpInListener : ListenerHandler
private readonly PropertyFetcher beforeActionTemplateFetcher = new("Template");
#endif
private readonly AspNetCoreInstrumentationOptions options;
- private readonly bool emitOldAttributes;
- private readonly bool emitNewAttributes;
public HttpInListener(AspNetCoreInstrumentationOptions options)
: base(DiagnosticSourceName)
@@ -76,10 +72,6 @@ public HttpInListener(AspNetCoreInstrumentationOptions options)
Guard.ThrowIfNull(options);
this.options = options;
-
- this.emitOldAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old);
-
- this.emitNewAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New);
}
public override void OnEventWritten(string name, object payload)
@@ -97,12 +89,6 @@ public override void OnEventWritten(string name, object payload)
this.OnStopActivity(Activity.Current, payload);
}
- break;
- case OnMvcBeforeActionEvent:
- {
- this.OnMvcBeforeAction(Activity.Current, payload);
- }
-
break;
case OnUnhandledHostingExceptionEvent:
case OnUnHandledDiagnosticsExceptionEvent:
@@ -202,79 +188,38 @@ public void OnStartActivity(Activity activity, object payload)
#endif
var path = (request.PathBase.HasValue || request.Path.HasValue) ? (request.PathBase + request.Path).ToString() : "/";
- activity.DisplayName = path;
+ activity.DisplayName = this.GetDisplayName(request.Method);
- // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/semantic_conventions/http.md
- if (this.emitOldAttributes)
- {
- if (request.Host.HasValue)
- {
- activity.SetTag(SemanticConventions.AttributeNetHostName, request.Host.Host);
+ // see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md
- if (request.Host.Port is not null && request.Host.Port != 80 && request.Host.Port != 443)
- {
- activity.SetTag(SemanticConventions.AttributeNetHostPort, request.Host.Port);
- }
- }
-
- activity.SetTag(SemanticConventions.AttributeHttpMethod, request.Method);
- activity.SetTag(SemanticConventions.AttributeHttpScheme, request.Scheme);
- activity.SetTag(SemanticConventions.AttributeHttpTarget, path);
- activity.SetTag(SemanticConventions.AttributeHttpUrl, GetUri(request));
- activity.SetTag(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocol(request.Protocol));
+ if (request.Host.HasValue)
+ {
+ activity.SetTag(SemanticConventions.AttributeServerAddress, request.Host.Host);
- if (request.Headers.TryGetValue("User-Agent", out var values))
+ if (request.Host.Port is not null && request.Host.Port != 80 && request.Host.Port != 443)
{
- var userAgent = values.Count > 0 ? values[0] : null;
- if (!string.IsNullOrEmpty(userAgent))
- {
- activity.SetTag(SemanticConventions.AttributeHttpUserAgent, userAgent);
- }
+ activity.SetTag(SemanticConventions.AttributeServerPort, request.Host.Port);
}
}
- // see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-spans.md
- if (this.emitNewAttributes)
+ if (request.QueryString.HasValue)
{
- if (request.Host.HasValue)
- {
- activity.SetTag(SemanticConventions.AttributeServerAddress, request.Host.Host);
-
- if (request.Host.Port is not null && request.Host.Port != 80 && request.Host.Port != 443)
- {
- activity.SetTag(SemanticConventions.AttributeServerPort, request.Host.Port);
- }
- }
-
- if (request.QueryString.HasValue)
- {
- // QueryString should be sanitized. see: https://github.com/open-telemetry/opentelemetry-dotnet/issues/4571
- activity.SetTag(SemanticConventions.AttributeUrlQuery, request.QueryString.Value);
- }
+ // QueryString should be sanitized. see: https://github.com/open-telemetry/opentelemetry-dotnet/issues/4571
+ activity.SetTag(SemanticConventions.AttributeUrlQuery, request.QueryString.Value);
+ }
- if (RequestMethodHelper.KnownMethods.TryGetValue(request.Method, out var httpMethod))
- {
- activity.SetTag(SemanticConventions.AttributeHttpRequestMethod, httpMethod);
- }
- else
- {
- // Set to default "_OTHER" as per spec.
- // https://github.com/open-telemetry/semantic-conventions/blob/v1.22.0/docs/http/http-spans.md#common-attributes
- activity.SetTag(SemanticConventions.AttributeHttpRequestMethod, "_OTHER");
- activity.SetTag(SemanticConventions.AttributeHttpRequestMethodOriginal, request.Method);
- }
+ RequestMethodHelper.SetHttpMethodTag(activity, request.Method);
- activity.SetTag(SemanticConventions.AttributeUrlScheme, request.Scheme);
- activity.SetTag(SemanticConventions.AttributeUrlPath, path);
- activity.SetTag(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetFlavorTagValueFromProtocol(request.Protocol));
+ activity.SetTag(SemanticConventions.AttributeUrlScheme, request.Scheme);
+ activity.SetTag(SemanticConventions.AttributeUrlPath, path);
+ activity.SetTag(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetFlavorTagValueFromProtocol(request.Protocol));
- if (request.Headers.TryGetValue("User-Agent", out var values))
+ if (request.Headers.TryGetValue("User-Agent", out var values))
+ {
+ var userAgent = values.Count > 0 ? values[0] : null;
+ if (!string.IsNullOrEmpty(userAgent))
{
- var userAgent = values.Count > 0 ? values[0] : null;
- if (!string.IsNullOrEmpty(userAgent))
- {
- activity.SetTag(SemanticConventions.AttributeUserAgentOriginal, userAgent);
- }
+ activity.SetTag(SemanticConventions.AttributeUserAgentOriginal, userAgent);
}
}
@@ -302,15 +247,16 @@ public void OnStopActivity(Activity activity, object payload)
var response = context.Response;
- if (this.emitOldAttributes)
+#if !NETSTANDARD
+ var routePattern = (context.GetEndpoint() as RouteEndpoint)?.RoutePattern.RawText;
+ if (!string.IsNullOrEmpty(routePattern))
{
- activity.SetTag(SemanticConventions.AttributeHttpStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode));
+ activity.DisplayName = this.GetDisplayName(context.Request.Method, routePattern);
+ activity.SetTag(SemanticConventions.AttributeHttpRoute, routePattern);
}
+#endif
- if (this.emitNewAttributes)
- {
- activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode));
- }
+ activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode));
#if !NETSTANDARD2_0
if (this.options.EnableGrpcAspNetCoreSupport && TryGetGrpcMethod(activity, out var grpcMethod))
@@ -363,57 +309,6 @@ public void OnStopActivity(Activity activity, object payload)
}
}
- public void OnMvcBeforeAction(Activity activity, object payload)
- {
- // We cannot rely on Activity.Current here
- // There could be activities started by middleware
- // after activity started by framework resulting in different Activity.Current.
- // so, we need to first find the activity started by Asp.Net Core.
- // For .net6.0 onwards we could use IHttpActivityFeature to get the activity created by framework
- // var httpActivityFeature = context.Features.Get();
- // activity = httpActivityFeature.Activity;
- // However, this will not work as in case of custom propagator
- // we start a new activity during onStart event which is a sibling to the activity created by framework
- // So, in that case we need to get the activity created by us here.
- // we can do so only by looping through activity.Parent chain.
- while (activity != null)
- {
- if (string.Equals(activity.OperationName, ActivityOperationName, StringComparison.Ordinal))
- {
- break;
- }
-
- activity = activity.Parent;
- }
-
- if (activity == null)
- {
- return;
- }
-
- if (activity.IsAllDataRequested)
- {
-#if !NET6_0_OR_GREATER
- _ = this.beforeActionActionDescriptorFetcher.TryFetch(payload, out var actionDescriptor);
- _ = this.beforeActionAttributeRouteInfoFetcher.TryFetch(actionDescriptor, out var attributeRouteInfo);
- _ = this.beforeActionTemplateFetcher.TryFetch(attributeRouteInfo, out var template);
-#else
- var beforeActionEventData = payload as BeforeActionEventData;
- var template = beforeActionEventData.ActionDescriptor?.AttributeRouteInfo?.Template;
-#endif
- if (!string.IsNullOrEmpty(template))
- {
- // override the span name that was previously set to the path part of URL.
- activity.DisplayName = template;
- activity.SetTag(SemanticConventions.AttributeHttpRoute, template);
- }
-
- // TODO: Should we get values from RouteData?
- // private readonly PropertyFetcher beforeActionRouteDataFetcher = new PropertyFetcher("routeData");
- // var routeData = this.beforeActionRouteDataFetcher.Fetch(payload) as RouteData;
- }
- }
-
public void OnException(Activity activity, object payload)
{
if (activity.IsAllDataRequested)
@@ -425,10 +320,7 @@ public void OnException(Activity activity, object payload)
return;
}
- if (this.emitNewAttributes)
- {
- activity.SetTag(SemanticConventions.AttributeErrorType, exc.GetType().FullName);
- }
+ activity.SetTag(SemanticConventions.AttributeErrorType, exc.GetType().FullName);
if (this.options.RecordException)
{
@@ -509,7 +401,18 @@ private static bool TryGetGrpcMethod(Activity activity, out string grpcMethod)
grpcMethod = GrpcTagHelper.GetGrpcMethodFromActivity(activity);
return !string.IsNullOrEmpty(grpcMethod);
}
+#endif
+
+ private string GetDisplayName(string httpMethod, string httpRoute = null)
+ {
+ var normalizedMethod = RequestMethodHelper.GetNormalizedHttpMethod(httpMethod);
+
+ return string.IsNullOrEmpty(httpRoute)
+ ? normalizedMethod
+ : $"{normalizedMethod} {httpRoute}";
+ }
+#if !NETSTANDARD2_0
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AddGrpcAttributes(Activity activity, string grpcMethod, HttpContext context)
{
@@ -520,28 +423,15 @@ private void AddGrpcAttributes(Activity activity, string grpcMethod, HttpContext
activity.SetTag(SemanticConventions.AttributeRpcSystem, GrpcTagHelper.RpcSystemGrpc);
- if (this.emitOldAttributes)
- {
- if (context.Connection.RemoteIpAddress != null)
- {
- // TODO: This attribute was changed in v1.13.0 https://github.com/open-telemetry/opentelemetry-specification/pull/2614
- activity.SetTag(SemanticConventions.AttributeNetPeerIp, context.Connection.RemoteIpAddress.ToString());
- }
-
- activity.SetTag(SemanticConventions.AttributeNetPeerPort, context.Connection.RemotePort);
- }
+ // see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/rpc/rpc-spans.md
- // see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/rpc/rpc-spans.md
- if (this.emitNewAttributes)
+ if (context.Connection.RemoteIpAddress != null)
{
- if (context.Connection.RemoteIpAddress != null)
- {
- activity.SetTag(SemanticConventions.AttributeClientAddress, context.Connection.RemoteIpAddress.ToString());
- }
-
- activity.SetTag(SemanticConventions.AttributeClientPort, context.Connection.RemotePort);
+ activity.SetTag(SemanticConventions.AttributeClientAddress, context.Connection.RemoteIpAddress.ToString());
}
+ activity.SetTag(SemanticConventions.AttributeClientPort, context.Connection.RemotePort);
+
bool validConversion = GrpcTagHelper.TryGetGrpcStatusCodeFromActivity(activity, out int status);
if (validConversion)
{
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs
index 5d8db405158..a47ee19858e 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs
@@ -24,13 +24,11 @@
using Microsoft.AspNetCore.Routing;
#endif
using OpenTelemetry.Trace;
-using static OpenTelemetry.Internal.HttpSemanticConventionHelper;
namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation;
internal sealed class HttpInMetricsListener : ListenerHandler
{
- internal const string HttpServerDurationMetricName = "http.server.duration";
internal const string HttpServerRequestDurationMetricName = "http.server.request.duration";
internal const string OnUnhandledHostingExceptionEvent = "Microsoft.AspNetCore.Hosting.UnhandledException";
@@ -43,31 +41,13 @@ internal sealed class HttpInMetricsListener : ListenerHandler
private static readonly object ErrorTypeHttpContextItemsKey = new();
private readonly Meter meter;
- private readonly AspNetCoreMetricsInstrumentationOptions options;
- private readonly Histogram httpServerDuration;
private readonly Histogram httpServerRequestDuration;
- private readonly bool emitOldAttributes;
- private readonly bool emitNewAttributes;
- internal HttpInMetricsListener(string name, Meter meter, AspNetCoreMetricsInstrumentationOptions options)
+ internal HttpInMetricsListener(string name, Meter meter)
: base(name)
{
this.meter = meter;
- this.options = options;
-
- this.emitOldAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old);
-
- this.emitNewAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New);
-
- if (this.emitOldAttributes)
- {
- this.httpServerDuration = meter.CreateHistogram(HttpServerDurationMetricName, "ms", "Measures the duration of inbound HTTP requests.");
- }
-
- if (this.emitNewAttributes)
- {
- this.httpServerRequestDuration = meter.CreateHistogram(HttpServerRequestDurationMetricName, "s", "Duration of HTTP server requests.");
- }
+ this.httpServerRequestDuration = meter.CreateHistogram(HttpServerRequestDurationMetricName, "s", "Duration of HTTP server requests.");
}
public override void OnEventWritten(string name, object payload)
@@ -77,24 +57,13 @@ public override void OnEventWritten(string name, object payload)
case OnUnhandledDiagnosticsExceptionEvent:
case OnUnhandledHostingExceptionEvent:
{
- if (this.emitNewAttributes)
- {
- this.OnExceptionEventWritten(name, payload);
- }
+ this.OnExceptionEventWritten(name, payload);
}
break;
case OnStopEvent:
{
- if (this.emitOldAttributes)
- {
- this.OnEventWritten_Old(name, payload);
- }
-
- if (this.emitNewAttributes)
- {
- this.OnEventWritten_New(name, payload);
- }
+ this.OnStopEventWritten(name, payload);
}
break;
@@ -106,7 +75,7 @@ public void OnExceptionEventWritten(string name, object payload)
// We need to use reflection here as the payload type is not a defined public type.
if (!TryFetchException(payload, out Exception exc) || !TryFetchHttpContext(payload, out HttpContext ctx))
{
- AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInMetricsListener), nameof(this.OnExceptionEventWritten), HttpServerDurationMetricName);
+ AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInMetricsListener), nameof(this.OnExceptionEventWritten), HttpServerRequestDurationMetricName);
return;
}
@@ -127,56 +96,7 @@ static bool TryFetchHttpContext(object payload, out HttpContext ctx)
=> HttpContextPropertyFetcher.TryFetch(payload, out ctx) && ctx != null;
}
- public void OnEventWritten_Old(string name, object payload)
- {
- var context = payload as HttpContext;
-
- if (context == null)
- {
- AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInMetricsListener), EventName, HttpServerDurationMetricName);
- return;
- }
-
- // TODO: Prometheus pulls metrics by invoking the /metrics endpoint. Decide if it makes sense to suppress this.
- // Below is just a temporary way of achieving this suppression for metrics (we should consider suppressing traces too).
- // If we want to suppress activity from Prometheus then we should use SuppressInstrumentationScope.
- if (context.Request.Path.HasValue && context.Request.Path.Value.Contains("metrics"))
- {
- return;
- }
-
- TagList tags = default;
-
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocol(context.Request.Protocol)));
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpScheme, context.Request.Scheme));
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpMethod, context.Request.Method));
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpStatusCode, TelemetryHelper.GetBoxedStatusCode(context.Response.StatusCode)));
-
- if (context.Request.Host.HasValue)
- {
- tags.Add(new KeyValuePair(SemanticConventions.AttributeNetHostName, context.Request.Host.Host));
-
- if (context.Request.Host.Port is not null && context.Request.Host.Port != 80 && context.Request.Host.Port != 443)
- {
- tags.Add(new KeyValuePair(SemanticConventions.AttributeNetHostPort, context.Request.Host.Port));
- }
- }
-
-#if NET6_0_OR_GREATER
- var route = (context.GetEndpoint() as RouteEndpoint)?.RoutePattern.RawText;
- if (!string.IsNullOrEmpty(route))
- {
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpRoute, route));
- }
-#endif
-
- // We are relying here on ASP.NET Core to set duration before writing the stop event.
- // https://github.com/dotnet/aspnetcore/blob/d6fa351048617ae1c8b47493ba1abbe94c3a24cf/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs#L449
- // TODO: Follow up with .NET team if we can continue to rely on this behavior.
- this.httpServerDuration.Record(Activity.Current.Duration.TotalMilliseconds, tags);
- }
-
- public void OnEventWritten_New(string name, object payload)
+ public void OnStopEventWritten(string name, object payload)
{
var context = payload as HttpContext;
if (context == null)
@@ -185,31 +105,15 @@ public void OnEventWritten_New(string name, object payload)
return;
}
- // TODO: Prometheus pulls metrics by invoking the /metrics endpoint. Decide if it makes sense to suppress this.
- // Below is just a temporary way of achieving this suppression for metrics (we should consider suppressing traces too).
- // If we want to suppress activity from Prometheus then we should use SuppressInstrumentationScope.
- if (context.Request.Path.HasValue && context.Request.Path.Value.Contains("metrics"))
- {
- return;
- }
-
TagList tags = default;
// see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-spans.md
- tags.Add(new KeyValuePair(SemanticConventions.AttributeNetworkProtocolName, NetworkProtocolName));
tags.Add(new KeyValuePair(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetFlavorTagValueFromProtocol(context.Request.Protocol)));
tags.Add(new KeyValuePair(SemanticConventions.AttributeUrlScheme, context.Request.Scheme));
tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(context.Response.StatusCode)));
- if (RequestMethodHelper.KnownMethods.TryGetValue(context.Request.Method, out var httpMethod))
- {
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, httpMethod));
- }
- else
- {
- // Set to default "_OTHER" as per spec.
- // https://github.com/open-telemetry/semantic-conventions/blob/v1.22.0/docs/http/http-spans.md#common-attributes
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, "_OTHER"));
- }
+
+ var httpMethod = RequestMethodHelper.GetNormalizedHttpMethod(context.Request.Method);
+ tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, httpMethod));
#if NET6_0_OR_GREATER
var route = (context.GetEndpoint() as RouteEndpoint)?.RoutePattern.RawText;
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/MeterProviderBuilderExtensions.cs
index 6118642b2ed..c394a3590ee 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/MeterProviderBuilderExtensions.cs
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/MeterProviderBuilderExtensions.cs
@@ -15,8 +15,6 @@
//
#if !NET8_0_OR_GREATER
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Options;
using OpenTelemetry.Instrumentation.AspNetCore;
using OpenTelemetry.Instrumentation.AspNetCore.Implementation;
#endif
@@ -46,22 +44,9 @@ public static MeterProviderBuilder AddAspNetCoreInstrumentation(
_ = TelemetryHelper.BoxedStatusCodes;
_ = RequestMethodHelper.KnownMethods;
- builder.ConfigureServices(services =>
- {
- services.RegisterOptionsFactory(configuration => new AspNetCoreMetricsInstrumentationOptions(configuration));
- });
-
builder.AddMeter(AspNetCoreMetrics.InstrumentationName);
- builder.AddInstrumentation(sp =>
- {
- var options = sp.GetRequiredService>().Get(Options.DefaultName);
-
- // TODO: Add additional options to AspNetCoreMetricsInstrumentationOptions ?
- // RecordException - probably doesn't make sense for metric instrumentation
- // EnableGrpcAspNetCoreSupport - this instrumentation will also need to also handle gRPC requests
- return new AspNetCoreMetrics(options);
- });
+ builder.AddInstrumentation(new AspNetCoreMetrics());
return builder;
#endif
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/README.md b/src/OpenTelemetry.Instrumentation.AspNetCore/README.md
index c62dd0050d6..5f32153a444 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/README.md
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/README.md
@@ -90,31 +90,51 @@ public void ConfigureServices(IServiceCollection services)
#### List of metrics produced
-A different metric is emitted depending on whether a user opts-in to the new
-Http Semantic Conventions using `OTEL_SEMCONV_STABILITY_OPT_IN`.
-
-* By default, the instrumentation emits the following metric.
-
- | Name | Instrument Type | Unit | Description | Attributes |
- |-------|-----------------|------|-------------|------------|
- | `http.server.duration` | Histogram | `ms` | Measures the duration of inbound HTTP requests. | http.flavor, http.scheme, http.method, http.status_code, net.host.name, net.host.port, http.route |
-
-* If user sets the environment variable to `http`, the instrumentation emits
- the following metric.
-
- | Name | Instrument Type | Unit | Description | Attributes |
- |-------|-----------------|------|-------------|------------|
- | `http.server.request.duration` | Histogram | `s` | Measures the duration of inbound HTTP requests. | network.protocol.version, url.scheme, http.request.method, http.response.status_code, http.route |
-
- This metric is emitted in `seconds` as per the semantic convention. While
- the convention [recommends using custom histogram buckets](https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-metrics.md)
- , this feature is not yet available via .NET Metrics API.
- A [workaround](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4820)
+When the application targets `.NET6.0` or `.NET7.0`, the instrumentation emits
+the following metric:
+
+| Name | Details |
+|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `http.server.request.duration` | [Specification](https://github.com/open-telemetry/semantic-conventions/blob/release/v1.23.x/docs/http/http-metrics.md#metric-httpserverrequestduration) |
+
+Starting from `.NET8.0`, metrics instrumentation is natively implemented, and
+the ASP.NET Core library has incorporated support for [built-in
+metrics](https://learn.microsoft.com/dotnet/core/diagnostics/built-in-metrics-aspnetcore)
+following the OpenTelemetry semantic conventions. The library includes additional
+metrics beyond those defined in the
+[specification](https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-metrics.md),
+covering additional scenarios for ASP.NET Core users. When the application
+targets `.NET8.0` and newer versions, the instrumentation library automatically
+enables all `built-in` metrics by default.
+
+Note that the `AddAspNetCoreInstrumentation()` extension simplifies the process
+of enabling all built-in metrics via a single line of code. Alternatively, for
+more granular control over emitted metrics, you can utilize the `AddMeter()`
+extension on `MeterProviderBuilder` for meters listed in
+[built-in-metrics-aspnetcore](https://learn.microsoft.com/dotnet/core/diagnostics/built-in-metrics-aspnetcore).
+Using `AddMeter()` for metrics activation eliminates the need to take dependency
+on the instrumentation library package and calling
+`AddAspNetCoreInstrumentation()`.
+
+If you utilize `AddAspNetCoreInstrumentation()` and wish to exclude unnecessary
+metrics, you can utilize
+[Views](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/docs/metrics/customizing-the-sdk#drop-an-instrument)
+to achieve this.
+
+**Note:** There is no difference in features or emitted metrics when enabling
+metrics using `AddMeter()` or `AddAspNetCoreInstrumentation()` on `.NET8.0` and
+newer versions.
+
+> **Note**
+> The `http.server.request.duration` metric is emitted in `seconds` as
+ per the semantic convention. While the convention [recommends using custom
+ histogram
+ buckets](https://github.com/open-telemetry/semantic-conventions/blob/release/v1.23.x/docs/http/http-metrics.md)
+ , this feature is not yet available via .NET Metrics API. A
+ [workaround](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4820)
has been included in OTel SDK starting version `1.6.0` which applies
- recommended buckets by default for `http.server.request.duration`.
-
-* If user sets the environment variable to `http/dup`, the instrumentation
- emits both `http.server.duration` and `http.server.request.duration`.
+ recommended buckets by default for `http.server.request.duration`. This
+ applies to all targeted frameworks.
## Advanced configuration
diff --git a/src/OpenTelemetry.Instrumentation.GrpcNetClient/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.GrpcNetClient/CHANGELOG.md
index a244edccb4f..2299a1573a9 100644
--- a/src/OpenTelemetry.Instrumentation.GrpcNetClient/CHANGELOG.md
+++ b/src/OpenTelemetry.Instrumentation.GrpcNetClient/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+## 1.6.0-beta.3
+
+Released 2023-Nov-17
+
## 1.6.0-beta.2
Released 2023-Oct-26
diff --git a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md
index 48fcda3d101..c54b2eefd09 100644
--- a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md
+++ b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md
@@ -2,6 +2,16 @@
## Unreleased
+* Removed support for `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable. The
+ library will now emit only the
+ [stable](https://github.com/open-telemetry/semantic-conventions/tree/v1.23.0/docs/http)
+ semantic conventions.
+ ([#5068](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5068))
+
+## 1.6.0-beta.3
+
+Released 2023-Nov-17
+
* Removed the Activity Status Description that was being set during
exceptions. Activity Status will continue to be reported as `Error`.
This is a **breaking change**. `EnrichWithException` can be leveraged
@@ -44,6 +54,12 @@
* Fixed `network.protocol.version` attribute values to match the specification.
([#5006](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5006))
+* Set `network.protocol.version` value using the protocol version on the
+ received response. If the request fails without response, then
+ `network.protocol.version` attribute will not be set on Activity and
+ `http.client.request.duration` metric.
+ ([#5043](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5043))
+
## 1.6.0-beta.2
Released 2023-Oct-26
@@ -118,6 +134,10 @@ Released 2023-Oct-26
definition.
([#4990](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4990))
+* `dns.lookups.duration` metric is renamed to `dns.lookup.duration`. This change
+ impacts only users on `.NET8.0` or newer framework.
+ ([#5049](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5049))
+
## 1.5.1-beta.1
Released 2023-Jul-20
diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientMetricInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientMetricInstrumentationOptions.cs
deleted file mode 100644
index a35b615eec6..00000000000
--- a/src/OpenTelemetry.Instrumentation.Http/HttpClientMetricInstrumentationOptions.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-//
-// Copyright The OpenTelemetry Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-using System.Diagnostics;
-using Microsoft.Extensions.Configuration;
-using static OpenTelemetry.Internal.HttpSemanticConventionHelper;
-
-namespace OpenTelemetry.Instrumentation.Http;
-
-internal sealed class HttpClientMetricInstrumentationOptions
-{
- internal readonly HttpSemanticConvention HttpSemanticConvention;
-
- ///
- /// Initializes a new instance of the class.
- ///
- public HttpClientMetricInstrumentationOptions()
- : this(new ConfigurationBuilder().AddEnvironmentVariables().Build())
- {
- }
-
- internal HttpClientMetricInstrumentationOptions(IConfiguration configuration)
- {
- Debug.Assert(configuration != null, "configuration was null");
-
- this.HttpSemanticConvention = GetSemanticConventionOptIn(configuration);
- }
-}
diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs
index 2f4db9ec9b2..6472ab91cff 100644
--- a/src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs
+++ b/src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs
@@ -37,11 +37,10 @@ internal sealed class HttpClientMetrics : IDisposable
///
/// Initializes a new instance of the class.
///
- /// HttpClient metric instrumentation options.
- public HttpClientMetrics(HttpClientMetricInstrumentationOptions options)
+ public HttpClientMetrics()
{
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(
- new HttpHandlerMetricsDiagnosticListener("HttpHandlerDiagnosticListener", options),
+ new HttpHandlerMetricsDiagnosticListener("HttpHandlerDiagnosticListener"),
this.isEnabled,
HttpInstrumentationEventSource.Log.UnknownErrorProcessingEvent);
this.diagnosticSourceSubscriber.Subscribe();
diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs
index 924f260a90e..68e08949228 100644
--- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs
+++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs
@@ -25,7 +25,6 @@
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Internal;
using OpenTelemetry.Trace;
-using static OpenTelemetry.Internal.HttpSemanticConventionHelper;
namespace OpenTelemetry.Instrumentation.Http.Implementation;
@@ -49,8 +48,6 @@ internal sealed class HttpHandlerDiagnosticListener : ListenerHandler
private static readonly PropertyFetcher StopExceptionFetcher = new("Exception");
private static readonly PropertyFetcher StopRequestStatusFetcher = new("RequestTaskStatus");
private readonly HttpClientInstrumentationOptions options;
- private readonly bool emitOldAttributes;
- private readonly bool emitNewAttributes;
static HttpHandlerDiagnosticListener()
{
@@ -68,10 +65,6 @@ public HttpHandlerDiagnosticListener(HttpClientInstrumentationOptions options)
: base("HttpHandlerDiagnosticListener")
{
this.options = options;
-
- this.emitOldAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old);
-
- this.emitNewAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New);
}
public override void OnEventWritten(string name, object payload)
@@ -168,52 +161,33 @@ public void OnStartActivity(Activity activity, object payload)
ActivityInstrumentationHelper.SetKindProperty(activity, ActivityKind.Client);
}
- // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/semantic_conventions/http.md
- if (this.emitOldAttributes)
+ // see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md
+ if (RequestMethodHelper.KnownMethods.TryGetValue(request.Method.Method, out var httpMethod))
{
- activity.SetTag(SemanticConventions.AttributeHttpScheme, request.RequestUri.Scheme);
- activity.SetTag(SemanticConventions.AttributeHttpMethod, HttpTagHelper.GetNameForHttpMethod(request.Method));
- activity.SetTag(SemanticConventions.AttributeNetPeerName, request.RequestUri.Host);
- if (!request.RequestUri.IsDefaultPort)
- {
- activity.SetTag(SemanticConventions.AttributeNetPeerPort, request.RequestUri.Port);
- }
-
- activity.SetTag(SemanticConventions.AttributeHttpUrl, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri));
- activity.SetTag(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.Version));
+ activity.SetTag(SemanticConventions.AttributeHttpRequestMethod, httpMethod);
}
-
- // see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-spans.md
- if (this.emitNewAttributes)
+ else
{
- if (RequestMethodHelper.KnownMethods.TryGetValue(request.Method.Method, out var httpMethod))
- {
- activity.SetTag(SemanticConventions.AttributeHttpRequestMethod, httpMethod);
- }
- else
- {
- // Set to default "_OTHER" as per spec.
- // https://github.com/open-telemetry/semantic-conventions/blob/v1.22.0/docs/http/http-spans.md#common-attributes
- activity.SetTag(SemanticConventions.AttributeHttpRequestMethod, "_OTHER");
- activity.SetTag(SemanticConventions.AttributeHttpRequestMethodOriginal, request.Method.Method);
- }
+ // Set to default "_OTHER" as per spec.
+ // https://github.com/open-telemetry/semantic-conventions/blob/v1.22.0/docs/http/http-spans.md#common-attributes
+ activity.SetTag(SemanticConventions.AttributeHttpRequestMethod, "_OTHER");
+ activity.SetTag(SemanticConventions.AttributeHttpRequestMethodOriginal, request.Method.Method);
+ }
- activity.SetTag(SemanticConventions.AttributeServerAddress, request.RequestUri.Host);
- if (!request.RequestUri.IsDefaultPort)
- {
- activity.SetTag(SemanticConventions.AttributeServerPort, request.RequestUri.Port);
- }
+ activity.SetTag(SemanticConventions.AttributeServerAddress, request.RequestUri.Host);
+ if (!request.RequestUri.IsDefaultPort)
+ {
+ activity.SetTag(SemanticConventions.AttributeServerPort, request.RequestUri.Port);
+ }
- activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri));
- activity.SetTag(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetProtocolVersionString(request.Version));
+ activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri));
- if (request.Headers.TryGetValues("User-Agent", out var userAgentValues))
+ if (request.Headers.TryGetValues("User-Agent", out var userAgentValues))
+ {
+ var userAgent = userAgentValues.FirstOrDefault();
+ if (!string.IsNullOrEmpty(userAgent))
{
- var userAgent = userAgentValues.FirstOrDefault();
- if (!string.IsNullOrEmpty(userAgent))
- {
- activity.SetTag(SemanticConventions.AttributeHttpUserAgent, userAgent);
- }
+ activity.SetTag(SemanticConventions.AttributeHttpUserAgent, userAgent);
}
}
@@ -276,18 +250,11 @@ public void OnStopActivity(Activity activity, object payload)
activity.SetStatus(SpanHelper.ResolveSpanStatusForHttpStatusCode(activity.Kind, (int)response.StatusCode));
}
- if (this.emitOldAttributes)
- {
- activity.SetTag(SemanticConventions.AttributeHttpStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode));
- }
-
- if (this.emitNewAttributes)
+ activity.SetTag(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetProtocolVersionString(response.Version));
+ activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode));
+ if (activity.Status == ActivityStatusCode.Error)
{
- activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode));
- if (activity.Status == ActivityStatusCode.Error)
- {
- activity.SetTag(SemanticConventions.AttributeErrorType, TelemetryHelper.GetStatusCodeString(response.StatusCode));
- }
+ activity.SetTag(SemanticConventions.AttributeErrorType, TelemetryHelper.GetStatusCodeString(response.StatusCode));
}
try
@@ -341,10 +308,7 @@ public void OnException(Activity activity, object payload)
return;
}
- if (this.emitNewAttributes)
- {
- activity.SetTag(SemanticConventions.AttributeErrorType, GetErrorType(exc));
- }
+ activity.SetTag(SemanticConventions.AttributeErrorType, GetErrorType(exc));
if (this.options.RecordException)
{
diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs
index 9b20cbb533e..b3d45af15e8 100644
--- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs
+++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs
@@ -25,7 +25,6 @@
using System.Reflection;
using OpenTelemetry.Internal;
using OpenTelemetry.Trace;
-using static OpenTelemetry.Internal.HttpSemanticConventionHelper;
namespace OpenTelemetry.Instrumentation.Http.Implementation;
@@ -38,7 +37,6 @@ internal sealed class HttpHandlerMetricsDiagnosticListener : ListenerHandler
internal static readonly string MeterVersion = AssemblyName.Version.ToString();
internal static readonly Meter Meter = new(MeterName, MeterVersion);
private const string OnUnhandledExceptionEvent = "System.Net.Http.Exception";
- private static readonly Histogram HttpClientDuration = Meter.CreateHistogram("http.client.duration", "ms", "Measures the duration of outbound HTTP requests.");
private static readonly Histogram HttpClientRequestDuration = Meter.CreateHistogram("http.client.request.duration", "s", "Duration of HTTP client requests.");
private static readonly PropertyFetcher StopRequestFetcher = new("Request");
@@ -49,27 +47,16 @@ internal sealed class HttpHandlerMetricsDiagnosticListener : ListenerHandler
private static readonly HttpRequestOptionsKey HttpRequestOptionsErrorKey = new HttpRequestOptionsKey(SemanticConventions.AttributeErrorType);
#endif
- private readonly HttpClientMetricInstrumentationOptions options;
- private readonly bool emitOldAttributes;
- private readonly bool emitNewAttributes;
-
- public HttpHandlerMetricsDiagnosticListener(string name, HttpClientMetricInstrumentationOptions options)
+ public HttpHandlerMetricsDiagnosticListener(string name)
: base(name)
{
- this.options = options;
-
- this.emitOldAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old);
- this.emitNewAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New);
}
public override void OnEventWritten(string name, object payload)
{
if (name == OnUnhandledExceptionEvent)
{
- if (this.emitNewAttributes)
- {
- this.OnExceptionEventWritten(Activity.Current, payload);
- }
+ this.OnExceptionEventWritten(Activity.Current, payload);
}
else if (name == OnStopEvent)
{
@@ -86,90 +73,61 @@ public void OnStopEventWritten(Activity activity, object payload)
if (TryFetchRequest(payload, out HttpRequestMessage request))
{
- // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/semantic_conventions/http.md
- if (this.emitOldAttributes)
- {
- TagList tags = default;
-
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpMethod, HttpTagHelper.GetNameForHttpMethod(request.Method)));
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpScheme, request.RequestUri.Scheme));
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.Version)));
- tags.Add(new KeyValuePair(SemanticConventions.AttributeNetPeerName, request.RequestUri.Host));
+ // see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-metrics.md
+ TagList tags = default;
- if (!request.RequestUri.IsDefaultPort)
- {
- tags.Add(new KeyValuePair(SemanticConventions.AttributeNetPeerPort, request.RequestUri.Port));
- }
-
- if (TryFetchResponse(payload, out HttpResponseMessage response))
- {
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode)));
- }
-
- // We are relying here on HttpClient library to set duration before writing the stop event.
- // https://github.com/dotnet/runtime/blob/90603686d314147017c8bbe1fa8965776ce607d0/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L178
- // TODO: Follow up with .NET team if we can continue to rely on this behavior.
- HttpClientDuration.Record(activity.Duration.TotalMilliseconds, tags);
+ if (RequestMethodHelper.KnownMethods.TryGetValue(request.Method.Method, out var httpMethod))
+ {
+ tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, httpMethod));
}
-
- // see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-spans.md
- if (this.emitNewAttributes)
+ else
{
- TagList tags = default;
+ // Set to default "_OTHER" as per spec.
+ // https://github.com/open-telemetry/semantic-conventions/blob/v1.22.0/docs/http/http-spans.md#common-attributes
+ tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, "_OTHER"));
+ }
- if (RequestMethodHelper.KnownMethods.TryGetValue(request.Method.Method, out var httpMethod))
- {
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, httpMethod));
- }
- else
- {
- // Set to default "_OTHER" as per spec.
- // https://github.com/open-telemetry/semantic-conventions/blob/v1.22.0/docs/http/http-spans.md#common-attributes
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, "_OTHER"));
- }
+ tags.Add(new KeyValuePair(SemanticConventions.AttributeServerAddress, request.RequestUri.Host));
+ tags.Add(new KeyValuePair(SemanticConventions.AttributeUrlScheme, request.RequestUri.Scheme));
- tags.Add(new KeyValuePair(SemanticConventions.AttributeServerAddress, request.RequestUri.Host));
- tags.Add(new KeyValuePair(SemanticConventions.AttributeUrlScheme, request.RequestUri.Scheme));
- tags.Add(new KeyValuePair(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetProtocolVersionString(request.Version)));
+ if (!request.RequestUri.IsDefaultPort)
+ {
+ tags.Add(new KeyValuePair(SemanticConventions.AttributeServerPort, request.RequestUri.Port));
+ }
- if (!request.RequestUri.IsDefaultPort)
- {
- tags.Add(new KeyValuePair(SemanticConventions.AttributeServerPort, request.RequestUri.Port));
- }
+ if (TryFetchResponse(payload, out HttpResponseMessage response))
+ {
+ tags.Add(new KeyValuePair(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetProtocolVersionString(response.Version)));
+ tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode)));
- if (TryFetchResponse(payload, out HttpResponseMessage response))
+ // Set error.type to status code for failed requests
+ // https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#common-attributes
+ if (SpanHelper.ResolveSpanStatusForHttpStatusCode(ActivityKind.Client, (int)response.StatusCode) == ActivityStatusCode.Error)
{
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode)));
-
- // Set error.type to status code for failed requests
- // https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#common-attributes
- if (SpanHelper.ResolveSpanStatusForHttpStatusCode(ActivityKind.Client, (int)response.StatusCode) == ActivityStatusCode.Error)
- {
- tags.Add(new KeyValuePair(SemanticConventions.AttributeErrorType, TelemetryHelper.GetStatusCodeString(response.StatusCode)));
- }
+ tags.Add(new KeyValuePair(SemanticConventions.AttributeErrorType, TelemetryHelper.GetStatusCodeString(response.StatusCode)));
}
+ }
- if (response == null)
- {
+ if (response == null)
+ {
#if !NET6_0_OR_GREATER
- request.Properties.TryGetValue(SemanticConventions.AttributeErrorType, out var errorType);
+ request.Properties.TryGetValue(SemanticConventions.AttributeErrorType, out var errorType);
#else
- request.Options.TryGetValue(HttpRequestOptionsErrorKey, out var errorType);
+ request.Options.TryGetValue(HttpRequestOptionsErrorKey, out var errorType);
#endif
- // Set error.type to exception type if response was not received.
- // https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#common-attributes
- if (errorType != null)
- {
- tags.Add(new KeyValuePair(SemanticConventions.AttributeErrorType, errorType));
- }
+ // Set error.type to exception type if response was not received.
+ // https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#common-attributes
+ if (errorType != null)
+ {
+ tags.Add(new KeyValuePair(SemanticConventions.AttributeErrorType, errorType));
}
-
- // We are relying here on HttpClient library to set duration before writing the stop event.
- // https://github.com/dotnet/runtime/blob/90603686d314147017c8bbe1fa8965776ce607d0/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L178
- // TODO: Follow up with .NET team if we can continue to rely on this behavior.
- HttpClientRequestDuration.Record(activity.Duration.TotalSeconds, tags);
}
+
+ // We are relying here on HttpClient library to set duration before writing the stop event.
+ // https://github.com/dotnet/runtime/blob/90603686d314147017c8bbe1fa8965776ce607d0/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L178
+ // TODO: Follow up with .NET team if we can continue to rely on this behavior.
+ HttpClientRequestDuration.Record(activity.Duration.TotalSeconds, tags);
}
// The AOT-annotation DynamicallyAccessedMembers in System.Net.Http library ensures that top-level properties on the payload object are always preserved.
diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpTagHelper.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpTagHelper.cs
index 892f6fa6c5a..08c50229db8 100644
--- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpTagHelper.cs
+++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpTagHelper.cs
@@ -28,12 +28,10 @@ internal static class HttpTagHelper
private static readonly ConcurrentDictionary MethodOperationNameCache = new();
private static readonly ConcurrentDictionary HttpMethodOperationNameCache = new();
private static readonly ConcurrentDictionary HttpMethodNameCache = new();
- private static readonly ConcurrentDictionary ProtocolVersionToStringCache = new();
private static readonly Func ConvertMethodToOperationNameRef = ConvertMethodToOperationName;
private static readonly Func ConvertHttpMethodToOperationNameRef = ConvertHttpMethodToOperationName;
private static readonly Func ConvertHttpMethodToNameRef = ConvertHttpMethodToName;
- private static readonly Func ConvertProtocolVersionToStringRef = ConvertProtocolVersionToString;
///
/// Gets the OpenTelemetry standard name for an activity based on its Http method.
@@ -56,13 +54,6 @@ internal static class HttpTagHelper
/// Span method name.
public static string GetNameForHttpMethod(HttpMethod method) => HttpMethodNameCache.GetOrAdd(method, ConvertHttpMethodToNameRef);
- ///
- /// Gets the OpenTelemetry standard version tag value for a span based on its protocol .
- ///
- /// .
- /// Span flavor value.
- public static string GetFlavorTagValueFromProtocolVersion(Version protocolVersion) => ProtocolVersionToStringCache.GetOrAdd(protocolVersion, ConvertProtocolVersionToStringRef);
-
///
/// Gets the OpenTelemetry standard uri tag value for a span based on its request .
///
@@ -92,6 +83,4 @@ public static string GetUriTagValueFromRequestUri(Uri uri)
private static string ConvertHttpMethodToOperationName(HttpMethod method) => $"HTTP {method}";
private static string ConvertHttpMethodToName(HttpMethod method) => method.ToString();
-
- private static string ConvertProtocolVersionToString(Version protocolVersion) => protocolVersion.ToString();
}
diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs
index 3852201fde7..800eb5cb106 100644
--- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs
+++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs
@@ -25,7 +25,6 @@
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Internal;
using OpenTelemetry.Trace;
-using static OpenTelemetry.Internal.HttpSemanticConventionHelper;
namespace OpenTelemetry.Instrumentation.Http.Implementation;
@@ -49,16 +48,9 @@ internal static class HttpWebRequestActivitySource
private static readonly string Version = AssemblyName.Version.ToString();
private static readonly ActivitySource WebRequestActivitySource = new(ActivitySourceName, Version);
private static readonly Meter WebRequestMeter = new(MeterName, Version);
- private static readonly Histogram HttpClientDuration = WebRequestMeter.CreateHistogram("http.client.duration", "ms", "Measures the duration of outbound HTTP requests.");
private static readonly Histogram HttpClientRequestDuration = WebRequestMeter.CreateHistogram("http.client.request.duration", "s", "Measures the duration of outbound HTTP requests.");
private static HttpClientInstrumentationOptions tracingOptions;
- private static HttpClientMetricInstrumentationOptions metricsOptions;
-
- private static bool tracingEmitOldAttributes;
- private static bool tracingEmitNewAttributes;
- private static bool metricsEmitOldAttributes;
- private static bool metricsEmitNewAttributes;
// Fields for reflection
private static FieldInfo connectionGroupListField;
@@ -96,7 +88,6 @@ static HttpWebRequestActivitySource()
PerformInjection();
TracingOptions = new HttpClientInstrumentationOptions();
- MetricsOptions = new HttpClientMetricInstrumentationOptions();
}
catch (Exception ex)
{
@@ -111,21 +102,6 @@ internal static HttpClientInstrumentationOptions TracingOptions
set
{
tracingOptions = value;
-
- tracingEmitOldAttributes = value.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old);
- tracingEmitNewAttributes = value.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New);
- }
- }
-
- internal static HttpClientMetricInstrumentationOptions MetricsOptions
- {
- get => metricsOptions;
- set
- {
- metricsOptions = value;
-
- metricsEmitOldAttributes = value.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old);
- metricsEmitNewAttributes = value.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New);
}
}
@@ -136,46 +112,27 @@ private static void AddRequestTagsAndInstrumentRequest(HttpWebRequest request, A
if (activity.IsAllDataRequested)
{
- // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/semantic_conventions/http.md
- if (tracingEmitOldAttributes)
+ // see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md
+ if (RequestMethodHelper.KnownMethods.TryGetValue(request.Method, out var httpMethod))
{
- activity.SetTag(SemanticConventions.AttributeHttpMethod, request.Method);
- activity.SetTag(SemanticConventions.AttributeNetPeerName, request.RequestUri.Host);
- if (!request.RequestUri.IsDefaultPort)
- {
- activity.SetTag(SemanticConventions.AttributeNetPeerPort, request.RequestUri.Port);
- }
-
- activity.SetTag(SemanticConventions.AttributeHttpScheme, request.RequestUri.Scheme);
- activity.SetTag(SemanticConventions.AttributeHttpUrl, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri));
- activity.SetTag(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.ProtocolVersion));
+ activity.SetTag(SemanticConventions.AttributeHttpRequestMethod, httpMethod);
}
-
- // see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-spans.md
- if (tracingEmitNewAttributes)
+ else
{
- if (RequestMethodHelper.KnownMethods.TryGetValue(request.Method, out var httpMethod))
- {
- activity.SetTag(SemanticConventions.AttributeHttpRequestMethod, httpMethod);
- }
- else
- {
- // Set to default "_OTHER" as per spec.
- // https://github.com/open-telemetry/semantic-conventions/blob/v1.22.0/docs/http/http-spans.md#common-attributes
- activity.SetTag(SemanticConventions.AttributeHttpRequestMethod, "_OTHER");
- activity.SetTag(SemanticConventions.AttributeHttpRequestMethodOriginal, request.Method);
- }
-
- activity.SetTag(SemanticConventions.AttributeServerAddress, request.RequestUri.Host);
- if (!request.RequestUri.IsDefaultPort)
- {
- activity.SetTag(SemanticConventions.AttributeServerPort, request.RequestUri.Port);
- }
+ // Set to default "_OTHER" as per spec.
+ // https://github.com/open-telemetry/semantic-conventions/blob/v1.22.0/docs/http/http-spans.md#common-attributes
+ activity.SetTag(SemanticConventions.AttributeHttpRequestMethod, "_OTHER");
+ activity.SetTag(SemanticConventions.AttributeHttpRequestMethodOriginal, request.Method);
+ }
- activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri));
- activity.SetTag(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetProtocolVersionString(request.ProtocolVersion));
+ activity.SetTag(SemanticConventions.AttributeServerAddress, request.RequestUri.Host);
+ if (!request.RequestUri.IsDefaultPort)
+ {
+ activity.SetTag(SemanticConventions.AttributeServerPort, request.RequestUri.Port);
}
+ activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri));
+
try
{
TracingOptions.EnrichWithHttpWebRequest?.Invoke(activity, request);
@@ -194,17 +151,8 @@ private static void AddResponseTags(HttpWebResponse response, Activity activity)
if (activity.IsAllDataRequested)
{
- if (tracingEmitOldAttributes)
- {
- activity.SetTag(SemanticConventions.AttributeHttpStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode));
- }
-
- if (tracingEmitNewAttributes)
- {
- activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode));
- }
-
- activity.SetStatus(SpanHelper.ResolveSpanStatusForHttpStatusCode(activity.Kind, (int)response.StatusCode));
+ activity.SetTag(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetProtocolVersionString(response.ProtocolVersion));
+ activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode));
try
{
@@ -243,42 +191,15 @@ private static string GetErrorType(Exception exception)
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void AddExceptionTags(Exception exception, Activity activity, out HttpStatusCode? statusCode)
+ private static void AddExceptionEvent(Exception exception, Activity activity)
{
Debug.Assert(activity != null, "Activity must not be null");
- statusCode = null;
-
if (!activity.IsAllDataRequested)
{
return;
}
- ActivityStatusCode status;
-
- if (exception is WebException wexc && wexc.Response is HttpWebResponse response)
- {
- statusCode = response.StatusCode;
-
- if (tracingEmitOldAttributes)
- {
- activity.SetTag(SemanticConventions.AttributeHttpStatusCode, (int)statusCode);
- }
-
- if (tracingEmitNewAttributes)
- {
- activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, (int)statusCode);
- }
-
- status = SpanHelper.ResolveSpanStatusForHttpStatusCode(activity.Kind, (int)statusCode);
- }
- else
- {
- status = ActivityStatusCode.Error;
- }
-
- activity.SetStatus(status);
-
if (TracingOptions.RecordException)
{
activity.RecordException(exception);
@@ -309,7 +230,7 @@ private static void ProcessRequest(HttpWebRequest request)
var enableTracing = WebRequestActivitySource.HasListeners()
&& TracingOptions.EventFilterHttpWebRequest(request);
- if (!enableTracing && !HttpClientDuration.Enabled && !HttpClientRequestDuration.Enabled)
+ if (!enableTracing && !HttpClientRequestDuration.Enabled)
{
// Tracing and metrics are not enabled, so we can skip generating signals
// Propagation must still be done in such cases, to allow
@@ -397,6 +318,8 @@ private static void ProcessResult(IAsyncResult asyncResult, AsyncCallback asyncC
{
HttpStatusCode? httpStatusCode = null;
string errorType = null;
+ Version protocolVersion = null;
+ ActivityStatusCode activityStatus = ActivityStatusCode.Unset;
// Activity may be null if we are not tracing in these cases:
// 1. No listeners
@@ -413,13 +336,30 @@ private static void ProcessResult(IAsyncResult asyncResult, AsyncCallback asyncC
if (result is Exception ex)
{
errorType = GetErrorType(ex);
- if (activity != null)
+ if (ex is WebException wexc && wexc.Response is HttpWebResponse response)
{
- AddExceptionTags(ex, activity, out httpStatusCode);
+ httpStatusCode = response.StatusCode;
+ protocolVersion = response.ProtocolVersion;
+ activityStatus = SpanHelper.ResolveSpanStatusForHttpStatusCode(ActivityKind.Client, (int)response.StatusCode);
+ if (activityStatus == ActivityStatusCode.Error)
+ {
+ // override the errorType to statusCode for failures.
+ errorType = TelemetryHelper.GetStatusCodeString(response.StatusCode);
+ }
+
+ if (activity != null)
+ {
+ AddResponseTags(response, activity);
+ AddExceptionEvent(ex, activity);
+ }
}
- else if (ex is WebException wexc && wexc.Response is HttpWebResponse response)
+ else
{
- httpStatusCode = response.StatusCode;
+ activityStatus = ActivityStatusCode.Error;
+ if (activity != null)
+ {
+ AddExceptionEvent(ex, activity);
+ }
}
}
else
@@ -447,6 +387,7 @@ private static void ProcessResult(IAsyncResult asyncResult, AsyncCallback asyncC
}
httpStatusCode = responseCopy.StatusCode;
+ protocolVersion = responseCopy.ProtocolVersion;
}
else
{
@@ -456,11 +397,14 @@ private static void ProcessResult(IAsyncResult asyncResult, AsyncCallback asyncC
}
httpStatusCode = response.StatusCode;
+ protocolVersion = response.ProtocolVersion;
}
- if (SpanHelper.ResolveSpanStatusForHttpStatusCode(ActivityKind.Client, (int)httpStatusCode.Value) == ActivityStatusCode.Error)
+ activityStatus = SpanHelper.ResolveSpanStatusForHttpStatusCode(ActivityKind.Client, (int)httpStatusCode.Value);
+
+ if (activityStatus == ActivityStatusCode.Error)
{
- // override the errorType to statusCode for failures.
+ // set the errorType to statusCode for failures.
errorType = TelemetryHelper.GetStatusCodeString(httpStatusCode.Value);
}
}
@@ -470,85 +414,66 @@ private static void ProcessResult(IAsyncResult asyncResult, AsyncCallback asyncC
HttpInstrumentationEventSource.Log.FailedProcessResult(ex);
}
- if (tracingEmitNewAttributes && errorType != null)
+ if (activity != null && activity.IsAllDataRequested)
{
- activity?.SetTag(SemanticConventions.AttributeErrorType, errorType);
+ activity.SetStatus(activityStatus);
+ if (errorType != null)
+ {
+ activity.SetTag(SemanticConventions.AttributeErrorType, errorType);
+ }
}
activity?.Stop();
- if (HttpClientDuration.Enabled || HttpClientRequestDuration.Enabled)
+ if (HttpClientRequestDuration.Enabled)
{
double durationS;
- double durationMs;
if (activity != null)
{
durationS = activity.Duration.TotalSeconds;
- durationMs = activity.Duration.TotalMilliseconds;
}
else
{
var endTimestamp = Stopwatch.GetTimestamp();
durationS = (endTimestamp - startTimestamp) / (double)Stopwatch.Frequency;
- durationMs = durationS * 1000;
}
- if (metricsEmitOldAttributes)
- {
- TagList tags = default;
-
- tags.Add(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.ProtocolVersion));
- tags.Add(SemanticConventions.AttributeHttpMethod, request.Method);
- tags.Add(SemanticConventions.AttributeHttpScheme, request.RequestUri.Scheme);
- tags.Add(SemanticConventions.AttributeNetPeerName, request.RequestUri.Host);
- if (!request.RequestUri.IsDefaultPort)
- {
- tags.Add(SemanticConventions.AttributeNetPeerPort, request.RequestUri.Port);
- }
-
- if (httpStatusCode.HasValue)
- {
- tags.Add(SemanticConventions.AttributeHttpStatusCode, (int)httpStatusCode.Value);
- }
+ TagList tags = default;
- HttpClientDuration.Record(durationMs, tags);
+ if (RequestMethodHelper.KnownMethods.TryGetValue(request.Method, out var httpMethod))
+ {
+ tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, httpMethod));
}
-
- if (metricsEmitNewAttributes)
+ else
{
- TagList tags = default;
-
- if (RequestMethodHelper.KnownMethods.TryGetValue(request.Method, out var httpMethod))
- {
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, httpMethod));
- }
- else
- {
- // Set to default "_OTHER" as per spec.
- // https://github.com/open-telemetry/semantic-conventions/blob/v1.22.0/docs/http/http-spans.md#common-attributes
- tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, "_OTHER"));
- }
+ // Set to default "_OTHER" as per spec.
+ // https://github.com/open-telemetry/semantic-conventions/blob/v1.22.0/docs/http/http-spans.md#common-attributes
+ tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, "_OTHER"));
+ }
- tags.Add(SemanticConventions.AttributeServerAddress, request.RequestUri.Host);
- tags.Add(SemanticConventions.AttributeUrlScheme, request.RequestUri.Scheme);
- tags.Add(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetProtocolVersionString(request.ProtocolVersion));
- if (!request.RequestUri.IsDefaultPort)
- {
- tags.Add(SemanticConventions.AttributeServerPort, request.RequestUri.Port);
- }
+ tags.Add(SemanticConventions.AttributeServerAddress, request.RequestUri.Host);
+ tags.Add(SemanticConventions.AttributeUrlScheme, request.RequestUri.Scheme);
+ if (protocolVersion != null)
+ {
+ tags.Add(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetProtocolVersionString(protocolVersion));
+ }
- if (httpStatusCode.HasValue)
- {
- tags.Add(SemanticConventions.AttributeHttpResponseStatusCode, (int)httpStatusCode.Value);
- }
+ if (!request.RequestUri.IsDefaultPort)
+ {
+ tags.Add(SemanticConventions.AttributeServerPort, request.RequestUri.Port);
+ }
- if (errorType != null)
- {
- tags.Add(SemanticConventions.AttributeErrorType, errorType);
- }
+ if (httpStatusCode.HasValue)
+ {
+ tags.Add(SemanticConventions.AttributeHttpResponseStatusCode, (int)httpStatusCode.Value);
+ }
- HttpClientRequestDuration.Record(durationS, tags);
+ if (errorType != null)
+ {
+ tags.Add(SemanticConventions.AttributeErrorType, errorType);
}
+
+ HttpClientRequestDuration.Record(durationS, tags);
}
}
diff --git a/src/OpenTelemetry.Instrumentation.Http/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.Http/MeterProviderBuilderExtensions.cs
index 73b7ca44c0a..3d6acde2c24 100644
--- a/src/OpenTelemetry.Instrumentation.Http/MeterProviderBuilderExtensions.cs
+++ b/src/OpenTelemetry.Instrumentation.Http/MeterProviderBuilderExtensions.cs
@@ -15,9 +15,9 @@
//
#if !NET8_0_OR_GREATER
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Options;
+#if !NETFRAMEWORK
using OpenTelemetry.Instrumentation.Http;
+#endif
using OpenTelemetry.Instrumentation.Http.Implementation;
#endif
@@ -49,28 +49,12 @@ public static MeterProviderBuilder AddHttpClientInstrumentation(
_ = TelemetryHelper.BoxedStatusCodes;
_ = RequestMethodHelper.KnownMethods;
- builder.ConfigureServices(services =>
- {
- services.RegisterOptionsFactory(configuration => new HttpClientMetricInstrumentationOptions(configuration));
- });
-
#if NETFRAMEWORK
builder.AddMeter(HttpWebRequestActivitySource.MeterName);
-
- if (builder is IDeferredMeterProviderBuilder deferredMeterProviderBuilder)
- {
- deferredMeterProviderBuilder.Configure((sp, builder) =>
- {
- var options = sp.GetRequiredService>().Get(Options.DefaultName);
-
- HttpWebRequestActivitySource.MetricsOptions = options;
- });
- }
#else
builder.AddMeter(HttpHandlerMetricsDiagnosticListener.MeterName);
- builder.AddInstrumentation(sp => new HttpClientMetrics(
- sp.GetRequiredService>().Get(Options.DefaultName)));
+ builder.AddInstrumentation(new HttpClientMetrics());
#endif
return builder;
#endif
diff --git a/src/OpenTelemetry.Instrumentation.Http/README.md b/src/OpenTelemetry.Instrumentation.Http/README.md
index 1331d7ad68b..8e502ad7968 100644
--- a/src/OpenTelemetry.Instrumentation.Http/README.md
+++ b/src/OpenTelemetry.Instrumentation.Http/README.md
@@ -99,31 +99,51 @@ to see how to enable this instrumentation in an ASP.NET application.
#### List of metrics produced
-A different metric is emitted depending on whether a user opts-in to the new
-Http Semantic Conventions using `OTEL_SEMCONV_STABILITY_OPT_IN`.
-
-* By default, the instrumentation emits the following metric.
-
- | Name | Instrument Type | Unit | Description |
- |-------|-----------------|------|-------------|
- | `http.client.duration` | Histogram | `ms` | Measures the duration of outbound HTTP requests. |
-
-* If user sets the environment variable to `http`, the instrumentation emits
- the following metric.
-
- | Name | Instrument Type | Unit | Description |
- |-------|-----------------|------|-------------|
- | `http.client.request.duration` | Histogram | `s` | Measures the duration of outbound HTTP requests. |
-
- This metric is emitted in `seconds` as per the semantic convention. While
- the convention [recommends using custom histogram buckets](https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-metrics.md)
- , this feature is not yet available via .NET Metrics API.
- A [workaround](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4820)
+When the application targets `NETFRAMEWORK`, `.NET6.0` or `.NET7.0`, the
+instrumentation emits the following metric:
+
+| Name | Details |
+|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `http.client.request.duration` | [Specification](https://github.com/open-telemetry/semantic-conventions/blob/release/v1.23.x/docs/http/http-metrics.md#metric-httpclientrequestduration) |
+
+Starting from `.NET8.0`, metrics instrumentation is natively implemented, and
+the HttpClient library has incorporated support for [built-in
+metrics](https://learn.microsoft.com/dotnet/core/diagnostics/built-in-metrics-system-net)
+following the OpenTelemetry semantic conventions. The library includes additional
+metrics beyond those defined in the
+[specification](https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-metrics.md),
+covering additional scenarios for HttpClient users. When the application targets
+`.NET8.0` and newer versions, the instrumentation library automatically enables
+all `built-in` metrics by default.
+
+Note that the `AddHttpClientInstrumentation()` extension simplifies the process
+of enabling all built-in metrics via a single line of code. Alternatively, for
+more granular control over emitted metrics, you can utilize the `AddMeter()`
+extension on `MeterProviderBuilder` for meters listed in
+[built-in-metrics-system-net](https://learn.microsoft.com/dotnet/core/diagnostics/built-in-metrics-system-net).
+Using `AddMeter()` for metrics activation eliminates the need to take dependency
+on the instrumentation library package and calling
+`AddHttpClientInstrumentation()`.
+
+If you utilize `AddHttpClientInstrumentation()` and wish to exclude unnecessary
+metrics, you can utilize
+[Views](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/docs/metrics/customizing-the-sdk#drop-an-instrument)
+to achieve this.
+
+**Note:** There is no difference in features or emitted metrics when enabling
+metrics using `AddMeter()` or `AddHttpClientInstrumentation()` on `.NET8.0` and
+newer versions.
+
+> **Note**
+> The `http.client.request.duration` metric is emitted in `seconds` as
+ per the semantic convention. While the convention [recommends using custom
+ histogram
+ buckets](https://github.com/open-telemetry/semantic-conventions/blob/release/v1.23.x/docs/http/http-metrics.md)
+ , this feature is not yet available via .NET Metrics API. A
+ [workaround](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4820)
has been included in OTel SDK starting version `1.6.0` which applies
- recommended buckets by default for `http.client.request.duration`.
-
-* If user sets the environment variable to `http/dup`, the instrumentation
- emits both `http.client.duration` and `http.client.request.duration`.
+ recommended buckets by default for `http.client.request.duration`. This
+ applies to all targeted frameworks.
## Advanced configuration
diff --git a/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md
index fe47ff3defb..75ca992ec03 100644
--- a/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md
+++ b/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md
@@ -2,9 +2,13 @@
## Unreleased
+## 1.6.0-beta.3
+
+Released 2023-Nov-17
+
* Updated `Microsoft.Extensions.Configuration` and
- `Microsoft.Extensions.Options` package version to `8.0.0-rc.2.23479.6`.
- ([#5015](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5015))
+ `Microsoft.Extensions.Options` package version to `8.0.0`.
+ ([#5051](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5051))
## 1.6.0-beta.2
diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md
index d95648b95ef..2ebf6ad5f07 100644
--- a/src/OpenTelemetry/CHANGELOG.md
+++ b/src/OpenTelemetry/CHANGELOG.md
@@ -2,10 +2,6 @@
## Unreleased
-* Updated `Microsoft.Extensions.Logging` package version to
- `8.0.0-rc.2.23479.6`.
- ([#4959](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4959))
-
* The `AddService` `ResourceBuilder` extension method will now generate the same
`service.instance.id` for the lifetime of a process when
`autoGenerateServiceInstanceId` is `true`.
@@ -34,8 +30,28 @@
([#5021](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5021))
* Updated `Microsoft.Extensions.Logging.Configuration` package version to
- `8.0.0-rc.2.23479.6`.
- ([#5020](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5020))
+ `8.0.0`.
+ ([#5051](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5051))
+
+* Updated `Microsoft.Extensions.Logging` package version to
+ `8.0.0`.
+ ([#5051](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5051))
+
+* Revert the default behavior of Metrics SDK for Delta aggregation. It would not
+ reclaim unused Metric Points by default. You can enable the SDK to reclaim
+ unused Metric Points by setting the environment variable
+ `OTEL_DOTNET_EXPERIMENTAL_METRICS_RECLAIM_UNUSED_METRIC_POINTS` to `true`
+ before setting up the `MeterProvider`.
+ ([#5052](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5052))
+
+* Update Metrics SDK to override the default histogram buckets for ASP.NET
+ (.NET Framework).
+
+ Histogram metrics for the meter name `OpenTelemetry.Instrumentation.AspNet`
+ and instrument name `http.request.server.duration` which have their `Unit`
+ as `s` (second) will have their default histogram buckets as `[ 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 ]`.
+ ([#5063](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5063))
## 1.7.0-alpha.1
diff --git a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs
index 1cbba26f9f8..67a668cf3d5 100644
--- a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs
+++ b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs
@@ -347,6 +347,18 @@ public void LoggerProcessStateSkipped(string type, string reason)
this.WriteEvent(51, type, reason);
}
+ [Event(52, Message = "Instrument '{0}', Meter '{1}' has been deactivated.", Level = EventLevel.Informational)]
+ public void MetricInstrumentDeactivated(string instrumentName, string meterName)
+ {
+ this.WriteEvent(52, instrumentName, meterName);
+ }
+
+ [Event(53, Message = "Instrument '{0}', Meter '{1}' has been removed.", Level = EventLevel.Informational)]
+ public void MetricInstrumentRemoved(string instrumentName, string meterName)
+ {
+ this.WriteEvent(53, instrumentName, meterName);
+ }
+
#if DEBUG
public class OpenTelemetryEventListener : EventListener
{
diff --git a/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs b/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs
index d6cd664b686..ea7d8dc91c8 100644
--- a/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs
+++ b/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs
@@ -318,7 +318,12 @@ protected override void OnEventSourceCreated(EventSource eventSource)
/// Data of the EventSource event.
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
- this.WriteEvent(eventData.Message, eventData.Payload);
+ // Note: The EventSource check here works around a bug in EventListener.
+ // See: https://github.com/open-telemetry/opentelemetry-dotnet/pull/5046
+ if (eventData.EventSource.Name.StartsWith(EventSourceNamePrefix, StringComparison.OrdinalIgnoreCase))
+ {
+ this.WriteEvent(eventData.Message, eventData.Payload);
+ }
}
private void Dispose(bool disposing)
diff --git a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs
index 5776241441d..e4cf260aa16 100644
--- a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs
+++ b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs
@@ -50,15 +50,43 @@ public static class OpenTelemetryLoggingExtensions
/// The supplied for call chaining.
public static ILoggingBuilder AddOpenTelemetry(
this ILoggingBuilder builder)
+ => AddOpenTelemetryInternal(builder, configureBuilder: null, configureOptions: null);
+
+ ///
+ /// Adds an OpenTelemetry logger named 'OpenTelemetry' to the .
+ ///
+ ///
+ /// The to use.
+ /// Optional configuration action.
+ /// The supplied for call chaining.
+ public static ILoggingBuilder AddOpenTelemetry(
+ this ILoggingBuilder builder,
+ Action? configure)
+ => AddOpenTelemetryInternal(builder, configureBuilder: null, configureOptions: configure);
+
+ private static ILoggingBuilder AddOpenTelemetryInternal(
+ ILoggingBuilder builder,
+ Action? configureBuilder,
+ Action? configureOptions)
{
Guard.ThrowIfNull(builder);
builder.AddConfiguration();
+ var services = builder.Services;
+
+ if (configureOptions != null)
+ {
+ // TODO: Move this below the RegisterLoggerProviderOptions call so
+ // that user-supplied delegate fires AFTER the options are bound to
+ // Logging:OpenTelemetry configuration.
+ services.Configure(configureOptions);
+ }
+
// Note: This will bind logger options element (eg "Logging:OpenTelemetry") to OpenTelemetryLoggerOptions
- RegisterLoggerProviderOptions(builder.Services);
+ RegisterLoggerProviderOptions(services);
- new LoggerProviderBuilderBase(builder.Services).ConfigureBuilder(
+ var loggingBuilder = new LoggerProviderBuilderBase(services).ConfigureBuilder(
(sp, logging) =>
{
var options = sp.GetRequiredService>().CurrentValue;
@@ -78,7 +106,9 @@ public static ILoggingBuilder AddOpenTelemetry(
options.Processors.Clear();
});
- builder.Services.TryAddEnumerable(
+ configureBuilder?.Invoke(loggingBuilder);
+
+ services.TryAddEnumerable(
ServiceDescriptor.Singleton(
sp => new OpenTelemetryLoggerProvider(
sp.GetRequiredService(),
@@ -107,23 +137,4 @@ static void RegisterLoggerProviderOptions(IServiceCollection services)
LoggerProviderOptions.RegisterProviderOptions(services);
}
}
-
- ///
- /// Adds an OpenTelemetry logger named 'OpenTelemetry' to the .
- ///
- ///
- /// The to use.
- /// Optional configuration action.
- /// The supplied for call chaining.
- public static ILoggingBuilder AddOpenTelemetry(
- this ILoggingBuilder builder,
- Action? configure)
- {
- if (configure != null)
- {
- builder.Services.Configure(configure);
- }
-
- return AddOpenTelemetry(builder);
- }
}
diff --git a/src/OpenTelemetry/Metrics/AggregatorStore.cs b/src/OpenTelemetry/Metrics/AggregatorStore.cs
index 9bbceacf6d6..42d1aa0443c 100644
--- a/src/OpenTelemetry/Metrics/AggregatorStore.cs
+++ b/src/OpenTelemetry/Metrics/AggregatorStore.cs
@@ -25,6 +25,7 @@ namespace OpenTelemetry.Metrics;
internal sealed class AggregatorStore
{
internal readonly bool OutputDelta;
+ internal readonly bool ShouldReclaimUnusedMetricPoints;
internal long DroppedMeasurements = 0;
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.";
@@ -81,6 +82,7 @@ internal AggregatorStore(
AggregationTemporality temporality,
int maxMetricPoints,
bool emitOverflowAttribute,
+ bool shouldReclaimUnusedMetricPoints,
ExemplarFilter? exemplarFilter = null)
{
this.name = metricStreamIdentity.InstrumentName;
@@ -122,7 +124,9 @@ internal AggregatorStore(
reservedMetricPointsCount++;
}
- if (this.OutputDelta)
+ this.ShouldReclaimUnusedMetricPoints = shouldReclaimUnusedMetricPoints;
+
+ if (this.OutputDelta && shouldReclaimUnusedMetricPoints)
{
this.availableMetricPoints = new Queue(maxMetricPoints - reservedMetricPointsCount);
@@ -181,7 +185,7 @@ internal int Snapshot()
this.batchSize = 0;
if (this.OutputDelta)
{
- if (this.reclaimMetricPoints)
+ if (this.ShouldReclaimUnusedMetricPoints && this.reclaimMetricPoints)
{
this.SnapshotDeltaWithMetricPointReclaim();
}
diff --git a/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs
index d5c979604bf..767cf30714b 100644
--- a/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs
+++ b/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs
@@ -277,6 +277,8 @@ public static MeterProviderBuilder SetMaxMetricPointsPerMetricStream(this MeterP
/// The supplied for chaining.
public static MeterProviderBuilder SetResourceBuilder(this MeterProviderBuilder meterProviderBuilder, ResourceBuilder resourceBuilder)
{
+ Guard.ThrowIfNull(resourceBuilder);
+
meterProviderBuilder.ConfigureBuilder((sp, builder) =>
{
if (builder is MeterProviderBuilderSdk meterProviderBuilderSdk)
@@ -297,6 +299,8 @@ public static MeterProviderBuilder SetResourceBuilder(this MeterProviderBuilder
/// The supplied for chaining.
public static MeterProviderBuilder ConfigureResource(this MeterProviderBuilder meterProviderBuilder, Action configure)
{
+ Guard.ThrowIfNull(configure);
+
meterProviderBuilder.ConfigureBuilder((sp, builder) =>
{
if (builder is MeterProviderBuilderSdk meterProviderBuilderSdk)
diff --git a/src/OpenTelemetry/Metrics/MeterProviderSdk.cs b/src/OpenTelemetry/Metrics/MeterProviderSdk.cs
index 917bb0b35f8..74405b02be2 100644
--- a/src/OpenTelemetry/Metrics/MeterProviderSdk.cs
+++ b/src/OpenTelemetry/Metrics/MeterProviderSdk.cs
@@ -30,8 +30,11 @@ internal sealed class MeterProviderSdk : MeterProvider
internal readonly IDisposable? OwnedServiceProvider;
internal int ShutdownCount;
internal bool Disposed;
+ internal bool ShouldReclaimUnusedMetricPoints;
+ internal Action? OnCollectObservableInstruments;
private const string EmitOverFlowAttributeConfigKey = "OTEL_DOTNET_EXPERIMENTAL_METRICS_EMIT_OVERFLOW_ATTRIBUTE";
+ private const string ReclaimUnusedMetricPointsConfigKey = "OTEL_DOTNET_EXPERIMENTAL_METRICS_RECLAIM_UNUSED_METRIC_POINTS";
private readonly List
@@ -17,6 +18,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryBuilderTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryBuilderTests.cs
index 4d31768c2ff..43da25a08b4 100644
--- a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryBuilderTests.cs
+++ b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryBuilderTests.cs
@@ -75,4 +75,41 @@ public void ConfigureResourceTest()
loggerProvider.Resource.Attributes,
kvp => kvp.Key == "l_key1" && (string)kvp.Value == "l_value1");
}
+
+ [Fact]
+ public void ConfigureResourceServiceProviderTest()
+ {
+ var services = new ServiceCollection();
+
+ services.AddSingleton();
+
+ services.AddOpenTelemetry()
+ .ConfigureResource(r => r.AddDetector(sp => sp.GetRequiredService()))
+ .WithLogging()
+ .WithMetrics()
+ .WithTracing();
+
+ using var sp = services.BuildServiceProvider();
+
+ var tracerProvider = sp.GetRequiredService() as TracerProviderSdk;
+ var meterProvider = sp.GetRequiredService() as MeterProviderSdk;
+ var loggerProvider = sp.GetRequiredService() as LoggerProviderSdk;
+
+ Assert.NotNull(tracerProvider);
+ Assert.NotNull(meterProvider);
+ Assert.NotNull(loggerProvider);
+
+ Assert.Single(tracerProvider.Resource.Attributes, kvp => kvp.Key == "key1" && (string)kvp.Value == "value1");
+ Assert.Single(meterProvider.Resource.Attributes, kvp => kvp.Key == "key1" && (string)kvp.Value == "value1");
+ Assert.Single(loggerProvider.Resource.Attributes, kvp => kvp.Key == "key1" && (string)kvp.Value == "value1");
+ }
+
+ private sealed class TestResourceDetector : IResourceDetector
+ {
+ public Resource Detect() => ResourceBuilder.CreateEmpty().AddAttributes(
+ new Dictionary
+ {
+ ["key1"] = "value1",
+ }).Build();
+ }
}
diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryMetricsBuilderExtensionsTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryMetricsBuilderExtensionsTests.cs
new file mode 100644
index 00000000000..66f2c11ee97
--- /dev/null
+++ b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryMetricsBuilderExtensionsTests.cs
@@ -0,0 +1,259 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System.Diagnostics.Metrics;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Configuration.Memory;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.Metrics;
+using Microsoft.Extensions.Options;
+using OpenTelemetry.Internal;
+using OpenTelemetry.Logs;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Metrics.Tests;
+using OpenTelemetry.Tests;
+using OpenTelemetry.Trace;
+using Xunit;
+
+namespace OpenTelemetry.Extensions.Hosting.Tests;
+
+public class OpenTelemetryMetricsBuilderExtensionsTests
+{
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void EnableMetricsTest(bool useWithMetricsStyle)
+ {
+ using var meter = new Meter(Utils.GetCurrentMethodName());
+ List exportedItems = new();
+
+ using (var host = MetricTestsBase.BuildHost(
+ useWithMetricsStyle,
+ configureMetricsBuilder: builder => builder.EnableMetrics(meter.Name),
+ configureMeterProviderBuilder: builder => builder.AddInMemoryExporter(exportedItems)))
+ {
+ var counter = meter.CreateCounter("TestCounter");
+ counter.Add(1);
+ }
+
+ AssertSingleMetricWithLongSum(exportedItems);
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void EnableMetricsWithAddMeterTest(bool useWithMetricsStyle)
+ {
+ using var meter = new Meter(Utils.GetCurrentMethodName());
+ List exportedItems = new();
+
+ using (var host = MetricTestsBase.BuildHost(
+ useWithMetricsStyle,
+ configureMetricsBuilder: builder => builder.EnableMetrics(meter.Name),
+ configureMeterProviderBuilder: builder => builder
+ .AddSdkMeter(meter.Name)
+ .AddInMemoryExporter(exportedItems)))
+ {
+ var counter = meter.CreateCounter("TestCounter");
+ counter.Add(1);
+ }
+
+ AssertSingleMetricWithLongSum(exportedItems);
+ }
+
+ [Theory]
+ [InlineData(false, MetricReaderTemporalityPreference.Delta)]
+ [InlineData(true, MetricReaderTemporalityPreference.Delta)]
+ [InlineData(false, MetricReaderTemporalityPreference.Cumulative)]
+ [InlineData(true, MetricReaderTemporalityPreference.Cumulative)]
+ public void ReloadOfMetricsViaIConfigurationWithExportCleanupTest(bool useWithMetricsStyle, MetricReaderTemporalityPreference temporalityPreference)
+ {
+ using var inMemoryEventListener = new InMemoryEventListener(OpenTelemetrySdkEventSource.Log);
+
+ using var meter = new Meter(Utils.GetCurrentMethodName());
+ List exportedItems = new();
+
+ var source = new MemoryConfigurationSource();
+ var memory = new MemoryConfigurationProvider(source);
+ var configuration = new ConfigurationRoot(new[] { memory });
+
+ using var host = MetricTestsBase.BuildHost(
+ useWithMetricsStyle,
+ configureAppConfiguration: (context, builder) => builder.AddConfiguration(configuration),
+ configureMeterProviderBuilder: builder => builder
+ .AddInMemoryExporter(exportedItems, reader => reader.TemporalityPreference = temporalityPreference));
+
+ var meterProvider = host.Services.GetRequiredService();
+ var options = host.Services.GetRequiredService>();
+
+ var counter = meter.CreateCounter("TestCounter");
+ counter.Add(1);
+
+ meterProvider.ForceFlush();
+
+ Assert.Empty(exportedItems);
+
+ memory.Set($"Metrics:EnabledMetrics:{meter.Name}:Default", "true");
+
+ configuration.Reload();
+
+ counter.Add(1);
+
+ meterProvider.ForceFlush();
+
+ AssertSingleMetricWithLongSum(exportedItems);
+
+ exportedItems.Clear();
+
+ memory.Set($"Metrics:EnabledMetrics:{meter.Name}:Default", "false");
+
+ configuration.Reload();
+
+ counter.Add(1);
+
+ meterProvider.ForceFlush();
+
+ if (temporalityPreference == MetricReaderTemporalityPreference.Cumulative)
+ {
+ // Note: When in Cumulative the metric shows up on the export
+ // immediately after being deactivated and then is ignored.
+ AssertSingleMetricWithLongSum(exportedItems);
+
+ meterProvider.ForceFlush();
+ exportedItems.Clear();
+ Assert.Empty(exportedItems);
+ }
+ else
+ {
+ Assert.Empty(exportedItems);
+ }
+
+ memory.Set($"Metrics:OpenTelemetry:EnabledMetrics:{meter.Name}:Default", "true");
+
+ configuration.Reload();
+
+ counter.Add(1);
+
+ meterProvider.ForceFlush();
+
+ AssertSingleMetricWithLongSum(exportedItems);
+
+ var duplicateMetricInstrumentEvents = inMemoryEventListener.Events.Where((e) => e.EventId == 38);
+
+ // Note: We currently log a duplicate warning anytime a metric is reactivated.
+ Assert.Single(duplicateMetricInstrumentEvents);
+
+ var metricInstrumentDeactivatedEvents = inMemoryEventListener.Events.Where((e) => e.EventId == 52);
+
+ Assert.Single(metricInstrumentDeactivatedEvents);
+
+ var metricInstrumentRemovedEvents = inMemoryEventListener.Events.Where((e) => e.EventId == 53);
+
+ Assert.Single(metricInstrumentRemovedEvents);
+ }
+
+ [Theory]
+ [InlineData(false, MetricReaderTemporalityPreference.Delta)]
+ [InlineData(true, MetricReaderTemporalityPreference.Delta)]
+ [InlineData(false, MetricReaderTemporalityPreference.Cumulative)]
+ [InlineData(true, MetricReaderTemporalityPreference.Cumulative)]
+ public void ReloadOfMetricsViaIConfigurationWithoutExportCleanupTest(bool useWithMetricsStyle, MetricReaderTemporalityPreference temporalityPreference)
+ {
+ using var inMemoryEventListener = new InMemoryEventListener(OpenTelemetrySdkEventSource.Log);
+
+ using var meter = new Meter(Utils.GetCurrentMethodName());
+ List exportedItems = new();
+
+ var source = new MemoryConfigurationSource();
+ var memory = new MemoryConfigurationProvider(source);
+ memory.Set($"Metrics:EnabledMetrics:{meter.Name}:Default", "true");
+ var configuration = new ConfigurationRoot(new[] { memory });
+
+ using var host = MetricTestsBase.BuildHost(
+ useWithMetricsStyle,
+ configureAppConfiguration: (context, builder) => builder.AddConfiguration(configuration),
+ configureMeterProviderBuilder: builder => builder
+ .AddInMemoryExporter(exportedItems, reader => reader.TemporalityPreference = temporalityPreference));
+
+ var meterProvider = host.Services.GetRequiredService();
+ var options = host.Services.GetRequiredService>();
+
+ var counter = meter.CreateCounter("TestCounter");
+ counter.Add(1);
+
+ memory.Set($"Metrics:EnabledMetrics:{meter.Name}:Default", "false");
+ configuration.Reload();
+ counter.Add(1);
+
+ memory.Set($"Metrics:EnabledMetrics:{meter.Name}:Default", "true");
+ configuration.Reload();
+ counter.Add(1);
+
+ meterProvider.ForceFlush();
+
+ // Note: We end up with 2 of the same metric being exported. This is
+ // because the current behavior when something is deactivated is to
+ // remove the metric. The next publish creates a new metric.
+ Assert.Equal(2, exportedItems.Count);
+
+ AssertMetricWithLongSum(exportedItems[0]);
+ AssertMetricWithLongSum(exportedItems[1]);
+
+ exportedItems.Clear();
+
+ counter.Add(1);
+
+ meterProvider.ForceFlush();
+
+ AssertSingleMetricWithLongSum(
+ exportedItems,
+ expectedValue: temporalityPreference == MetricReaderTemporalityPreference.Delta ? 1 : 2);
+
+ var duplicateMetricInstrumentEvents = inMemoryEventListener.Events.Where((e) => e.EventId == 38);
+
+ // Note: We currently log a duplicate warning anytime a metric is reactivated.
+ Assert.Single(duplicateMetricInstrumentEvents);
+
+ var metricInstrumentDeactivatedEvents = inMemoryEventListener.Events.Where((e) => e.EventId == 52);
+
+ Assert.Single(metricInstrumentDeactivatedEvents);
+
+ var metricInstrumentRemovedEvents = inMemoryEventListener.Events.Where((e) => e.EventId == 53);
+
+ Assert.Single(metricInstrumentRemovedEvents);
+ }
+
+ private static void AssertSingleMetricWithLongSum(List exportedItems, long expectedValue = 1)
+ {
+ Assert.Single(exportedItems);
+
+ AssertMetricWithLongSum(exportedItems[0], expectedValue);
+ }
+
+ private static void AssertMetricWithLongSum(Metric metric, long expectedValue = 1)
+ {
+ List metricPoints = new();
+ foreach (ref readonly var mp in metric.GetMetricPoints())
+ {
+ metricPoints.Add(mp);
+ }
+
+ Assert.Single(metricPoints);
+
+ var metricPoint = metricPoints[0];
+ Assert.Equal(expectedValue, metricPoint.GetSumLong());
+ }
+}
diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs
index 181dec9e367..2b447a816d8 100644
--- a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs
+++ b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs
@@ -37,9 +37,9 @@ public async Task AddOpenTelemetry_StartWithoutProvidersDoesNotThrow()
var host = builder.Build();
- await host.StartAsync().ConfigureAwait(false);
+ await host.StartAsync();
- await host.StopAsync().ConfigureAwait(false);
+ await host.StopAsync();
}
[Fact]
@@ -75,9 +75,9 @@ public async Task AddOpenTelemetry_StartWithExceptionsThrows()
var host = builder.Build();
- await Assert.ThrowsAsync(() => host.StartAsync()).ConfigureAwait(false);
+ await Assert.ThrowsAsync(() => host.StartAsync());
- await host.StopAsync().ConfigureAwait(false);
+ await host.StopAsync();
Assert.True(expectedInnerExceptionThrown);
}
@@ -172,11 +172,11 @@ public async Task AddOpenTelemetry_WithTracing_HostConfigurationHonoredTest()
Assert.False(configureBuilderCalled);
- await host.StartAsync().ConfigureAwait(false);
+ await host.StartAsync();
Assert.True(configureBuilderCalled);
- await host.StopAsync().ConfigureAwait(false);
+ await host.StopAsync();
host.Dispose();
}
@@ -295,11 +295,11 @@ public async Task AddOpenTelemetry_WithMetrics_HostConfigurationHonoredTest()
Assert.False(configureBuilderCalled);
- await host.StartAsync().ConfigureAwait(false);
+ await host.StartAsync();
Assert.True(configureBuilderCalled);
- await host.StopAsync().ConfigureAwait(false);
+ await host.StopAsync();
host.Dispose();
}
@@ -418,11 +418,11 @@ public async Task AddOpenTelemetry_WithLogging_HostConfigurationHonoredTest()
Assert.False(configureBuilderCalled);
- await host.StartAsync().ConfigureAwait(false);
+ await host.StartAsync();
Assert.True(configureBuilderCalled);
- await host.StopAsync().ConfigureAwait(false);
+ await host.StopAsync();
host.Dispose();
}
@@ -469,8 +469,8 @@ public async Task AddOpenTelemetry_HostedServiceOrder_DoesNotMatter()
});
var host = builder.Build();
- await host.StartAsync().ConfigureAwait(false);
- await host.StopAsync().ConfigureAwait(false);
+ await host.StartAsync();
+ await host.StopAsync();
host.Dispose();
Assert.Single(exportedItems);
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs
index 93ef8753772..a138be0d012 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs
@@ -23,6 +23,7 @@
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Moq;
using OpenTelemetry.Context.Propagation;
@@ -83,7 +84,7 @@ void ConfigureTestServices(IServiceCollection services)
.CreateClient())
{
// Act
- using var response = await client.GetAsync("/api/values").ConfigureAwait(false);
+ using var response = await client.GetAsync("/api/values");
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
@@ -94,7 +95,7 @@ void ConfigureTestServices(IServiceCollection services)
Assert.Single(exportedItems);
var activity = exportedItems[0];
- Assert.Equal(200, activity.GetTagValue(SemanticConventions.AttributeHttpStatusCode));
+ Assert.Equal(200, activity.GetTagValue(SemanticConventions.AttributeHttpResponseStatusCode));
Assert.Equal(ActivityStatusCode.Unset, activity.Status);
ValidateAspNetCoreActivity(activity, "/api/values");
}
@@ -130,7 +131,7 @@ void ConfigureTestServices(IServiceCollection services)
.CreateClient())
{
// Act
- using var response = await client.GetAsync("/api/values").ConfigureAwait(false);
+ using var response = await client.GetAsync("/api/values");
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
@@ -177,7 +178,7 @@ public async Task SuccessfulTemplateControllerCallUsesParentContext()
request.Headers.Add("traceparent", $"00-{expectedTraceId}-{expectedSpanId}-01");
// Act
- var response = await client.SendAsync(request).ConfigureAwait(false);
+ var response = await client.SendAsync(request);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
@@ -189,7 +190,6 @@ public async Task SuccessfulTemplateControllerCallUsesParentContext()
var activity = exportedItems[0];
Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", activity.OperationName);
- Assert.Equal("api/Values/{id}", activity.DisplayName);
Assert.Equal(expectedTraceId, activity.Context.TraceId);
Assert.Equal(expectedSpanId, activity.ParentSpanId);
@@ -241,7 +241,7 @@ public async Task CustomPropagator(bool addSampler)
}))
{
using var client = testFactory.CreateClient();
- using var response = await client.GetAsync("/api/values/2").ConfigureAwait(false);
+ using var response = await client.GetAsync("/api/values/2");
response.EnsureSuccessStatusCode(); // Status Code 200-299
WaitForActivityExport(exportedItems, 1);
@@ -251,7 +251,6 @@ public async Task CustomPropagator(bool addSampler)
var activity = exportedItems[0];
Assert.True(activity.Duration != TimeSpan.Zero);
- Assert.Equal("api/Values/{id}", activity.DisplayName);
Assert.Equal(expectedTraceId, activity.Context.TraceId);
Assert.Equal(expectedSpanId, activity.ParentSpanId);
@@ -292,8 +291,8 @@ void ConfigureTestServices(IServiceCollection services)
using var client = testFactory.CreateClient();
// Act
- using var response1 = await client.GetAsync("/api/values").ConfigureAwait(false);
- using var response2 = await client.GetAsync("/api/values/2").ConfigureAwait(false);
+ using var response1 = await client.GetAsync("/api/values");
+ using var response2 = await client.GetAsync("/api/values/2");
// Assert
response1.EnsureSuccessStatusCode(); // Status Code 200-299
@@ -344,8 +343,8 @@ void ConfigureTestServices(IServiceCollection services)
// Act
using (var inMemoryEventListener = new InMemoryEventListener(AspNetCoreInstrumentationEventSource.Log))
{
- using var response1 = await client.GetAsync("/api/values").ConfigureAwait(false);
- using var response2 = await client.GetAsync("/api/values/2").ConfigureAwait(false);
+ using var response1 = await client.GetAsync("/api/values");
+ using var response2 = await client.GetAsync("/api/values/2");
response1.EnsureSuccessStatusCode(); // Status Code 200-299
response2.EnsureSuccessStatusCode(); // Status Code 200-299
@@ -389,8 +388,8 @@ public async Task ExtractContextIrrespectiveOfSamplingDecision(SamplingDecision
// Test TraceContext Propagation
var request = new HttpRequestMessage(HttpMethod.Get, "/api/GetChildActivityTraceContext");
- var response = await client.SendAsync(request).ConfigureAwait(false);
- var childActivityTraceContext = JsonSerializer.Deserialize>(response.Content.ReadAsStringAsync().Result);
+ var response = await client.SendAsync(request);
+ var childActivityTraceContext = JsonSerializer.Deserialize>(await response.Content.ReadAsStringAsync());
response.EnsureSuccessStatusCode();
@@ -401,8 +400,8 @@ public async Task ExtractContextIrrespectiveOfSamplingDecision(SamplingDecision
// Test Baggage Context Propagation
request = new HttpRequestMessage(HttpMethod.Get, "/api/GetChildActivityBaggageContext");
- response = await client.SendAsync(request).ConfigureAwait(false);
- var childActivityBaggageContext = JsonSerializer.Deserialize>(response.Content.ReadAsStringAsync().Result);
+ response = await client.SendAsync(request);
+ var childActivityBaggageContext = JsonSerializer.Deserialize>(await response.Content.ReadAsStringAsync());
response.EnsureSuccessStatusCode();
@@ -455,12 +454,12 @@ public async Task ExtractContextIrrespectiveOfTheFilterApplied()
// Test TraceContext Propagation
var request = new HttpRequestMessage(HttpMethod.Get, "/api/GetChildActivityTraceContext");
- var response = await client.SendAsync(request).ConfigureAwait(false);
+ var response = await client.SendAsync(request);
// Ensure that filter was called
Assert.True(isFilterCalled);
- var childActivityTraceContext = JsonSerializer.Deserialize>(response.Content.ReadAsStringAsync().Result);
+ var childActivityTraceContext = JsonSerializer.Deserialize>(await response.Content.ReadAsStringAsync());
response.EnsureSuccessStatusCode();
@@ -471,8 +470,8 @@ public async Task ExtractContextIrrespectiveOfTheFilterApplied()
// Test Baggage Context Propagation
request = new HttpRequestMessage(HttpMethod.Get, "/api/GetChildActivityBaggageContext");
- response = await client.SendAsync(request).ConfigureAwait(false);
- var childActivityBaggageContext = JsonSerializer.Deserialize>(response.Content.ReadAsStringAsync().Result);
+ response = await client.SendAsync(request);
+ var childActivityBaggageContext = JsonSerializer.Deserialize>(await response.Content.ReadAsStringAsync());
response.EnsureSuccessStatusCode();
@@ -539,7 +538,7 @@ void ConfigureTestServices(IServiceCollection services)
request.Headers.TryAddWithoutValidation("baggage", "TestKey1=123,TestKey2=456");
// Act
- using var response = await client.SendAsync(request).ConfigureAwait(false);
+ using var response = await client.SendAsync(request);
}
stopSignal.WaitOne(5000);
@@ -593,7 +592,7 @@ void ConfigureTestServices(IServiceCollection services)
.CreateClient();
// Act
- using var response = await client.GetAsync("/api/values").ConfigureAwait(false);
+ using var response = await client.GetAsync("/api/values");
// Assert
Assert.Equal(shouldFilterBeCalled, filterCalled);
@@ -628,7 +627,7 @@ void ConfigureTestServices(IServiceCollection services)
})
.CreateClient())
{
- using var response = await client.GetAsync("/api/values/2").ConfigureAwait(false);
+ using var response = await client.GetAsync("/api/values/2");
response.EnsureSuccessStatusCode();
WaitForActivityExport(exportedItems, 2);
}
@@ -644,10 +643,9 @@ void ConfigureTestServices(IServiceCollection services)
Assert.Equal(activityName, middlewareActivity.OperationName);
Assert.Equal(activityName, middlewareActivity.DisplayName);
- // tag http.route should be added on activity started by asp.net core
- Assert.Equal("api/Values/{id}", aspnetcoreframeworkactivity.GetTagValue(SemanticConventions.AttributeHttpRoute) as string);
+ // tag http.method should be added on activity started by asp.net core
+ Assert.Equal("GET", aspnetcoreframeworkactivity.GetTagValue(SemanticConventions.AttributeHttpRequestMethod) as string);
Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", aspnetcoreframeworkactivity.OperationName);
- Assert.Equal("api/Values/{id}", aspnetcoreframeworkactivity.DisplayName);
}
[Theory]
@@ -694,7 +692,7 @@ void ConfigureTestServices(IServiceCollection services)
try
{
- using var response = await client.SendAsync(message).ConfigureAwait(false);
+ using var response = await client.SendAsync(message);
response.EnsureSuccessStatusCode();
}
catch
@@ -747,7 +745,7 @@ public async Task ActivitiesStartedInMiddlewareBySettingHostActivityToNullShould
})
.CreateClient())
{
- using var response = await client.GetAsync("/api/values/2").ConfigureAwait(false);
+ using var response = await client.GetAsync("/api/values/2");
response.EnsureSuccessStatusCode();
WaitForActivityExport(exportedItems, 2);
}
@@ -763,10 +761,9 @@ public async Task ActivitiesStartedInMiddlewareBySettingHostActivityToNullShould
Assert.Equal(activityName, middlewareActivity.OperationName);
Assert.Equal(activityName, middlewareActivity.DisplayName);
- // tag http.route should not be added on activity started by asp.net core as it will not be found during OnEventWritten event
- Assert.DoesNotContain(aspnetcoreframeworkactivity.TagObjects, t => t.Key == SemanticConventions.AttributeHttpRoute);
+ // tag http.method should be added on activity started by asp.net core
+ Assert.Equal("GET", aspnetcoreframeworkactivity.GetTagValue(SemanticConventions.AttributeHttpRequestMethod) as string);
Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", aspnetcoreframeworkactivity.OperationName);
- Assert.Equal("/api/values/2", aspnetcoreframeworkactivity.DisplayName);
}
#if NET7_0_OR_GREATER
@@ -797,7 +794,7 @@ void ConfigureTestServices(IServiceCollection services)
.CreateClient())
{
// Act
- using var response = await client.GetAsync("/api/values").ConfigureAwait(false);
+ using var response = await client.GetAsync("/api/values");
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
@@ -830,7 +827,7 @@ public async Task ShouldExportActivityWithOneOrMoreExceptionFilters(int mode)
.CreateClient())
{
// Act
- using var response = await client.GetAsync("/api/error").ConfigureAwait(false);
+ using var response = await client.GetAsync("/api/error");
WaitForActivityExport(exportedItems, 1);
}
@@ -844,51 +841,42 @@ public async Task DiagnosticSourceCallbacksAreReceivedOnlyForSubscribedEvents()
{
int numberOfUnSubscribedEvents = 0;
int numberofSubscribedEvents = 0;
- void ConfigureTestServices(IServiceCollection services)
- {
- this.tracerProvider = Sdk.CreateTracerProviderBuilder()
- .AddAspNetCoreInstrumentation(
- new TestHttpInListener(new AspNetCoreInstrumentationOptions())
+
+ this.tracerProvider = Sdk.CreateTracerProviderBuilder()
+ .AddAspNetCoreInstrumentation(
+ new TestHttpInListener(new AspNetCoreInstrumentationOptions())
+ {
+ OnEventWrittenCallback = (name, payload) =>
{
- OnEventWrittenCallback = (name, payload) =>
+ switch (name)
{
- switch (name)
- {
- case HttpInListener.OnStartEvent:
- {
- numberofSubscribedEvents++;
- }
-
- break;
- case HttpInListener.OnStopEvent:
- {
- numberofSubscribedEvents++;
- }
+ case HttpInListener.OnStartEvent:
+ {
+ numberofSubscribedEvents++;
+ }
- break;
- case HttpInListener.OnMvcBeforeActionEvent:
- {
- numberofSubscribedEvents++;
- }
+ break;
+ case HttpInListener.OnStopEvent:
+ {
+ numberofSubscribedEvents++;
+ }
- break;
- default:
- {
- numberOfUnSubscribedEvents++;
- }
+ break;
+ default:
+ {
+ numberOfUnSubscribedEvents++;
+ }
- break;
- }
- },
- })
- .Build();
- }
+ break;
+ }
+ },
+ })
+ .Build();
// Arrange
using (var client = this.factory
.WithWebHostBuilder(builder =>
{
- builder.ConfigureTestServices(ConfigureTestServices);
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
})
.CreateClient())
@@ -896,11 +884,11 @@ void ConfigureTestServices(IServiceCollection services)
using var request = new HttpRequestMessage(HttpMethod.Get, "/api/values");
// Act
- using var response = await client.SendAsync(request).ConfigureAwait(false);
+ using var response = await client.SendAsync(request);
}
Assert.Equal(0, numberOfUnSubscribedEvents);
- Assert.Equal(3, numberofSubscribedEvents);
+ Assert.Equal(2, numberofSubscribedEvents);
}
[Fact]
@@ -909,62 +897,53 @@ public async Task DiagnosticSourceExceptionCallbackIsReceivedForUnHandledExcepti
int numberOfUnSubscribedEvents = 0;
int numberofSubscribedEvents = 0;
int numberOfExceptionCallbacks = 0;
- void ConfigureTestServices(IServiceCollection services)
- {
- this.tracerProvider = Sdk.CreateTracerProviderBuilder()
- .AddAspNetCoreInstrumentation(
- new TestHttpInListener(new AspNetCoreInstrumentationOptions())
+
+ this.tracerProvider = Sdk.CreateTracerProviderBuilder()
+ .AddAspNetCoreInstrumentation(
+ new TestHttpInListener(new AspNetCoreInstrumentationOptions())
+ {
+ OnEventWrittenCallback = (name, payload) =>
{
- OnEventWrittenCallback = (name, payload) =>
+ switch (name)
{
- switch (name)
- {
- case HttpInListener.OnStartEvent:
- {
- numberofSubscribedEvents++;
- }
-
- break;
- case HttpInListener.OnStopEvent:
- {
- numberofSubscribedEvents++;
- }
+ case HttpInListener.OnStartEvent:
+ {
+ numberofSubscribedEvents++;
+ }
- break;
- case HttpInListener.OnMvcBeforeActionEvent:
- {
- numberofSubscribedEvents++;
- }
+ break;
+ case HttpInListener.OnStopEvent:
+ {
+ numberofSubscribedEvents++;
+ }
- break;
+ break;
- // TODO: Add test case for validating name for both the types
- // of exception event.
- case HttpInListener.OnUnhandledHostingExceptionEvent:
- case HttpInListener.OnUnHandledDiagnosticsExceptionEvent:
- {
- numberofSubscribedEvents++;
- numberOfExceptionCallbacks++;
- }
+ // TODO: Add test case for validating name for both the types
+ // of exception event.
+ case HttpInListener.OnUnhandledHostingExceptionEvent:
+ case HttpInListener.OnUnHandledDiagnosticsExceptionEvent:
+ {
+ numberofSubscribedEvents++;
+ numberOfExceptionCallbacks++;
+ }
- break;
- default:
- {
- numberOfUnSubscribedEvents++;
- }
+ break;
+ default:
+ {
+ numberOfUnSubscribedEvents++;
+ }
- break;
- }
- },
- })
- .Build();
- }
+ break;
+ }
+ },
+ })
+ .Build();
// Arrange
using (var client = this.factory
.WithWebHostBuilder(builder =>
{
- builder.ConfigureTestServices(ConfigureTestServices);
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
})
.CreateClient())
@@ -974,7 +953,7 @@ void ConfigureTestServices(IServiceCollection services)
using var request = new HttpRequestMessage(HttpMethod.Get, "/api/error");
// Act
- using var response = await client.SendAsync(request).ConfigureAwait(false);
+ using var response = await client.SendAsync(request);
}
catch
{
@@ -984,18 +963,18 @@ void ConfigureTestServices(IServiceCollection services)
Assert.Equal(1, numberOfExceptionCallbacks);
Assert.Equal(0, numberOfUnSubscribedEvents);
- Assert.Equal(4, numberofSubscribedEvents);
+ Assert.Equal(3, numberofSubscribedEvents);
}
- [Fact(Skip = "https://github.com/open-telemetry/opentelemetry-dotnet/issues/4884")]
+ [Fact]
public async Task DiagnosticSourceExceptionCallBackIsNotReceivedForExceptionsHandledInMiddleware()
{
int numberOfUnSubscribedEvents = 0;
- int numberofSubscribedEvents = 0;
+ int numberOfSubscribedEvents = 0;
int numberOfExceptionCallbacks = 0;
// configure SDK
- using var tracerprovider = Sdk.CreateTracerProviderBuilder()
+ this.tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddAspNetCoreInstrumentation(
new TestHttpInListener(new AspNetCoreInstrumentationOptions())
{
@@ -1005,13 +984,13 @@ public async Task DiagnosticSourceExceptionCallBackIsNotReceivedForExceptionsHan
{
case HttpInListener.OnStartEvent:
{
- numberofSubscribedEvents++;
+ numberOfSubscribedEvents++;
}
break;
case HttpInListener.OnStopEvent:
{
- numberofSubscribedEvents++;
+ numberOfSubscribedEvents++;
}
break;
@@ -1021,7 +1000,7 @@ public async Task DiagnosticSourceExceptionCallBackIsNotReceivedForExceptionsHan
case HttpInListener.OnUnhandledHostingExceptionEvent:
case HttpInListener.OnUnHandledDiagnosticsExceptionEvent:
{
- numberofSubscribedEvents++;
+ numberOfSubscribedEvents++;
numberOfExceptionCallbacks++;
}
@@ -1037,96 +1016,35 @@ public async Task DiagnosticSourceExceptionCallBackIsNotReceivedForExceptionsHan
})
.Build();
- var builder = WebApplication.CreateBuilder();
- builder.Logging.ClearProviders();
- var app = builder.Build();
-
- app.UseExceptionHandler(handler =>
- {
- handler.Run(async (ctx) =>
+ using (var client = this.factory
+ .WithWebHostBuilder(builder =>
{
- await ctx.Response.WriteAsync("handled").ConfigureAwait(false);
- });
- });
-
- app.Map("/error", ThrowException);
-
- static void ThrowException(IApplicationBuilder app)
+ builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
+ builder.Configure(app => app
+ .UseExceptionHandler(handler =>
+ {
+ handler.Run(async (ctx) =>
+ {
+ await ctx.Response.WriteAsync("handled");
+ });
+ }));
+ })
+ .CreateClient())
{
- app.Run(context =>
+ try
{
- throw new Exception("CustomException");
- });
- }
-
- _ = app.RunAsync();
-
- using var client = new HttpClient();
- try
- {
- await client.GetStringAsync("http://localhost:5000/error").ConfigureAwait(false);
- }
- catch
- {
- // ignore 500 error.
- }
-
- Assert.Equal(0, numberOfExceptionCallbacks);
- Assert.Equal(0, numberOfUnSubscribedEvents);
- Assert.Equal(2, numberofSubscribedEvents);
-
- await app.DisposeAsync().ConfigureAwait(false);
- }
-
- [Fact]
- public async Task RouteInformationIsNotAddedToRequestsOutsideOfMVC()
- {
- var exportedItems = new List();
-
- // configure SDK
- using var tracerprovider = Sdk.CreateTracerProviderBuilder()
- .AddAspNetCoreInstrumentation()
- .AddInMemoryExporter(exportedItems)
- .Build();
-
- var builder = WebApplication.CreateBuilder();
- builder.Logging.ClearProviders();
- var app = builder.Build();
-
- app.MapGet("/custom/{name:alpha}", () => "Hello");
-
- _ = app.RunAsync();
-
- using var client = new HttpClient();
- var res = await client.GetStringAsync("http://localhost:5000/custom/abc").ConfigureAwait(false);
- Assert.NotNull(res);
-
- tracerprovider.ForceFlush();
- for (var i = 0; i < 10; i++)
- {
- if (exportedItems.Count > 0)
+ using var request = new HttpRequestMessage(HttpMethod.Get, "/api/error");
+ using var response = await client.SendAsync(request);
+ }
+ catch
{
- break;
+ // ignore exception
}
-
- // We need to let End callback execute as it is executed AFTER response was returned.
- // In unit tests environment there may be a lot of parallel unit tests executed, so
- // giving some breezing room for the End callback to complete
- await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
}
- var activity = exportedItems[0];
-
- Assert.NotNull(activity);
-
- // After fix update to Contains http.route
- Assert.DoesNotContain(activity.TagObjects, t => t.Key == SemanticConventions.AttributeHttpRoute);
- Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", activity.OperationName);
-
- // After fix this should be /custom/{name:alpha}
- Assert.Equal("/custom/abc", activity.DisplayName);
-
- await app.DisposeAsync().ConfigureAwait(false);
+ Assert.Equal(0, numberOfExceptionCallbacks);
+ Assert.Equal(0, numberOfUnSubscribedEvents);
+ Assert.Equal(2, numberOfSubscribedEvents);
}
public void Dispose()
@@ -1158,7 +1076,7 @@ private static void ValidateAspNetCoreActivity(Activity activityToValidate, stri
Assert.Equal(HttpInListener.ActivitySourceName, activityToValidate.Source.Name);
Assert.Equal(HttpInListener.Version.ToString(), activityToValidate.Source.Version);
#endif
- Assert.Equal(expectedHttpPath, activityToValidate.GetTagValue(SemanticConventions.AttributeHttpTarget) as string);
+ Assert.Equal(expectedHttpPath, activityToValidate.GetTagValue(SemanticConventions.AttributeUrlPath) as string);
}
private static void AssertException(List exportedItems)
@@ -1195,16 +1113,10 @@ private void ConfigureExceptionFilters(IServiceCollection services, int mode, re
.Build();
}
- private class ExtractOnlyPropagator : TextMapPropagator
+ private class ExtractOnlyPropagator(ActivityContext activityContext, Baggage baggage) : TextMapPropagator
{
- private readonly ActivityContext activityContext;
- private readonly Baggage baggage;
-
- public ExtractOnlyPropagator(ActivityContext activityContext, Baggage baggage)
- {
- this.activityContext = activityContext;
- this.baggage = baggage;
- }
+ private readonly ActivityContext activityContext = activityContext;
+ private readonly Baggage baggage = baggage;
public override ISet Fields => throw new NotImplementedException();
@@ -1219,16 +1131,10 @@ public override void Inject(PropagationContext context, T carrier, Action> attributes = null) : Sampler
{
- private readonly SamplingDecision samplingDecision;
- private readonly IEnumerable> attributes;
-
- public TestSampler(SamplingDecision samplingDecision, IEnumerable> attributes = null)
- {
- this.samplingDecision = samplingDecision;
- this.attributes = attributes;
- }
+ private readonly SamplingDecision samplingDecision = samplingDecision;
+ private readonly IEnumerable> attributes = attributes;
public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)
{
@@ -1236,15 +1142,10 @@ public override SamplingResult ShouldSample(in SamplingParameters samplingParame
}
}
- private class TestHttpInListener : HttpInListener
+ private class TestHttpInListener(AspNetCoreInstrumentationOptions options) : HttpInListener(options)
{
public Action OnEventWrittenCallback;
- public TestHttpInListener(AspNetCoreInstrumentationOptions options)
- : base(options)
- {
- }
-
public override void OnEventWritten(string name, object payload)
{
base.OnEventWritten(name, payload);
@@ -1253,17 +1154,11 @@ public override void OnEventWritten(string name, object payload)
}
}
- private class TestNullHostActivityMiddlewareImpl : ActivityMiddleware.ActivityMiddlewareImpl
+ private class TestNullHostActivityMiddlewareImpl(string activitySourceName, string activityName) : ActivityMiddleware.ActivityMiddlewareImpl
{
- private ActivitySource activitySource;
+ private readonly ActivitySource activitySource = new(activitySourceName);
+ private readonly string activityName = activityName;
private Activity activity;
- private string activityName;
-
- public TestNullHostActivityMiddlewareImpl(string activitySourceName, string activityName)
- {
- this.activitySource = new ActivitySource(activitySourceName);
- this.activityName = activityName;
- }
public override void PreProcess(HttpContext context)
{
@@ -1281,17 +1176,11 @@ public override void PostProcess(HttpContext context)
}
}
- private class TestActivityMiddlewareImpl : ActivityMiddleware.ActivityMiddlewareImpl
+ private class TestActivityMiddlewareImpl(string activitySourceName, string activityName) : ActivityMiddleware.ActivityMiddlewareImpl
{
- private ActivitySource activitySource;
+ private readonly ActivitySource activitySource = new(activitySourceName);
+ private readonly string activityName = activityName;
private Activity activity;
- private string activityName;
-
- public TestActivityMiddlewareImpl(string activitySourceName, string activityName)
- {
- this.activitySource = new ActivitySource(activitySourceName);
- this.activityName = activityName;
- }
public override void PreProcess(HttpContext context)
{
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs
index 1059bee9281..c685f0e2737 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs
@@ -39,11 +39,11 @@ public IncomingRequestsCollectionsIsAccordingToTheSpecTests(WebApplicationFactor
}
[Theory]
- [InlineData("/api/values", null, "user-agent", 503, "503")]
- [InlineData("/api/values", "?query=1", null, 503, null)]
+ [InlineData("/api/values", null, "user-agent", 200, null)]
+ [InlineData("/api/values", "?query=1", null, 200, null)]
[InlineData("/api/exception", null, null, 503, null)]
[InlineData("/api/exception", null, null, 503, null, true)]
- public async Task SuccessfulTemplateControllerCallGeneratesASpan_Old(
+ public async Task SuccessfulTemplateControllerCallGeneratesASpan_New(
string urlPath,
string query,
string userAgent,
@@ -51,102 +51,94 @@ public async Task SuccessfulTemplateControllerCallGeneratesASpan_Old(
string reasonPhrase,
bool recordException = false)
{
- try
- {
- Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", "none");
-
- var exportedItems = new List();
+ var exportedItems = new List();
- // Arrange
- using (var client = this.factory
- .WithWebHostBuilder(builder =>
- {
- builder.ConfigureTestServices((IServiceCollection services) =>
- {
- services.AddSingleton(new TestCallbackMiddlewareImpl(statusCode, reasonPhrase));
- services.AddOpenTelemetry()
- .WithTracing(builder => builder
- .AddAspNetCoreInstrumentation(options => options.RecordException = recordException)
- .AddInMemoryExporter(exportedItems));
- });
- builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
- })
- .CreateClient())
+ // Arrange
+ using (var client = this.factory
+ .WithWebHostBuilder(builder =>
{
- try
+ builder.ConfigureTestServices((IServiceCollection services) =>
{
- if (!string.IsNullOrEmpty(userAgent))
- {
- client.DefaultRequestHeaders.Add("User-Agent", userAgent);
- }
-
- // Act
- var path = urlPath;
- if (query != null)
- {
- path += query;
- }
-
- using var response = await client.GetAsync(path).ConfigureAwait(false);
- }
- catch (Exception)
+ services.AddSingleton(new TestCallbackMiddlewareImpl(statusCode, reasonPhrase));
+ services.AddOpenTelemetry()
+ .WithTracing(builder => builder
+ .AddAspNetCoreInstrumentation(options => options.RecordException = recordException)
+ .AddInMemoryExporter(exportedItems));
+ });
+ builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
+ })
+ .CreateClient())
+ {
+ try
+ {
+ if (!string.IsNullOrEmpty(userAgent))
{
- // ignore errors
+ client.DefaultRequestHeaders.Add("User-Agent", userAgent);
}
- for (var i = 0; i < 10; i++)
+ // Act
+ var path = urlPath;
+ if (query != null)
{
- if (exportedItems.Count == 1)
- {
- break;
- }
-
- // We need to let End callback execute as it is executed AFTER response was returned.
- // In unit tests environment there may be a lot of parallel unit tests executed, so
- // giving some breezing room for the End callback to complete
- await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
+ path += query;
}
- }
-
- Assert.Single(exportedItems);
- var activity = exportedItems[0];
-
- Assert.Equal(ActivityKind.Server, activity.Kind);
- Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeNetHostName));
- Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeHttpMethod));
- Assert.Equal("1.1", activity.GetTagValue(SemanticConventions.AttributeHttpFlavor));
- Assert.Equal("http", activity.GetTagValue(SemanticConventions.AttributeHttpScheme));
- Assert.Equal(urlPath, activity.GetTagValue(SemanticConventions.AttributeHttpTarget));
- Assert.Equal($"http://localhost{urlPath}{query}", activity.GetTagValue(SemanticConventions.AttributeHttpUrl));
- Assert.Equal(statusCode, activity.GetTagValue(SemanticConventions.AttributeHttpStatusCode));
- if (statusCode == 503)
- {
- Assert.Equal(ActivityStatusCode.Error, activity.Status);
+ using var response = await client.GetAsync(path);
}
- else
+ catch (Exception)
{
- Assert.Equal(ActivityStatusCode.Unset, activity.Status);
+ // ignore errors
}
- // Instrumentation is not expected to set status description
- // as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode
- Assert.Null(activity.StatusDescription);
-
- if (recordException)
+ for (var i = 0; i < 10; i++)
{
- Assert.Single(activity.Events);
- Assert.Equal("exception", activity.Events.First().Name);
+ if (exportedItems.Count == 1)
+ {
+ break;
+ }
+
+ // We need to let End callback execute as it is executed AFTER response was returned.
+ // In unit tests environment there may be a lot of parallel unit tests executed, so
+ // giving some breezing room for the End callback to complete
+ await Task.Delay(TimeSpan.FromSeconds(1));
}
+ }
- ValidateTagValue(activity, SemanticConventions.AttributeHttpUserAgent, userAgent);
+ Assert.Single(exportedItems);
+ var activity = exportedItems[0];
- activity.Dispose();
+ Assert.Equal(ActivityKind.Server, activity.Kind);
+ Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeServerAddress));
+ Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeHttpRequestMethod));
+ Assert.Equal("1.1", activity.GetTagValue(SemanticConventions.AttributeNetworkProtocolVersion));
+ Assert.Equal("http", activity.GetTagValue(SemanticConventions.AttributeUrlScheme));
+ Assert.Equal(urlPath, activity.GetTagValue(SemanticConventions.AttributeUrlPath));
+ Assert.Equal(query, activity.GetTagValue(SemanticConventions.AttributeUrlQuery));
+ Assert.Equal(statusCode, activity.GetTagValue(SemanticConventions.AttributeHttpResponseStatusCode));
+
+ if (statusCode == 503)
+ {
+ Assert.Equal(ActivityStatusCode.Error, activity.Status);
+ Assert.Equal("System.Exception", activity.GetTagValue(SemanticConventions.AttributeErrorType));
+ }
+ else
+ {
+ Assert.Equal(ActivityStatusCode.Unset, activity.Status);
}
- finally
+
+ // Instrumentation is not expected to set status description
+ // as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode
+ Assert.Null(activity.StatusDescription);
+
+ if (recordException)
{
- Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", null);
+ Assert.Single(activity.Events);
+ Assert.Equal("exception", activity.Events.First().Name);
}
+
+ ValidateTagValue(activity, SemanticConventions.AttributeUserAgentOriginal, userAgent);
+
+ activity.Dispose();
}
private static void ValidateTagValue(Activity activity, string attribute, string expectedValue)
@@ -176,7 +168,7 @@ public override async Task ProcessAsync(HttpContext context)
{
context.Response.StatusCode = this.statusCode;
context.Response.HttpContext.Features.Get().ReasonPhrase = this.reasonPhrase;
- await context.Response.WriteAsync("empty").ConfigureAwait(false);
+ await context.Response.WriteAsync("empty");
if (context.Request.Path.Value.EndsWith("exception"))
{
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests_Dupe.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests_Dupe.cs
deleted file mode 100644
index 8bfe675ed5d..00000000000
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests_Dupe.cs
+++ /dev/null
@@ -1,196 +0,0 @@
-//
-// Copyright The OpenTelemetry Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-using System.Diagnostics;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Http.Features;
-using Microsoft.AspNetCore.Mvc.Testing;
-using Microsoft.AspNetCore.TestHost;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using OpenTelemetry.Trace;
-using TestApp.AspNetCore;
-using Xunit;
-
-namespace OpenTelemetry.Instrumentation.AspNetCore.Tests;
-
-public class IncomingRequestsCollectionsIsAccordingToTheSpecTests_Dupe
- : IClassFixture>
-{
- private readonly WebApplicationFactory factory;
-
- public IncomingRequestsCollectionsIsAccordingToTheSpecTests_Dupe(WebApplicationFactory factory)
- {
- this.factory = factory;
- }
-
- [Theory]
- [InlineData("/api/values", null, "user-agent", 503, "503")]
- [InlineData("/api/values", "?query=1", null, 503, null)]
- [InlineData("/api/exception", null, null, 503, null)]
- [InlineData("/api/exception", null, null, 503, null, true)]
- public async Task SuccessfulTemplateControllerCallGeneratesASpan_Dupe(
- string urlPath,
- string query,
- string userAgent,
- int statusCode,
- string reasonPhrase,
- bool recordException = false)
- {
- try
- {
- Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup");
-
- var exportedItems = new List();
-
- // Arrange
- using (var client = this.factory
- .WithWebHostBuilder(builder =>
- {
- builder.ConfigureTestServices((IServiceCollection services) =>
- {
- services.AddSingleton(new TestCallbackMiddlewareImpl(statusCode, reasonPhrase));
- services.AddOpenTelemetry()
- .WithTracing(builder => builder
- .AddAspNetCoreInstrumentation(options => options.RecordException = recordException)
- .AddInMemoryExporter(exportedItems));
- });
- builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
- })
- .CreateClient())
- {
- try
- {
- if (!string.IsNullOrEmpty(userAgent))
- {
- client.DefaultRequestHeaders.Add("User-Agent", userAgent);
- }
-
- // Act
- var path = urlPath;
- if (query != null)
- {
- path += query;
- }
-
- using var response = await client.GetAsync(path).ConfigureAwait(false);
- }
- catch (Exception)
- {
- // ignore errors
- }
-
- for (var i = 0; i < 10; i++)
- {
- if (exportedItems.Count == 1)
- {
- break;
- }
-
- // We need to let End callback execute as it is executed AFTER response was returned.
- // In unit tests environment there may be a lot of parallel unit tests executed, so
- // giving some breezing room for the End callback to complete
- await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
- }
- }
-
- Assert.Single(exportedItems);
- var activity = exportedItems[0];
-
- Assert.Equal(ActivityKind.Server, activity.Kind);
- Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeServerAddress));
- Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeNetHostName));
- Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeHttpRequestMethod));
- Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeHttpMethod));
- Assert.Equal("1.1", activity.GetTagValue(SemanticConventions.AttributeNetworkProtocolVersion));
- Assert.Equal("1.1", activity.GetTagValue(SemanticConventions.AttributeHttpFlavor));
- Assert.Equal("http", activity.GetTagValue(SemanticConventions.AttributeUrlScheme));
- Assert.Equal("http", activity.GetTagValue(SemanticConventions.AttributeHttpScheme));
- Assert.Equal(urlPath, activity.GetTagValue(SemanticConventions.AttributeUrlPath));
- Assert.Equal(urlPath, activity.GetTagValue(SemanticConventions.AttributeHttpTarget));
- Assert.Equal($"http://localhost{urlPath}{query}", activity.GetTagValue(SemanticConventions.AttributeHttpUrl));
- Assert.Equal(query, activity.GetTagValue(SemanticConventions.AttributeUrlQuery));
- Assert.Equal(statusCode, activity.GetTagValue(SemanticConventions.AttributeHttpResponseStatusCode));
- Assert.Equal(statusCode, activity.GetTagValue(SemanticConventions.AttributeHttpStatusCode));
-
- if (statusCode == 503)
- {
- Assert.Equal(ActivityStatusCode.Error, activity.Status);
- }
- else
- {
- Assert.Equal(ActivityStatusCode.Unset, activity.Status);
- }
-
- // Instrumentation is not expected to set status description
- // as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode
- Assert.Null(activity.StatusDescription);
-
- if (recordException)
- {
- Assert.Single(activity.Events);
- Assert.Equal("exception", activity.Events.First().Name);
- }
-
- ValidateTagValue(activity, SemanticConventions.AttributeUserAgentOriginal, userAgent);
-
- activity.Dispose();
- }
- finally
- {
- Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", null);
- }
- }
-
- private static void ValidateTagValue(Activity activity, string attribute, string expectedValue)
- {
- if (string.IsNullOrEmpty(expectedValue))
- {
- Assert.Null(activity.GetTagValue(attribute));
- }
- else
- {
- Assert.Equal(expectedValue, activity.GetTagValue(attribute));
- }
- }
-
- public class TestCallbackMiddlewareImpl : CallbackMiddleware.CallbackMiddlewareImpl
- {
- private readonly int statusCode;
- private readonly string reasonPhrase;
-
- public TestCallbackMiddlewareImpl(int statusCode, string reasonPhrase)
- {
- this.statusCode = statusCode;
- this.reasonPhrase = reasonPhrase;
- }
-
- public override async Task ProcessAsync(HttpContext context)
- {
- context.Response.StatusCode = this.statusCode;
- context.Response.HttpContext.Features.Get().ReasonPhrase = this.reasonPhrase;
- await context.Response.WriteAsync("empty").ConfigureAwait(false);
-
- if (context.Request.Path.Value.EndsWith("exception"))
- {
- throw new Exception("exception description");
- }
-
- return false;
- }
- }
-}
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests_New.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests_New.cs
deleted file mode 100644
index 5c56ffb6719..00000000000
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests_New.cs
+++ /dev/null
@@ -1,190 +0,0 @@
-//
-// Copyright The OpenTelemetry Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-using System.Diagnostics;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Http.Features;
-using Microsoft.AspNetCore.Mvc.Testing;
-using Microsoft.AspNetCore.TestHost;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using OpenTelemetry.Trace;
-using TestApp.AspNetCore;
-using Xunit;
-
-namespace OpenTelemetry.Instrumentation.AspNetCore.Tests;
-
-public class IncomingRequestsCollectionsIsAccordingToTheSpecTests_New
- : IClassFixture>
-{
- private readonly WebApplicationFactory factory;
-
- public IncomingRequestsCollectionsIsAccordingToTheSpecTests_New(WebApplicationFactory factory)
- {
- this.factory = factory;
- }
-
- [Theory]
- [InlineData("/api/values", null, "user-agent", 200, null)]
- [InlineData("/api/values", "?query=1", null, 200, null)]
- [InlineData("/api/exception", null, null, 503, null)]
- [InlineData("/api/exception", null, null, 503, null, true)]
- public async Task SuccessfulTemplateControllerCallGeneratesASpan_New(
- string urlPath,
- string query,
- string userAgent,
- int statusCode,
- string reasonPhrase,
- bool recordException = false)
- {
- try
- {
- Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", "http");
-
- var exportedItems = new List();
-
- // Arrange
- using (var client = this.factory
- .WithWebHostBuilder(builder =>
- {
- builder.ConfigureTestServices((IServiceCollection services) =>
- {
- services.AddSingleton(new TestCallbackMiddlewareImpl(statusCode, reasonPhrase));
- services.AddOpenTelemetry()
- .WithTracing(builder => builder
- .AddAspNetCoreInstrumentation(options => options.RecordException = recordException)
- .AddInMemoryExporter(exportedItems));
- });
- builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
- })
- .CreateClient())
- {
- try
- {
- if (!string.IsNullOrEmpty(userAgent))
- {
- client.DefaultRequestHeaders.Add("User-Agent", userAgent);
- }
-
- // Act
- var path = urlPath;
- if (query != null)
- {
- path += query;
- }
-
- using var response = await client.GetAsync(path).ConfigureAwait(false);
- }
- catch (Exception)
- {
- // ignore errors
- }
-
- for (var i = 0; i < 10; i++)
- {
- if (exportedItems.Count == 1)
- {
- break;
- }
-
- // We need to let End callback execute as it is executed AFTER response was returned.
- // In unit tests environment there may be a lot of parallel unit tests executed, so
- // giving some breezing room for the End callback to complete
- await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
- }
- }
-
- Assert.Single(exportedItems);
- var activity = exportedItems[0];
-
- Assert.Equal(ActivityKind.Server, activity.Kind);
- Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeServerAddress));
- Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeHttpRequestMethod));
- Assert.Equal("1.1", activity.GetTagValue(SemanticConventions.AttributeNetworkProtocolVersion));
- Assert.Equal("http", activity.GetTagValue(SemanticConventions.AttributeUrlScheme));
- Assert.Equal(urlPath, activity.GetTagValue(SemanticConventions.AttributeUrlPath));
- Assert.Equal(query, activity.GetTagValue(SemanticConventions.AttributeUrlQuery));
- Assert.Equal(statusCode, activity.GetTagValue(SemanticConventions.AttributeHttpResponseStatusCode));
-
- if (statusCode == 503)
- {
- Assert.Equal(ActivityStatusCode.Error, activity.Status);
- Assert.Equal("System.Exception", activity.GetTagValue(SemanticConventions.AttributeErrorType));
- }
- else
- {
- Assert.Equal(ActivityStatusCode.Unset, activity.Status);
- }
-
- // Instrumentation is not expected to set status description
- // as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode
- Assert.Null(activity.StatusDescription);
-
- if (recordException)
- {
- Assert.Single(activity.Events);
- Assert.Equal("exception", activity.Events.First().Name);
- }
-
- ValidateTagValue(activity, SemanticConventions.AttributeUserAgentOriginal, userAgent);
-
- activity.Dispose();
- }
- finally
- {
- Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", null);
- }
- }
-
- private static void ValidateTagValue(Activity activity, string attribute, string expectedValue)
- {
- if (string.IsNullOrEmpty(expectedValue))
- {
- Assert.Null(activity.GetTagValue(attribute));
- }
- else
- {
- Assert.Equal(expectedValue, activity.GetTagValue(attribute));
- }
- }
-
- public class TestCallbackMiddlewareImpl : CallbackMiddleware.CallbackMiddlewareImpl
- {
- private readonly int statusCode;
- private readonly string reasonPhrase;
-
- public TestCallbackMiddlewareImpl(int statusCode, string reasonPhrase)
- {
- this.statusCode = statusCode;
- this.reasonPhrase = reasonPhrase;
- }
-
- public override async Task ProcessAsync(HttpContext context)
- {
- context.Response.StatusCode = this.statusCode;
- context.Response.HttpContext.Features.Get().ReasonPhrase = this.reasonPhrase;
- await context.Response.WriteAsync("empty").ConfigureAwait(false);
-
- if (context.Request.Path.Value.EndsWith("exception"))
- {
- throw new Exception("exception description");
- }
-
- return false;
- }
- }
-}
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs
index 0185df65271..b5769f4cb48 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs
@@ -26,8 +26,10 @@
#if NET8_0_OR_GREATER
using Microsoft.AspNetCore.RateLimiting;
#endif
-using Microsoft.Extensions.Configuration;
+#if NET8_0_OR_GREATER
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+#endif
using Microsoft.Extensions.Logging;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
@@ -35,72 +37,67 @@
namespace OpenTelemetry.Instrumentation.AspNetCore.Tests;
-public class MetricTests
- : IClassFixture>, IDisposable
+public class MetricTests(WebApplicationFactory factory)
+ : IClassFixture>, IDisposable
{
- public const string SemanticConventionOptInKeyName = "OTEL_SEMCONV_STABILITY_OPT_IN";
-
private const int StandardTagsCount = 6;
- private readonly WebApplicationFactory factory;
+ private readonly WebApplicationFactory factory = factory;
private MeterProvider meterProvider;
- public MetricTests(WebApplicationFactory factory)
- {
- this.factory = factory;
- }
-
[Fact]
public void AddAspNetCoreInstrumentation_BadArgs()
{
MeterProviderBuilder builder = null;
- Assert.Throws(() => builder.AddAspNetCoreInstrumentation());
+ Assert.Throws(builder.AddAspNetCoreInstrumentation);
}
#if NET8_0_OR_GREATER
[Fact]
public async Task ValidateNet8MetricsAsync()
{
- var metricItems = new List();
-
+ var exportedItems = new List();
this.meterProvider = Sdk.CreateMeterProviderBuilder()
- .AddAspNetCoreInstrumentation()
- .AddInMemoryExporter(metricItems)
- .Build();
+ .AddAspNetCoreInstrumentation()
+ .AddInMemoryExporter(exportedItems)
+ .Build();
var builder = WebApplication.CreateBuilder();
- builder.Logging.ClearProviders();
+ builder.WebHost.UseUrls("http://*:0");
var app = builder.Build();
app.MapGet("/", () => "Hello");
_ = app.RunAsync();
+ var url = app.Urls.ToArray()[0];
+ var portNumber = url.Substring(url.LastIndexOf(':') + 1);
+
using var client = new HttpClient();
- var res = await client.GetStringAsync("http://localhost:5000/").ConfigureAwait(false);
- Assert.NotNull(res);
+ var res = await client.GetAsync($"http://localhost:{portNumber}/");
+ Assert.True(res.IsSuccessStatusCode);
// We need to let metric callback execute as it is executed AFTER response was returned.
// In unit tests environment there may be a lot of parallel unit tests executed, so
// giving some breezing room for the callbacks to complete
- await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
+ await Task.Delay(TimeSpan.FromSeconds(1));
this.meterProvider.Dispose();
- var requestDurationMetric = metricItems
+ var requestDurationMetric = exportedItems
.Count(item => item.Name == "http.server.request.duration");
- var activeRequestsMetric = metricItems.
+ var activeRequestsMetric = exportedItems.
Count(item => item.Name == "http.server.active_requests");
- var routeMatchingMetric = metricItems.
+ var routeMatchingMetric = exportedItems.
Count(item => item.Name == "aspnetcore.routing.match_attempts");
- var kestrelActiveConnectionsMetric = metricItems.
- Count(item => item.Name == "kestrel.active_connections");
+ var kestrelActiveConnectionsMetric = exportedItems.
+ Count(item => item.Name == "kestrel.active_connections");
- var kestrelQueuedConnectionMetric = metricItems.
- Count(item => item.Name == "kestrel.queued_connections");
+ var kestrelQueuedConnectionMetric = exportedItems.
+ Count(item => item.Name == "kestrel.queued_connections");
Assert.Equal(1, requestDurationMetric);
Assert.Equal(1, activeRequestsMetric);
@@ -121,22 +118,28 @@ public async Task ValidateNet8MetricsAsync()
[Fact]
public async Task ValidateNet8RateLimitingMetricsAsync()
{
- var metricItems = new List();
+ var exportedItems = new List();
- this.meterProvider = Sdk.CreateMeterProviderBuilder()
- .AddAspNetCoreInstrumentation()
- .AddInMemoryExporter(metricItems)
- .Build();
+ void ConfigureTestServices(IServiceCollection services)
+ {
+ this.meterProvider = Sdk.CreateMeterProviderBuilder()
+ .AddAspNetCoreInstrumentation()
+ .AddInMemoryExporter(exportedItems)
+ .Build();
+
+ services.AddRateLimiter(_ => _
+ .AddFixedWindowLimiter(policyName: "fixed", options =>
+ {
+ options.PermitLimit = 4;
+ options.Window = TimeSpan.FromSeconds(12);
+ options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
+ options.QueueLimit = 2;
+ }));
+ }
var builder = WebApplication.CreateBuilder();
- builder.Services.AddRateLimiter(_ => _
- .AddFixedWindowLimiter(policyName: "fixed", options =>
- {
- options.PermitLimit = 4;
- options.Window = TimeSpan.FromSeconds(12);
- options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
- options.QueueLimit = 2;
- }));
+ builder.WebHost.UseUrls("http://*:0");
+ ConfigureTestServices(builder.Services);
builder.Logging.ClearProviders();
var app = builder.Build();
@@ -150,30 +153,33 @@ public async Task ValidateNet8RateLimitingMetricsAsync()
_ = app.RunAsync();
+ var url = app.Urls.ToArray()[0];
+ var portNumber = url.Substring(url.LastIndexOf(':') + 1);
+
using var client = new HttpClient();
- var res = await client.GetStringAsync("http://localhost:5000/").ConfigureAwait(false);
+ var res = await client.GetAsync($"http://localhost:{portNumber}/");
Assert.NotNull(res);
// We need to let metric callback execute as it is executed AFTER response was returned.
// In unit tests environment there may be a lot of parallel unit tests executed, so
// giving some breezing room for the callbacks to complete
- await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
+ await Task.Delay(TimeSpan.FromSeconds(1));
this.meterProvider.Dispose();
- var activeRequestleasesMetric = metricItems
+ var activeRequestLeasesMetric = exportedItems
.Where(item => item.Name == "aspnetcore.rate_limiting.active_request_leases")
.ToArray();
- var requestLeaseDurationMetric = metricItems.
+ var requestLeaseDurationMetric = exportedItems.
Where(item => item.Name == "aspnetcore.rate_limiting.request_lease.duration")
.ToArray();
- var limitingRequestsMetric = metricItems.
+ var limitingRequestsMetric = exportedItems.
Where(item => item.Name == "aspnetcore.rate_limiting.requests")
.ToArray();
- Assert.Single(activeRequestleasesMetric);
+ Assert.Single(activeRequestLeasesMetric);
Assert.Single(requestLeaseDurationMetric);
Assert.Single(limitingRequestsMetric);
@@ -188,16 +194,11 @@ public async Task ValidateNet8RateLimitingMetricsAsync()
[Theory]
[InlineData("/api/values/2", "api/Values/{id}", null, 200)]
[InlineData("/api/Error", "api/Error", "System.Exception", 500)]
- public async Task RequestMetricIsCaptured_New(string api, string expectedRoute, string expectedErrorType, int expectedStatusCode)
+ public async Task RequestMetricIsCaptured(string api, string expectedRoute, string expectedErrorType, int expectedStatusCode)
{
- var configuration = new ConfigurationBuilder()
- .AddInMemoryCollection(new Dictionary { [SemanticConventionOptInKeyName] = "http" })
- .Build();
-
var metricItems = new List();
this.meterProvider = Sdk.CreateMeterProviderBuilder()
- .ConfigureServices(services => services.AddSingleton(configuration))
.AddAspNetCoreInstrumentation()
.AddInMemoryExporter(metricItems)
.Build();
@@ -211,7 +212,7 @@ public async Task RequestMetricIsCaptured_New(string api, string expectedRoute,
{
try
{
- using var response = await client.GetAsync(api).ConfigureAwait(false);
+ using var response = await client.GetAsync(api);
response.EnsureSuccessStatusCode();
}
catch
@@ -223,7 +224,7 @@ public async Task RequestMetricIsCaptured_New(string api, string expectedRoute,
// We need to let End callback execute as it is executed AFTER response was returned.
// In unit tests environment there may be a lot of parallel unit tests executed, so
// giving some breezing room for the End callback to complete
- await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
+ await Task.Delay(TimeSpan.FromSeconds(1));
this.meterProvider.Dispose();
@@ -237,12 +238,12 @@ public async Task RequestMetricIsCaptured_New(string api, string expectedRoute,
var metricPoints = GetMetricPoints(metric);
Assert.Single(metricPoints);
- AssertMetricPoints_New(
+ AssertMetricPoints(
metricPoints: metricPoints,
expectedRoutes: new List { expectedRoute },
expectedErrorType,
expectedStatusCode,
- expectedTagsCount: expectedErrorType == null ? 6 : 7);
+ expectedTagsCount: expectedErrorType == null ? 5 : 6);
}
[Theory]
@@ -259,14 +260,9 @@ public async Task RequestMetricIsCaptured_New(string api, string expectedRoute,
[InlineData("CUSTOM", "_OTHER")]
public async Task HttpRequestMethodIsCapturedAsPerSpec(string originalMethod, string expectedMethod)
{
- var configuration = new ConfigurationBuilder()
- .AddInMemoryCollection(new Dictionary { [SemanticConventionOptInKeyName] = "http" })
- .Build();
-
var metricItems = new List();
this.meterProvider = Sdk.CreateMeterProviderBuilder()
- .ConfigureServices(services => services.AddSingleton(configuration))
.AddAspNetCoreInstrumentation()
.AddInMemoryExporter(metricItems)
.Build();
@@ -283,7 +279,7 @@ public async Task HttpRequestMethodIsCapturedAsPerSpec(string originalMethod, st
try
{
- using var response = await client.SendAsync(message).ConfigureAwait(false);
+ using var response = await client.SendAsync(message);
}
catch
{
@@ -293,7 +289,7 @@ public async Task HttpRequestMethodIsCapturedAsPerSpec(string originalMethod, st
// We need to let End callback execute as it is executed AFTER response was returned.
// In unit tests environment there may be a lot of parallel unit tests executed, so
// giving some breezing room for the End callback to complete
- await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
+ await Task.Delay(TimeSpan.FromSeconds(1));
this.meterProvider.Dispose();
@@ -321,129 +317,6 @@ public async Task HttpRequestMethodIsCapturedAsPerSpec(string originalMethod, st
Assert.DoesNotContain(attributes, t => t.Key == SemanticConventions.AttributeHttpRequestMethodOriginal);
}
-#if !NET8_0_OR_GREATER
- [Fact]
- public async Task RequestMetricIsCaptured_Old()
- {
- var configuration = new ConfigurationBuilder()
- .AddInMemoryCollection(new Dictionary { [SemanticConventionOptInKeyName] = null })
- .Build();
-
- var metricItems = new List();
-
- this.meterProvider = Sdk.CreateMeterProviderBuilder()
- .ConfigureServices(services => services.AddSingleton(configuration))
- .AddAspNetCoreInstrumentation()
- .AddInMemoryExporter(metricItems)
- .Build();
-
- using (var client = this.factory
- .WithWebHostBuilder(builder =>
- {
- builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
- })
- .CreateClient())
- {
- using var response1 = await client.GetAsync("/api/values").ConfigureAwait(false);
- using var response2 = await client.GetAsync("/api/values/2").ConfigureAwait(false);
-
- response1.EnsureSuccessStatusCode();
- response2.EnsureSuccessStatusCode();
- }
-
- // We need to let End callback execute as it is executed AFTER response was returned.
- // In unit tests environment there may be a lot of parallel unit tests executed, so
- // giving some breezing room for the End callback to complete
- await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
-
- this.meterProvider.Dispose();
-
- var requestMetrics = metricItems
- .Where(item => item.Name == "http.server.duration")
- .ToArray();
-
- var metric = Assert.Single(requestMetrics);
- Assert.Equal("ms", metric.Unit);
- var metricPoints = GetMetricPoints(metric);
- Assert.Equal(2, metricPoints.Count);
-
- AssertMetricPoints_Old(
- metricPoints: metricPoints,
- expectedRoutes: new List { "api/Values", "api/Values/{id}" },
- expectedTagsCount: 6);
- }
-
- [Fact]
- public async Task RequestMetricIsCaptured_Dup()
- {
- var configuration = new ConfigurationBuilder()
- .AddInMemoryCollection(new Dictionary { [SemanticConventionOptInKeyName] = "http/dup" })
- .Build();
-
- var metricItems = new List();
-
- this.meterProvider = Sdk.CreateMeterProviderBuilder()
- .ConfigureServices(services => services.AddSingleton(configuration))
- .AddAspNetCoreInstrumentation()
- .AddInMemoryExporter(metricItems)
- .Build();
-
- using (var client = this.factory
- .WithWebHostBuilder(builder =>
- {
- builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
- })
- .CreateClient())
- {
- using var response1 = await client.GetAsync("/api/values").ConfigureAwait(false);
- using var response2 = await client.GetAsync("/api/values/2").ConfigureAwait(false);
-
- response1.EnsureSuccessStatusCode();
- response2.EnsureSuccessStatusCode();
- }
-
- // We need to let End callback execute as it is executed AFTER response was returned.
- // In unit tests environment there may be a lot of parallel unit tests executed, so
- // giving some breezing room for the End callback to complete
- await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
-
- this.meterProvider.Dispose();
-
- // Validate Old Semantic Convention
- var requestMetrics = metricItems
- .Where(item => item.Name == "http.server.duration")
- .ToArray();
-
- var metric = Assert.Single(requestMetrics);
- Assert.Equal("ms", metric.Unit);
- var metricPoints = GetMetricPoints(metric);
- Assert.Equal(2, metricPoints.Count);
-
- AssertMetricPoints_Old(
- metricPoints: metricPoints,
- expectedRoutes: new List { "api/Values", "api/Values/{id}" },
- expectedTagsCount: 6);
-
- // Validate New Semantic Convention
- requestMetrics = metricItems
- .Where(item => item.Name == "http.server.request.duration")
- .ToArray();
-
- metric = Assert.Single(requestMetrics);
-
- Assert.Equal("s", metric.Unit);
- metricPoints = GetMetricPoints(metric);
- Assert.Equal(2, metricPoints.Count);
-
- AssertMetricPoints_New(
- metricPoints: metricPoints,
- expectedRoutes: new List { "api/Values", "api/Values/{id}" },
- null,
- 200,
- expectedTagsCount: 6);
- }
-#endif
-
public void Dispose()
{
this.meterProvider?.Dispose();
@@ -463,7 +336,7 @@ private static List GetMetricPoints(Metric metric)
return metricPoints;
}
- private static void AssertMetricPoints_New(
+ private static void AssertMetricPoints(
List metricPoints,
List expectedRoutes,
string expectedErrorType,
@@ -488,39 +361,7 @@ private static void AssertMetricPoints_New(
if (metricPoint.HasValue)
{
- AssertMetricPoint_New(metricPoint.Value, expectedStatusCode, expectedRoute, expectedErrorType, expectedTagsCount);
- }
- else
- {
- Assert.Fail($"A metric for route '{expectedRoute}' was not found");
- }
- }
- }
-
- private static void AssertMetricPoints_Old(
- List metricPoints,
- List expectedRoutes,
- int expectedTagsCount)
- {
- // Assert that one MetricPoint exists for each ExpectedRoute
- foreach (var expectedRoute in expectedRoutes)
- {
- MetricPoint? metricPoint = null;
-
- foreach (var mp in metricPoints)
- {
- foreach (var tag in mp.Tags)
- {
- if (tag.Key == SemanticConventions.AttributeHttpRoute && tag.Value.ToString() == expectedRoute)
- {
- metricPoint = mp;
- }
- }
- }
-
- if (metricPoint.HasValue)
- {
- AssertMetricPoint_Old(metricPoint.Value, expectedRoute, expectedTagsCount);
+ AssertMetricPoint(metricPoint.Value, expectedStatusCode, expectedRoute, expectedErrorType, expectedTagsCount);
}
else
{
@@ -529,7 +370,7 @@ private static void AssertMetricPoints_Old(
}
}
- private static KeyValuePair[] AssertMetricPoint_New(
+ private static void AssertMetricPoint(
MetricPoint metricPoint,
int expectedStatusCode,
string expectedRoute,
@@ -565,13 +406,8 @@ private static KeyValuePair[] AssertMetricPoint_New(
if (expectedErrorType != null)
{
-#if NET8_0_OR_GREATER
- // Expected to change in next release
- // https://github.com/dotnet/aspnetcore/issues/51029
- var errorType = new KeyValuePair("exception.type", expectedErrorType);
-#else
var errorType = new KeyValuePair(SemanticConventions.AttributeErrorType, expectedErrorType);
-#endif
+
Assert.Contains(errorType, attributes);
}
@@ -592,56 +428,5 @@ private static KeyValuePair[] AssertMetricPoint_New(
Enumerable.SequenceEqual(expectedHistogramBoundsNew, histogramBounds);
Assert.True(histogramBoundsMatchCorrectly);
-
- return attributes;
- }
-
- private static KeyValuePair[] AssertMetricPoint_Old(
- MetricPoint metricPoint,
- string expectedRoute = "api/Values",
- int expectedTagsCount = StandardTagsCount)
- {
- var count = metricPoint.GetHistogramCount();
- var sum = metricPoint.GetHistogramSum();
-
- Assert.Equal(1L, count);
- Assert.True(sum > 0);
-
- var attributes = new KeyValuePair[metricPoint.Tags.Count];
- int i = 0;
- foreach (var tag in metricPoint.Tags)
- {
- attributes[i++] = tag;
- }
-
- // Inspect Attributes
- Assert.Equal(expectedTagsCount, attributes.Length);
-
- var method = new KeyValuePair(SemanticConventions.AttributeHttpMethod, "GET");
- var scheme = new KeyValuePair(SemanticConventions.AttributeHttpScheme, "http");
- var statusCode = new KeyValuePair(SemanticConventions.AttributeHttpStatusCode, 200);
- var flavor = new KeyValuePair(SemanticConventions.AttributeHttpFlavor, "1.1");
- var host = new KeyValuePair(SemanticConventions.AttributeNetHostName, "localhost");
- var route = new KeyValuePair(SemanticConventions.AttributeHttpRoute, expectedRoute);
- Assert.Contains(method, attributes);
- Assert.Contains(scheme, attributes);
- Assert.Contains(statusCode, attributes);
- Assert.Contains(flavor, attributes);
- Assert.Contains(host, attributes);
- Assert.Contains(route, attributes);
-
- // Inspect Histogram Bounds
- var histogramBuckets = metricPoint.GetHistogramBuckets();
- var histogramBounds = new List();
- foreach (var t in histogramBuckets)
- {
- histogramBounds.Add(t.ExplicitBound);
- }
-
- Assert.Equal(
- expected: new List { 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000, double.PositiveInfinity },
- actual: histogramBounds);
-
- return attributes;
}
}
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.net6.0.md b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.net6.0.md
index ac9ff0d5117..c12dc40dadc 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.net6.0.md
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.net6.0.md
@@ -1,35 +1,35 @@
# Test results for ASP.NET Core 6
-| Span http.route | Metric http.route | App | Test Name |
-| - | - | - | - |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Root path](#conventionalrouting-root-path) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Non-default action with route parameter and query string](#conventionalrouting-non-default-action-with-route-parameter-and-query-string) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Non-default action with query string](#conventionalrouting-non-default-action-with-query-string) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Not Found (404)](#conventionalrouting-not-found-404) |
-| :broken_heart: | :green_heart: | ConventionalRouting | [Route template with parameter constraint](#conventionalrouting-route-template-with-parameter-constraint) |
-| :broken_heart: | :green_heart: | ConventionalRouting | [Path that does not match parameter constraint](#conventionalrouting-path-that-does-not-match-parameter-constraint) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Area using area:exists, default controller/action](#conventionalrouting-area-using-areaexists-default-controlleraction) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Area using area:exists, non-default action](#conventionalrouting-area-using-areaexists-non-default-action) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Area w/o area:exists, default controller/action](#conventionalrouting-area-wo-areaexists-default-controlleraction) |
-| :green_heart: | :green_heart: | AttributeRouting | [Default action](#attributerouting-default-action) |
-| :green_heart: | :green_heart: | AttributeRouting | [Action without parameter](#attributerouting-action-without-parameter) |
-| :green_heart: | :green_heart: | AttributeRouting | [Action with parameter](#attributerouting-action-with-parameter) |
-| :green_heart: | :green_heart: | AttributeRouting | [Action with parameter before action name in template](#attributerouting-action-with-parameter-before-action-name-in-template) |
-| :green_heart: | :green_heart: | AttributeRouting | [Action invoked resulting in 400 Bad Request](#attributerouting-action-invoked-resulting-in-400-bad-request) |
-| :broken_heart: | :broken_heart: | RazorPages | [Root path](#razorpages-root-path) |
-| :broken_heart: | :broken_heart: | RazorPages | [Index page](#razorpages-index-page) |
-| :broken_heart: | :broken_heart: | RazorPages | [Throws exception](#razorpages-throws-exception) |
-| :green_heart: | :green_heart: | RazorPages | [Static content](#razorpages-static-content) |
-| :broken_heart: | :green_heart: | MinimalApi | [Action without parameter](#minimalapi-action-without-parameter) |
-| :broken_heart: | :green_heart: | MinimalApi | [Action with parameter](#minimalapi-action-with-parameter) |
+| http.route | App | Test Name |
+| - | - | - |
+| :broken_heart: | ConventionalRouting | [Root path](#conventionalrouting-root-path) |
+| :broken_heart: | ConventionalRouting | [Non-default action with route parameter and query string](#conventionalrouting-non-default-action-with-route-parameter-and-query-string) |
+| :broken_heart: | ConventionalRouting | [Non-default action with query string](#conventionalrouting-non-default-action-with-query-string) |
+| :green_heart: | ConventionalRouting | [Not Found (404)](#conventionalrouting-not-found-404) |
+| :green_heart: | ConventionalRouting | [Route template with parameter constraint](#conventionalrouting-route-template-with-parameter-constraint) |
+| :green_heart: | ConventionalRouting | [Path that does not match parameter constraint](#conventionalrouting-path-that-does-not-match-parameter-constraint) |
+| :broken_heart: | ConventionalRouting | [Area using area:exists, default controller/action](#conventionalrouting-area-using-areaexists-default-controlleraction) |
+| :broken_heart: | ConventionalRouting | [Area using area:exists, non-default action](#conventionalrouting-area-using-areaexists-non-default-action) |
+| :broken_heart: | ConventionalRouting | [Area w/o area:exists, default controller/action](#conventionalrouting-area-wo-areaexists-default-controlleraction) |
+| :green_heart: | AttributeRouting | [Default action](#attributerouting-default-action) |
+| :green_heart: | AttributeRouting | [Action without parameter](#attributerouting-action-without-parameter) |
+| :green_heart: | AttributeRouting | [Action with parameter](#attributerouting-action-with-parameter) |
+| :green_heart: | AttributeRouting | [Action with parameter before action name in template](#attributerouting-action-with-parameter-before-action-name-in-template) |
+| :green_heart: | AttributeRouting | [Action invoked resulting in 400 Bad Request](#attributerouting-action-invoked-resulting-in-400-bad-request) |
+| :broken_heart: | RazorPages | [Root path](#razorpages-root-path) |
+| :broken_heart: | RazorPages | [Index page](#razorpages-index-page) |
+| :broken_heart: | RazorPages | [Throws exception](#razorpages-throws-exception) |
+| :green_heart: | RazorPages | [Static content](#razorpages-static-content) |
+| :green_heart: | MinimalApi | [Action without parameter](#minimalapi-action-without-parameter) |
+| :green_heart: | MinimalApi | [Action with parameter](#minimalapi-action-with-parameter) |
## ConventionalRouting: Root path
```json
{
"IdealHttpRoute": "ConventionalRoute/Default/{id?}",
- "ActivityDisplayName": "/",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET {controller=ConventionalRoute}/{action=Default}/{id?}",
+ "ActivityHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"MetricHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -58,8 +58,8 @@
```json
{
"IdealHttpRoute": "ConventionalRoute/ActionWithStringParameter/{id?}",
- "ActivityDisplayName": "/ConventionalRoute/ActionWithStringParameter/2",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET {controller=ConventionalRoute}/{action=Default}/{id?}",
+ "ActivityHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"MetricHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -92,8 +92,8 @@
```json
{
"IdealHttpRoute": "ConventionalRoute/ActionWithStringParameter/{id?}",
- "ActivityDisplayName": "/ConventionalRoute/ActionWithStringParameter",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET {controller=ConventionalRoute}/{action=Default}/{id?}",
+ "ActivityHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"MetricHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -125,7 +125,7 @@
```json
{
"IdealHttpRoute": "",
- "ActivityDisplayName": "/ConventionalRoute/NotFound",
+ "ActivityDisplayName": "GET",
"ActivityHttpRoute": "",
"MetricHttpRoute": "",
"RouteInfo": {
@@ -144,8 +144,8 @@
```json
{
"IdealHttpRoute": "SomePath/{id}/{num:int}",
- "ActivityDisplayName": "/SomePath/SomeString/2",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET SomePath/{id}/{num:int}",
+ "ActivityHttpRoute": "SomePath/{id}/{num:int}",
"MetricHttpRoute": "SomePath/{id}/{num:int}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -179,7 +179,7 @@
```json
{
"IdealHttpRoute": "",
- "ActivityDisplayName": "/SomePath/SomeString/NotAnInt",
+ "ActivityDisplayName": "GET",
"ActivityHttpRoute": "",
"MetricHttpRoute": "",
"RouteInfo": {
@@ -198,8 +198,8 @@
```json
{
"IdealHttpRoute": "{area:exists}/ControllerForMyArea/Default/{id?}",
- "ActivityDisplayName": "/MyArea",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET {area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
+ "ActivityHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
"MetricHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -229,8 +229,8 @@
```json
{
"IdealHttpRoute": "{area:exists}/ControllerForMyArea/NonDefault/{id?}",
- "ActivityDisplayName": "/MyArea/ControllerForMyArea/NonDefault",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET {area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
+ "ActivityHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
"MetricHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -260,8 +260,8 @@
```json
{
"IdealHttpRoute": "SomePrefix/AnotherArea/Index/{id?}",
- "ActivityDisplayName": "/SomePrefix",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET SomePrefix/{controller=AnotherArea}/{action=Index}/{id?}",
+ "ActivityHttpRoute": "SomePrefix/{controller=AnotherArea}/{action=Index}/{id?}",
"MetricHttpRoute": "SomePrefix/{controller=AnotherArea}/{action=Index}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -291,7 +291,7 @@
```json
{
"IdealHttpRoute": "AttributeRoute",
- "ActivityDisplayName": "AttributeRoute",
+ "ActivityDisplayName": "GET AttributeRoute",
"ActivityHttpRoute": "AttributeRoute",
"MetricHttpRoute": "AttributeRoute",
"RouteInfo": {
@@ -321,7 +321,7 @@
```json
{
"IdealHttpRoute": "AttributeRoute/Get",
- "ActivityDisplayName": "AttributeRoute/Get",
+ "ActivityDisplayName": "GET AttributeRoute/Get",
"ActivityHttpRoute": "AttributeRoute/Get",
"MetricHttpRoute": "AttributeRoute/Get",
"RouteInfo": {
@@ -351,7 +351,7 @@
```json
{
"IdealHttpRoute": "AttributeRoute/Get/{id}",
- "ActivityDisplayName": "AttributeRoute/Get/{id}",
+ "ActivityDisplayName": "GET AttributeRoute/Get/{id}",
"ActivityHttpRoute": "AttributeRoute/Get/{id}",
"MetricHttpRoute": "AttributeRoute/Get/{id}",
"RouteInfo": {
@@ -384,7 +384,7 @@
```json
{
"IdealHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
- "ActivityDisplayName": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
+ "ActivityDisplayName": "GET AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"ActivityHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"MetricHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"RouteInfo": {
@@ -417,7 +417,7 @@
```json
{
"IdealHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
- "ActivityDisplayName": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
+ "ActivityDisplayName": "GET AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"ActivityHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"MetricHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"RouteInfo": {
@@ -450,7 +450,7 @@
```json
{
"IdealHttpRoute": "/Index",
- "ActivityDisplayName": "/",
+ "ActivityDisplayName": "GET",
"ActivityHttpRoute": "",
"MetricHttpRoute": "",
"RouteInfo": {
@@ -479,7 +479,7 @@
```json
{
"IdealHttpRoute": "/Index",
- "ActivityDisplayName": "Index",
+ "ActivityDisplayName": "GET Index",
"ActivityHttpRoute": "Index",
"MetricHttpRoute": "Index",
"RouteInfo": {
@@ -508,7 +508,7 @@
```json
{
"IdealHttpRoute": "/PageThatThrowsException",
- "ActivityDisplayName": "PageThatThrowsException",
+ "ActivityDisplayName": "GET PageThatThrowsException",
"ActivityHttpRoute": "PageThatThrowsException",
"MetricHttpRoute": "PageThatThrowsException",
"RouteInfo": {
@@ -537,7 +537,7 @@
```json
{
"IdealHttpRoute": "",
- "ActivityDisplayName": "/js/site.js",
+ "ActivityDisplayName": "GET",
"ActivityHttpRoute": "",
"MetricHttpRoute": "",
"RouteInfo": {
@@ -556,8 +556,8 @@
```json
{
"IdealHttpRoute": "/MinimalApi",
- "ActivityDisplayName": "/MinimalApi",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET /MinimalApi",
+ "ActivityHttpRoute": "/MinimalApi",
"MetricHttpRoute": "/MinimalApi",
"RouteInfo": {
"HttpMethod": "GET",
@@ -575,8 +575,8 @@
```json
{
"IdealHttpRoute": "/MinimalApi/{id}",
- "ActivityDisplayName": "/MinimalApi/123",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET /MinimalApi/{id}",
+ "ActivityHttpRoute": "/MinimalApi/{id}",
"MetricHttpRoute": "/MinimalApi/{id}",
"RouteInfo": {
"HttpMethod": "GET",
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.net7.0.md b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.net7.0.md
index f93e5de8ea8..ec252654a2a 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.net7.0.md
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.net7.0.md
@@ -1,37 +1,37 @@
# Test results for ASP.NET Core 7
-| Span http.route | Metric http.route | App | Test Name |
-| - | - | - | - |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Root path](#conventionalrouting-root-path) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Non-default action with route parameter and query string](#conventionalrouting-non-default-action-with-route-parameter-and-query-string) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Non-default action with query string](#conventionalrouting-non-default-action-with-query-string) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Not Found (404)](#conventionalrouting-not-found-404) |
-| :broken_heart: | :green_heart: | ConventionalRouting | [Route template with parameter constraint](#conventionalrouting-route-template-with-parameter-constraint) |
-| :broken_heart: | :green_heart: | ConventionalRouting | [Path that does not match parameter constraint](#conventionalrouting-path-that-does-not-match-parameter-constraint) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Area using area:exists, default controller/action](#conventionalrouting-area-using-areaexists-default-controlleraction) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Area using area:exists, non-default action](#conventionalrouting-area-using-areaexists-non-default-action) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Area w/o area:exists, default controller/action](#conventionalrouting-area-wo-areaexists-default-controlleraction) |
-| :green_heart: | :green_heart: | AttributeRouting | [Default action](#attributerouting-default-action) |
-| :green_heart: | :green_heart: | AttributeRouting | [Action without parameter](#attributerouting-action-without-parameter) |
-| :green_heart: | :green_heart: | AttributeRouting | [Action with parameter](#attributerouting-action-with-parameter) |
-| :green_heart: | :green_heart: | AttributeRouting | [Action with parameter before action name in template](#attributerouting-action-with-parameter-before-action-name-in-template) |
-| :green_heart: | :green_heart: | AttributeRouting | [Action invoked resulting in 400 Bad Request](#attributerouting-action-invoked-resulting-in-400-bad-request) |
-| :broken_heart: | :broken_heart: | RazorPages | [Root path](#razorpages-root-path) |
-| :broken_heart: | :broken_heart: | RazorPages | [Index page](#razorpages-index-page) |
-| :broken_heart: | :broken_heart: | RazorPages | [Throws exception](#razorpages-throws-exception) |
-| :green_heart: | :green_heart: | RazorPages | [Static content](#razorpages-static-content) |
-| :broken_heart: | :green_heart: | MinimalApi | [Action without parameter](#minimalapi-action-without-parameter) |
-| :broken_heart: | :green_heart: | MinimalApi | [Action with parameter](#minimalapi-action-with-parameter) |
-| :broken_heart: | :green_heart: | MinimalApi | [Action without parameter (MapGroup)](#minimalapi-action-without-parameter-mapgroup) |
-| :broken_heart: | :green_heart: | MinimalApi | [Action with parameter (MapGroup)](#minimalapi-action-with-parameter-mapgroup) |
+| http.route | App | Test Name |
+| - | - | - |
+| :broken_heart: | ConventionalRouting | [Root path](#conventionalrouting-root-path) |
+| :broken_heart: | ConventionalRouting | [Non-default action with route parameter and query string](#conventionalrouting-non-default-action-with-route-parameter-and-query-string) |
+| :broken_heart: | ConventionalRouting | [Non-default action with query string](#conventionalrouting-non-default-action-with-query-string) |
+| :green_heart: | ConventionalRouting | [Not Found (404)](#conventionalrouting-not-found-404) |
+| :green_heart: | ConventionalRouting | [Route template with parameter constraint](#conventionalrouting-route-template-with-parameter-constraint) |
+| :green_heart: | ConventionalRouting | [Path that does not match parameter constraint](#conventionalrouting-path-that-does-not-match-parameter-constraint) |
+| :broken_heart: | ConventionalRouting | [Area using area:exists, default controller/action](#conventionalrouting-area-using-areaexists-default-controlleraction) |
+| :broken_heart: | ConventionalRouting | [Area using area:exists, non-default action](#conventionalrouting-area-using-areaexists-non-default-action) |
+| :broken_heart: | ConventionalRouting | [Area w/o area:exists, default controller/action](#conventionalrouting-area-wo-areaexists-default-controlleraction) |
+| :green_heart: | AttributeRouting | [Default action](#attributerouting-default-action) |
+| :green_heart: | AttributeRouting | [Action without parameter](#attributerouting-action-without-parameter) |
+| :green_heart: | AttributeRouting | [Action with parameter](#attributerouting-action-with-parameter) |
+| :green_heart: | AttributeRouting | [Action with parameter before action name in template](#attributerouting-action-with-parameter-before-action-name-in-template) |
+| :green_heart: | AttributeRouting | [Action invoked resulting in 400 Bad Request](#attributerouting-action-invoked-resulting-in-400-bad-request) |
+| :broken_heart: | RazorPages | [Root path](#razorpages-root-path) |
+| :broken_heart: | RazorPages | [Index page](#razorpages-index-page) |
+| :broken_heart: | RazorPages | [Throws exception](#razorpages-throws-exception) |
+| :green_heart: | RazorPages | [Static content](#razorpages-static-content) |
+| :green_heart: | MinimalApi | [Action without parameter](#minimalapi-action-without-parameter) |
+| :green_heart: | MinimalApi | [Action with parameter](#minimalapi-action-with-parameter) |
+| :green_heart: | MinimalApi | [Action without parameter (MapGroup)](#minimalapi-action-without-parameter-mapgroup) |
+| :green_heart: | MinimalApi | [Action with parameter (MapGroup)](#minimalapi-action-with-parameter-mapgroup) |
## ConventionalRouting: Root path
```json
{
"IdealHttpRoute": "ConventionalRoute/Default/{id?}",
- "ActivityDisplayName": "/",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET {controller=ConventionalRoute}/{action=Default}/{id?}",
+ "ActivityHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"MetricHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -60,8 +60,8 @@
```json
{
"IdealHttpRoute": "ConventionalRoute/ActionWithStringParameter/{id?}",
- "ActivityDisplayName": "/ConventionalRoute/ActionWithStringParameter/2",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET {controller=ConventionalRoute}/{action=Default}/{id?}",
+ "ActivityHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"MetricHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -94,8 +94,8 @@
```json
{
"IdealHttpRoute": "ConventionalRoute/ActionWithStringParameter/{id?}",
- "ActivityDisplayName": "/ConventionalRoute/ActionWithStringParameter",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET {controller=ConventionalRoute}/{action=Default}/{id?}",
+ "ActivityHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"MetricHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -127,7 +127,7 @@
```json
{
"IdealHttpRoute": "",
- "ActivityDisplayName": "/ConventionalRoute/NotFound",
+ "ActivityDisplayName": "GET",
"ActivityHttpRoute": "",
"MetricHttpRoute": "",
"RouteInfo": {
@@ -146,8 +146,8 @@
```json
{
"IdealHttpRoute": "SomePath/{id}/{num:int}",
- "ActivityDisplayName": "/SomePath/SomeString/2",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET SomePath/{id}/{num:int}",
+ "ActivityHttpRoute": "SomePath/{id}/{num:int}",
"MetricHttpRoute": "SomePath/{id}/{num:int}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -181,7 +181,7 @@
```json
{
"IdealHttpRoute": "",
- "ActivityDisplayName": "/SomePath/SomeString/NotAnInt",
+ "ActivityDisplayName": "GET",
"ActivityHttpRoute": "",
"MetricHttpRoute": "",
"RouteInfo": {
@@ -200,8 +200,8 @@
```json
{
"IdealHttpRoute": "{area:exists}/ControllerForMyArea/Default/{id?}",
- "ActivityDisplayName": "/MyArea",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET {area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
+ "ActivityHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
"MetricHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -231,8 +231,8 @@
```json
{
"IdealHttpRoute": "{area:exists}/ControllerForMyArea/NonDefault/{id?}",
- "ActivityDisplayName": "/MyArea/ControllerForMyArea/NonDefault",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET {area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
+ "ActivityHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
"MetricHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -262,8 +262,8 @@
```json
{
"IdealHttpRoute": "SomePrefix/AnotherArea/Index/{id?}",
- "ActivityDisplayName": "/SomePrefix",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET SomePrefix/{controller=AnotherArea}/{action=Index}/{id?}",
+ "ActivityHttpRoute": "SomePrefix/{controller=AnotherArea}/{action=Index}/{id?}",
"MetricHttpRoute": "SomePrefix/{controller=AnotherArea}/{action=Index}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -293,7 +293,7 @@
```json
{
"IdealHttpRoute": "AttributeRoute",
- "ActivityDisplayName": "AttributeRoute",
+ "ActivityDisplayName": "GET AttributeRoute",
"ActivityHttpRoute": "AttributeRoute",
"MetricHttpRoute": "AttributeRoute",
"RouteInfo": {
@@ -323,7 +323,7 @@
```json
{
"IdealHttpRoute": "AttributeRoute/Get",
- "ActivityDisplayName": "AttributeRoute/Get",
+ "ActivityDisplayName": "GET AttributeRoute/Get",
"ActivityHttpRoute": "AttributeRoute/Get",
"MetricHttpRoute": "AttributeRoute/Get",
"RouteInfo": {
@@ -353,7 +353,7 @@
```json
{
"IdealHttpRoute": "AttributeRoute/Get/{id}",
- "ActivityDisplayName": "AttributeRoute/Get/{id}",
+ "ActivityDisplayName": "GET AttributeRoute/Get/{id}",
"ActivityHttpRoute": "AttributeRoute/Get/{id}",
"MetricHttpRoute": "AttributeRoute/Get/{id}",
"RouteInfo": {
@@ -386,7 +386,7 @@
```json
{
"IdealHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
- "ActivityDisplayName": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
+ "ActivityDisplayName": "GET AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"ActivityHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"MetricHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"RouteInfo": {
@@ -419,7 +419,7 @@
```json
{
"IdealHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
- "ActivityDisplayName": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
+ "ActivityDisplayName": "GET AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"ActivityHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"MetricHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"RouteInfo": {
@@ -452,7 +452,7 @@
```json
{
"IdealHttpRoute": "/Index",
- "ActivityDisplayName": "/",
+ "ActivityDisplayName": "GET",
"ActivityHttpRoute": "",
"MetricHttpRoute": "",
"RouteInfo": {
@@ -481,7 +481,7 @@
```json
{
"IdealHttpRoute": "/Index",
- "ActivityDisplayName": "Index",
+ "ActivityDisplayName": "GET Index",
"ActivityHttpRoute": "Index",
"MetricHttpRoute": "Index",
"RouteInfo": {
@@ -510,7 +510,7 @@
```json
{
"IdealHttpRoute": "/PageThatThrowsException",
- "ActivityDisplayName": "PageThatThrowsException",
+ "ActivityDisplayName": "GET PageThatThrowsException",
"ActivityHttpRoute": "PageThatThrowsException",
"MetricHttpRoute": "PageThatThrowsException",
"RouteInfo": {
@@ -539,7 +539,7 @@
```json
{
"IdealHttpRoute": "",
- "ActivityDisplayName": "/js/site.js",
+ "ActivityDisplayName": "GET",
"ActivityHttpRoute": "",
"MetricHttpRoute": "",
"RouteInfo": {
@@ -558,8 +558,8 @@
```json
{
"IdealHttpRoute": "/MinimalApi",
- "ActivityDisplayName": "/MinimalApi",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET /MinimalApi",
+ "ActivityHttpRoute": "/MinimalApi",
"MetricHttpRoute": "/MinimalApi",
"RouteInfo": {
"HttpMethod": "GET",
@@ -577,8 +577,8 @@
```json
{
"IdealHttpRoute": "/MinimalApi/{id}",
- "ActivityDisplayName": "/MinimalApi/123",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET /MinimalApi/{id}",
+ "ActivityHttpRoute": "/MinimalApi/{id}",
"MetricHttpRoute": "/MinimalApi/{id}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -598,8 +598,8 @@
```json
{
"IdealHttpRoute": "/MinimalApiUsingMapGroup/",
- "ActivityDisplayName": "/MinimalApiUsingMapGroup",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET /MinimalApiUsingMapGroup/",
+ "ActivityHttpRoute": "/MinimalApiUsingMapGroup/",
"MetricHttpRoute": "/MinimalApiUsingMapGroup/",
"RouteInfo": {
"HttpMethod": "GET",
@@ -617,8 +617,8 @@
```json
{
"IdealHttpRoute": "/MinimalApiUsingMapGroup/{id}",
- "ActivityDisplayName": "/MinimalApiUsingMapGroup/123",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET /MinimalApiUsingMapGroup/{id}",
+ "ActivityHttpRoute": "/MinimalApiUsingMapGroup/{id}",
"MetricHttpRoute": "/MinimalApiUsingMapGroup/{id}",
"RouteInfo": {
"HttpMethod": "GET",
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.net8.0.md b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.net8.0.md
index 4cacc1eac0c..3b712f73e20 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.net8.0.md
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/README.net8.0.md
@@ -1,37 +1,37 @@
# Test results for ASP.NET Core 8
-| Span http.route | Metric http.route | App | Test Name |
-| - | - | - | - |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Root path](#conventionalrouting-root-path) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Non-default action with route parameter and query string](#conventionalrouting-non-default-action-with-route-parameter-and-query-string) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Non-default action with query string](#conventionalrouting-non-default-action-with-query-string) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Not Found (404)](#conventionalrouting-not-found-404) |
-| :broken_heart: | :green_heart: | ConventionalRouting | [Route template with parameter constraint](#conventionalrouting-route-template-with-parameter-constraint) |
-| :broken_heart: | :green_heart: | ConventionalRouting | [Path that does not match parameter constraint](#conventionalrouting-path-that-does-not-match-parameter-constraint) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Area using area:exists, default controller/action](#conventionalrouting-area-using-areaexists-default-controlleraction) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Area using area:exists, non-default action](#conventionalrouting-area-using-areaexists-non-default-action) |
-| :broken_heart: | :broken_heart: | ConventionalRouting | [Area w/o area:exists, default controller/action](#conventionalrouting-area-wo-areaexists-default-controlleraction) |
-| :green_heart: | :green_heart: | AttributeRouting | [Default action](#attributerouting-default-action) |
-| :green_heart: | :green_heart: | AttributeRouting | [Action without parameter](#attributerouting-action-without-parameter) |
-| :green_heart: | :green_heart: | AttributeRouting | [Action with parameter](#attributerouting-action-with-parameter) |
-| :green_heart: | :green_heart: | AttributeRouting | [Action with parameter before action name in template](#attributerouting-action-with-parameter-before-action-name-in-template) |
-| :green_heart: | :green_heart: | AttributeRouting | [Action invoked resulting in 400 Bad Request](#attributerouting-action-invoked-resulting-in-400-bad-request) |
-| :broken_heart: | :broken_heart: | RazorPages | [Root path](#razorpages-root-path) |
-| :broken_heart: | :broken_heart: | RazorPages | [Index page](#razorpages-index-page) |
-| :broken_heart: | :broken_heart: | RazorPages | [Throws exception](#razorpages-throws-exception) |
-| :green_heart: | :green_heart: | RazorPages | [Static content](#razorpages-static-content) |
-| :broken_heart: | :green_heart: | MinimalApi | [Action without parameter](#minimalapi-action-without-parameter) |
-| :broken_heart: | :green_heart: | MinimalApi | [Action with parameter](#minimalapi-action-with-parameter) |
-| :broken_heart: | :green_heart: | MinimalApi | [Action without parameter (MapGroup)](#minimalapi-action-without-parameter-mapgroup) |
-| :broken_heart: | :green_heart: | MinimalApi | [Action with parameter (MapGroup)](#minimalapi-action-with-parameter-mapgroup) |
+| http.route | App | Test Name |
+| - | - | - |
+| :broken_heart: | ConventionalRouting | [Root path](#conventionalrouting-root-path) |
+| :broken_heart: | ConventionalRouting | [Non-default action with route parameter and query string](#conventionalrouting-non-default-action-with-route-parameter-and-query-string) |
+| :broken_heart: | ConventionalRouting | [Non-default action with query string](#conventionalrouting-non-default-action-with-query-string) |
+| :green_heart: | ConventionalRouting | [Not Found (404)](#conventionalrouting-not-found-404) |
+| :green_heart: | ConventionalRouting | [Route template with parameter constraint](#conventionalrouting-route-template-with-parameter-constraint) |
+| :green_heart: | ConventionalRouting | [Path that does not match parameter constraint](#conventionalrouting-path-that-does-not-match-parameter-constraint) |
+| :broken_heart: | ConventionalRouting | [Area using area:exists, default controller/action](#conventionalrouting-area-using-areaexists-default-controlleraction) |
+| :broken_heart: | ConventionalRouting | [Area using area:exists, non-default action](#conventionalrouting-area-using-areaexists-non-default-action) |
+| :broken_heart: | ConventionalRouting | [Area w/o area:exists, default controller/action](#conventionalrouting-area-wo-areaexists-default-controlleraction) |
+| :green_heart: | AttributeRouting | [Default action](#attributerouting-default-action) |
+| :green_heart: | AttributeRouting | [Action without parameter](#attributerouting-action-without-parameter) |
+| :green_heart: | AttributeRouting | [Action with parameter](#attributerouting-action-with-parameter) |
+| :green_heart: | AttributeRouting | [Action with parameter before action name in template](#attributerouting-action-with-parameter-before-action-name-in-template) |
+| :green_heart: | AttributeRouting | [Action invoked resulting in 400 Bad Request](#attributerouting-action-invoked-resulting-in-400-bad-request) |
+| :broken_heart: | RazorPages | [Root path](#razorpages-root-path) |
+| :broken_heart: | RazorPages | [Index page](#razorpages-index-page) |
+| :broken_heart: | RazorPages | [Throws exception](#razorpages-throws-exception) |
+| :green_heart: | RazorPages | [Static content](#razorpages-static-content) |
+| :green_heart: | MinimalApi | [Action without parameter](#minimalapi-action-without-parameter) |
+| :green_heart: | MinimalApi | [Action with parameter](#minimalapi-action-with-parameter) |
+| :green_heart: | MinimalApi | [Action without parameter (MapGroup)](#minimalapi-action-without-parameter-mapgroup) |
+| :green_heart: | MinimalApi | [Action with parameter (MapGroup)](#minimalapi-action-with-parameter-mapgroup) |
## ConventionalRouting: Root path
```json
{
"IdealHttpRoute": "ConventionalRoute/Default/{id?}",
- "ActivityDisplayName": "/",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET {controller=ConventionalRoute}/{action=Default}/{id?}",
+ "ActivityHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"MetricHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -60,8 +60,8 @@
```json
{
"IdealHttpRoute": "ConventionalRoute/ActionWithStringParameter/{id?}",
- "ActivityDisplayName": "/ConventionalRoute/ActionWithStringParameter/2",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET {controller=ConventionalRoute}/{action=Default}/{id?}",
+ "ActivityHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"MetricHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -94,8 +94,8 @@
```json
{
"IdealHttpRoute": "ConventionalRoute/ActionWithStringParameter/{id?}",
- "ActivityDisplayName": "/ConventionalRoute/ActionWithStringParameter",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET {controller=ConventionalRoute}/{action=Default}/{id?}",
+ "ActivityHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"MetricHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -127,7 +127,7 @@
```json
{
"IdealHttpRoute": "",
- "ActivityDisplayName": "/ConventionalRoute/NotFound",
+ "ActivityDisplayName": "GET",
"ActivityHttpRoute": "",
"MetricHttpRoute": "",
"RouteInfo": {
@@ -146,8 +146,8 @@
```json
{
"IdealHttpRoute": "SomePath/{id}/{num:int}",
- "ActivityDisplayName": "/SomePath/SomeString/2",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET SomePath/{id}/{num:int}",
+ "ActivityHttpRoute": "SomePath/{id}/{num:int}",
"MetricHttpRoute": "SomePath/{id}/{num:int}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -181,7 +181,7 @@
```json
{
"IdealHttpRoute": "",
- "ActivityDisplayName": "/SomePath/SomeString/NotAnInt",
+ "ActivityDisplayName": "GET",
"ActivityHttpRoute": "",
"MetricHttpRoute": "",
"RouteInfo": {
@@ -200,8 +200,8 @@
```json
{
"IdealHttpRoute": "{area:exists}/ControllerForMyArea/Default/{id?}",
- "ActivityDisplayName": "/MyArea",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET {area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
+ "ActivityHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
"MetricHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -231,8 +231,8 @@
```json
{
"IdealHttpRoute": "{area:exists}/ControllerForMyArea/NonDefault/{id?}",
- "ActivityDisplayName": "/MyArea/ControllerForMyArea/NonDefault",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET {area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
+ "ActivityHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
"MetricHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -262,8 +262,8 @@
```json
{
"IdealHttpRoute": "SomePrefix/AnotherArea/Index/{id?}",
- "ActivityDisplayName": "/SomePrefix",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET SomePrefix/{controller=AnotherArea}/{action=Index}/{id?}",
+ "ActivityHttpRoute": "SomePrefix/{controller=AnotherArea}/{action=Index}/{id?}",
"MetricHttpRoute": "SomePrefix/{controller=AnotherArea}/{action=Index}/{id?}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -293,7 +293,7 @@
```json
{
"IdealHttpRoute": "AttributeRoute",
- "ActivityDisplayName": "AttributeRoute",
+ "ActivityDisplayName": "GET AttributeRoute",
"ActivityHttpRoute": "AttributeRoute",
"MetricHttpRoute": "AttributeRoute",
"RouteInfo": {
@@ -323,7 +323,7 @@
```json
{
"IdealHttpRoute": "AttributeRoute/Get",
- "ActivityDisplayName": "AttributeRoute/Get",
+ "ActivityDisplayName": "GET AttributeRoute/Get",
"ActivityHttpRoute": "AttributeRoute/Get",
"MetricHttpRoute": "AttributeRoute/Get",
"RouteInfo": {
@@ -353,7 +353,7 @@
```json
{
"IdealHttpRoute": "AttributeRoute/Get/{id}",
- "ActivityDisplayName": "AttributeRoute/Get/{id}",
+ "ActivityDisplayName": "GET AttributeRoute/Get/{id}",
"ActivityHttpRoute": "AttributeRoute/Get/{id}",
"MetricHttpRoute": "AttributeRoute/Get/{id}",
"RouteInfo": {
@@ -386,7 +386,7 @@
```json
{
"IdealHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
- "ActivityDisplayName": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
+ "ActivityDisplayName": "GET AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"ActivityHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"MetricHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"RouteInfo": {
@@ -419,7 +419,7 @@
```json
{
"IdealHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
- "ActivityDisplayName": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
+ "ActivityDisplayName": "GET AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"ActivityHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"MetricHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
"RouteInfo": {
@@ -452,7 +452,7 @@
```json
{
"IdealHttpRoute": "/Index",
- "ActivityDisplayName": "/",
+ "ActivityDisplayName": "GET",
"ActivityHttpRoute": "",
"MetricHttpRoute": "",
"RouteInfo": {
@@ -481,7 +481,7 @@
```json
{
"IdealHttpRoute": "/Index",
- "ActivityDisplayName": "Index",
+ "ActivityDisplayName": "GET Index",
"ActivityHttpRoute": "Index",
"MetricHttpRoute": "Index",
"RouteInfo": {
@@ -510,7 +510,7 @@
```json
{
"IdealHttpRoute": "/PageThatThrowsException",
- "ActivityDisplayName": "PageThatThrowsException",
+ "ActivityDisplayName": "GET PageThatThrowsException",
"ActivityHttpRoute": "PageThatThrowsException",
"MetricHttpRoute": "PageThatThrowsException",
"RouteInfo": {
@@ -539,7 +539,7 @@
```json
{
"IdealHttpRoute": "",
- "ActivityDisplayName": "/js/site.js",
+ "ActivityDisplayName": "GET",
"ActivityHttpRoute": "",
"MetricHttpRoute": "",
"RouteInfo": {
@@ -558,8 +558,8 @@
```json
{
"IdealHttpRoute": "/MinimalApi",
- "ActivityDisplayName": "/MinimalApi",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET /MinimalApi",
+ "ActivityHttpRoute": "/MinimalApi",
"MetricHttpRoute": "/MinimalApi",
"RouteInfo": {
"HttpMethod": "GET",
@@ -577,8 +577,8 @@
```json
{
"IdealHttpRoute": "/MinimalApi/{id}",
- "ActivityDisplayName": "/MinimalApi/123",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET /MinimalApi/{id}",
+ "ActivityHttpRoute": "/MinimalApi/{id}",
"MetricHttpRoute": "/MinimalApi/{id}",
"RouteInfo": {
"HttpMethod": "GET",
@@ -598,8 +598,8 @@
```json
{
"IdealHttpRoute": "/MinimalApiUsingMapGroup/",
- "ActivityDisplayName": "/MinimalApiUsingMapGroup",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET /MinimalApiUsingMapGroup/",
+ "ActivityHttpRoute": "/MinimalApiUsingMapGroup/",
"MetricHttpRoute": "/MinimalApiUsingMapGroup/",
"RouteInfo": {
"HttpMethod": "GET",
@@ -617,8 +617,8 @@
```json
{
"IdealHttpRoute": "/MinimalApiUsingMapGroup/{id}",
- "ActivityDisplayName": "/MinimalApiUsingMapGroup/123",
- "ActivityHttpRoute": "",
+ "ActivityDisplayName": "GET /MinimalApiUsingMapGroup/{id}",
+ "ActivityHttpRoute": "/MinimalApiUsingMapGroup/{id}",
"MetricHttpRoute": "/MinimalApiUsingMapGroup/{id}",
"RouteInfo": {
"HttpMethod": "GET",
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestCases.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestCases.cs
index d2b7bb730df..bf5c40cbcb1 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestCases.cs
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestCases.cs
@@ -49,8 +49,7 @@ private static IEnumerable GetArgumentsFromTestCaseObject(IEnumerable<
continue;
}
- result.Add(new object[] { testCase, true });
- result.Add(new object[] { testCase, false });
+ result.Add(new object[] { testCase });
}
return result;
@@ -72,11 +71,7 @@ public class TestCase
public string? ExpectedHttpRoute { get; set; }
- public string? CurrentActivityDisplayName { get; set; }
-
- public string? CurrentActivityHttpRoute { get; set; }
-
- public string? CurrentMetricHttpRoute { get; set; }
+ public string? CurrentHttpRoute { get; set; }
public override string ToString()
{
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestCases.json b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestCases.json
index c5c8febb77b..4d871d986a1 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestCases.json
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestCases.json
@@ -5,9 +5,7 @@
"httpMethod": "GET",
"path": "/",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "/",
- "currentActivityHttpRoute": "",
- "currentMetricHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
+ "currentHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"expectedHttpRoute": "ConventionalRoute/Default/{id?}"
},
{
@@ -16,9 +14,7 @@
"httpMethod": "GET",
"path": "/ConventionalRoute/ActionWithStringParameter/2?num=3",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "/ConventionalRoute/ActionWithStringParameter/2",
- "currentActivityHttpRoute": "",
- "currentMetricHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
+ "currentHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"expectedHttpRoute": "ConventionalRoute/ActionWithStringParameter/{id?}"
},
{
@@ -27,9 +23,7 @@
"httpMethod": "GET",
"path": "/ConventionalRoute/ActionWithStringParameter?num=3",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "/ConventionalRoute/ActionWithStringParameter",
- "currentActivityHttpRoute": "",
- "currentMetricHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
+ "currentHttpRoute": "{controller=ConventionalRoute}/{action=Default}/{id?}",
"expectedHttpRoute": "ConventionalRoute/ActionWithStringParameter/{id?}"
},
{
@@ -38,9 +32,7 @@
"httpMethod": "GET",
"path": "/ConventionalRoute/NotFound",
"expectedStatusCode": 404,
- "currentActivityDisplayName": "/ConventionalRoute/NotFound",
- "currentActivityHttpRoute": "",
- "currentMetricHttpRoute": "",
+ "currentHttpRoute": null,
"expectedHttpRoute": ""
},
{
@@ -49,9 +41,7 @@
"httpMethod": "GET",
"path": "/SomePath/SomeString/2",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "/SomePath/SomeString/2",
- "currentActivityHttpRoute": "",
- "currentMetricHttpRoute": null,
+ "currentHttpRoute": null,
"expectedHttpRoute": "SomePath/{id}/{num:int}"
},
{
@@ -60,9 +50,7 @@
"httpMethod": "GET",
"path": "/SomePath/SomeString/NotAnInt",
"expectedStatusCode": 404,
- "currentActivityDisplayName": "/SomePath/SomeString/NotAnInt",
- "currentActivityHttpRoute": "",
- "currentMetricHttpRoute": null,
+ "currentHttpRoute": null,
"expectedHttpRoute": ""
},
{
@@ -71,9 +59,7 @@
"httpMethod": "GET",
"path": "/MyArea",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "/MyArea",
- "currentActivityHttpRoute": "",
- "currentMetricHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
+ "currentHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
"expectedHttpRoute": "{area:exists}/ControllerForMyArea/Default/{id?}"
},
{
@@ -82,9 +68,7 @@
"httpMethod": "GET",
"path": "/MyArea/ControllerForMyArea/NonDefault",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "/MyArea/ControllerForMyArea/NonDefault",
- "currentActivityHttpRoute": "",
- "currentMetricHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
+ "currentHttpRoute": "{area:exists}/{controller=ControllerForMyArea}/{action=Default}/{id?}",
"expectedHttpRoute": "{area:exists}/ControllerForMyArea/NonDefault/{id?}"
},
{
@@ -93,9 +77,7 @@
"httpMethod": "GET",
"path": "/SomePrefix",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "/SomePrefix",
- "currentActivityHttpRoute": "",
- "currentMetricHttpRoute": "SomePrefix/{controller=AnotherArea}/{action=Index}/{id?}",
+ "currentHttpRoute": "SomePrefix/{controller=AnotherArea}/{action=Index}/{id?}",
"expectedHttpRoute": "SomePrefix/AnotherArea/Index/{id?}"
},
{
@@ -104,9 +86,7 @@
"httpMethod": "GET",
"path": "/AttributeRoute",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "AttributeRoute",
- "currentActivityHttpRoute": null,
- "currentMetricHttpRoute": null,
+ "currentHttpRoute": null,
"expectedHttpRoute": "AttributeRoute"
},
{
@@ -115,9 +95,7 @@
"httpMethod": "GET",
"path": "/AttributeRoute/Get",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "AttributeRoute/Get",
- "currentActivityHttpRoute": null,
- "currentMetricHttpRoute": null,
+ "currentHttpRoute": null,
"expectedHttpRoute": "AttributeRoute/Get"
},
{
@@ -126,9 +104,7 @@
"httpMethod": "GET",
"path": "/AttributeRoute/Get/12",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "AttributeRoute/Get/{id}",
- "currentActivityHttpRoute": null,
- "currentMetricHttpRoute": null,
+ "currentHttpRoute": null,
"expectedHttpRoute": "AttributeRoute/Get/{id}"
},
{
@@ -137,9 +113,7 @@
"httpMethod": "GET",
"path": "/AttributeRoute/12/GetWithActionNameInDifferentSpotInTemplate",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
- "currentActivityHttpRoute": null,
- "currentMetricHttpRoute": null,
+ "currentHttpRoute": null,
"expectedHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate"
},
{
@@ -148,9 +122,7 @@
"httpMethod": "GET",
"path": "/AttributeRoute/NotAnInt/GetWithActionNameInDifferentSpotInTemplate",
"expectedStatusCode": 400,
- "currentActivityDisplayName": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate",
- "currentActivityHttpRoute": null,
- "currentMetricHttpRoute": null,
+ "currentHttpRoute": null,
"expectedHttpRoute": "AttributeRoute/{id}/GetWithActionNameInDifferentSpotInTemplate"
},
{
@@ -159,9 +131,7 @@
"httpMethod": "GET",
"path": "/",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "/",
- "currentActivityHttpRoute": "",
- "currentMetricHttpRoute": "",
+ "currentHttpRoute": "",
"expectedHttpRoute": "/Index"
},
{
@@ -170,9 +140,7 @@
"httpMethod": "GET",
"path": "/Index",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "Index",
- "currentActivityHttpRoute": "Index",
- "currentMetricHttpRoute": "Index",
+ "currentHttpRoute": "Index",
"expectedHttpRoute": "/Index"
},
{
@@ -181,9 +149,7 @@
"httpMethod": "GET",
"path": "/PageThatThrowsException",
"expectedStatusCode": 500,
- "currentActivityDisplayName": "PageThatThrowsException",
- "currentActivityHttpRoute": "PageThatThrowsException",
- "currentMetricHttpRoute": "PageThatThrowsException",
+ "currentHttpRoute": "PageThatThrowsException",
"expectedHttpRoute": "/PageThatThrowsException"
},
{
@@ -192,9 +158,7 @@
"httpMethod": "GET",
"path": "/js/site.js",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "/js/site.js",
- "currentActivityHttpRoute": null,
- "currentMetricHttpRoute": null,
+ "currentHttpRoute": null,
"expectedHttpRoute": ""
},
{
@@ -203,9 +167,7 @@
"httpMethod": "GET",
"path": "/MinimalApi",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "/MinimalApi",
- "currentActivityHttpRoute": "",
- "currentMetricHttpRoute": null,
+ "currentHttpRoute": null,
"expectedHttpRoute": "/MinimalApi"
},
{
@@ -214,9 +176,7 @@
"httpMethod": "GET",
"path": "/MinimalApi/123",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "/MinimalApi/123",
- "currentActivityHttpRoute": "",
- "currentMetricHttpRoute": null,
+ "currentHttpRoute": null,
"expectedHttpRoute": "/MinimalApi/{id}"
},
{
@@ -226,9 +186,7 @@
"httpMethod": "GET",
"path": "/MinimalApiUsingMapGroup",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "/MinimalApiUsingMapGroup",
- "currentActivityHttpRoute": "",
- "currentMetricHttpRoute": null,
+ "currentHttpRoute": null,
"expectedHttpRoute": "/MinimalApiUsingMapGroup/"
},
{
@@ -238,9 +196,7 @@
"httpMethod": "GET",
"path": "/MinimalApiUsingMapGroup/123",
"expectedStatusCode": 200,
- "currentActivityDisplayName": "/MinimalApiUsingMapGroup/123",
- "currentActivityHttpRoute": "",
- "currentMetricHttpRoute": null,
+ "currentHttpRoute": null,
"expectedHttpRoute": "/MinimalApiUsingMapGroup/{id}"
}
]
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestFixture.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestFixture.cs
index 1addda01cb6..c05e5b9e4d5 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestFixture.cs
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTestFixture.cs
@@ -51,7 +51,7 @@ public async Task MakeRequest(TestApplicationScenario scenario, string path)
var app = this.apps[scenario];
var baseUrl = app.Urls.First();
var url = $"{baseUrl}{path}";
- await HttpClient.GetAsync(url).ConfigureAwait(false);
+ await HttpClient.GetAsync(url);
}
public void AddTestResult(RoutingTestResult result)
@@ -77,16 +77,14 @@ private void GenerateReadme()
var sb = new StringBuilder();
sb.AppendLine($"# Test results for ASP.NET Core {Environment.Version.Major}");
sb.AppendLine();
- sb.AppendLine("| Span http.route | Metric http.route | App | Test Name |");
- sb.AppendLine("| - | - | - | - |");
+ sb.AppendLine("| http.route | App | Test Name |");
+ sb.AppendLine("| - | - | - |");
for (var i = 0; i < this.testResults.Count; ++i)
{
var result = this.testResults[i];
- var emoji1 = result.TestCase.CurrentActivityHttpRoute == null ? ":green_heart:" : ":broken_heart:";
- var emoji2 = result.TestCase.CurrentMetricHttpRoute == null ? ":green_heart:" : ":broken_heart:";
- sb.Append($"| {emoji1} | {emoji2} ");
- sb.AppendLine($"| {result.TestCase.TestApplicationScenario} | [{result.TestCase.Name}]({MakeAnchorTag(result.TestCase.TestApplicationScenario, result.TestCase.Name)}) |");
+ var emoji = result.TestCase.CurrentHttpRoute == null ? ":green_heart:" : ":broken_heart:";
+ sb.AppendLine($"| {emoji} | {result.TestCase.TestApplicationScenario} | [{result.TestCase.Name}]({MakeAnchorTag(result.TestCase.TestApplicationScenario, result.TestCase.Name)}) |");
}
for (var i = 0; i < this.testResults.Count; ++i)
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTests.cs
index d8983db9d4d..f03b21c0405 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTests.cs
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/RoutingTests.cs
@@ -17,14 +17,11 @@
#nullable enable
using System.Diagnostics;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using RouteTests.TestApplication;
using Xunit;
-using static OpenTelemetry.Internal.HttpSemanticConventionHelper;
namespace RouteTests;
@@ -49,20 +46,14 @@ public RoutingTests(RoutingTestFixture fixture)
[Theory]
[MemberData(nameof(TestData))]
- public async Task TestHttpRoute(RoutingTestCases.TestCase testCase, bool useLegacyConventions)
+ public async Task TestHttpRoute(RoutingTestCases.TestCase testCase)
{
- var configuration = new ConfigurationBuilder()
- .AddInMemoryCollection(new Dictionary { [SemanticConventionOptInKeyName] = useLegacyConventions ? null : "http" })
- .Build();
-
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
- .ConfigureServices(services => services.AddSingleton(configuration))
.AddAspNetCoreInstrumentation()
.AddInMemoryExporter(this.exportedActivities)
.Build()!;
using var meterProvider = Sdk.CreateMeterProviderBuilder()
- .ConfigureServices(services => services.AddSingleton(configuration))
.AddAspNetCoreInstrumentation()
.AddInMemoryExporter(this.exportedMetrics)
.Build()!;
@@ -76,7 +67,7 @@ public async Task TestHttpRoute(RoutingTestCases.TestCase testCase, bool useLega
break;
}
- await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
+ await Task.Delay(TimeSpan.FromSeconds(1));
}
meterProvider.ForceFlush();
@@ -91,73 +82,45 @@ public async Task TestHttpRoute(RoutingTestCases.TestCase testCase, bool useLega
var activity = Assert.Single(this.exportedActivities);
var metricPoint = Assert.Single(metricPoints);
- GetTagsFromActivity(useLegacyConventions, activity, out var activityHttpStatusCode, out var activityHttpMethod, out var activityHttpRoute);
- GetTagsFromMetricPoint(useLegacyConventions && Environment.Version.Major < 8, metricPoint, out var metricHttpStatusCode, out var metricHttpMethod, out var metricHttpRoute);
+ GetTagsFromActivity(activity, out var activityHttpStatusCode, out var activityHttpMethod, out var activityHttpRoute);
+ GetTagsFromMetricPoint(Environment.Version.Major < 8, metricPoint, out var metricHttpStatusCode, out var metricHttpMethod, out var metricHttpRoute);
Assert.Equal(testCase.ExpectedStatusCode, activityHttpStatusCode);
Assert.Equal(testCase.ExpectedStatusCode, metricHttpStatusCode);
Assert.Equal(testCase.HttpMethod, activityHttpMethod);
Assert.Equal(testCase.HttpMethod, metricHttpMethod);
- // TODO: The CurrentActivityDisplayName, CurrentActivityHttpRoute, and CurrentMetricHttpRoute
- // properties will go away. They only serve to capture status quo. The "else" blocks are the real
- // asserts that we ultimately want.
- // If any of the current properties are null, then that means we already conform to the
- // correct behavior.
- if (testCase.CurrentActivityDisplayName != null)
- {
- Assert.Equal(testCase.CurrentActivityDisplayName, activity.DisplayName);
- }
- else
- {
- // Activity.DisplayName should be a combination of http.method + http.route attributes, see:
- // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#name
- var expectedActivityDisplayName = string.IsNullOrEmpty(testCase.ExpectedHttpRoute)
- ? testCase.HttpMethod
- : $"{testCase.HttpMethod} {testCase.ExpectedHttpRoute}";
-
- Assert.Equal(expectedActivityDisplayName, activity.DisplayName);
- }
+ // TODO: The CurrentHttpRoute property will go away. It They only serve to capture status quo.
+ // If CurrentHttpRoute is null, then that means we already conform to the correct behavior.
+ var expectedHttpRoute = testCase.CurrentHttpRoute != null ? testCase.CurrentHttpRoute : testCase.ExpectedHttpRoute;
+ Assert.Equal(expectedHttpRoute, activityHttpRoute);
+ Assert.Equal(expectedHttpRoute, metricHttpRoute);
- if (testCase.CurrentActivityHttpRoute != null)
- {
- Assert.Equal(testCase.CurrentActivityHttpRoute, activityHttpRoute);
- }
- else
- {
- Assert.Equal(testCase.ExpectedHttpRoute, activityHttpRoute);
- }
+ // Activity.DisplayName should be a combination of http.method + http.route attributes, see:
+ // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#name
+ var expectedActivityDisplayName = string.IsNullOrEmpty(expectedHttpRoute)
+ ? testCase.HttpMethod
+ : $"{testCase.HttpMethod} {expectedHttpRoute}";
- if (testCase.CurrentMetricHttpRoute != null)
- {
- Assert.Equal(testCase.CurrentMetricHttpRoute, metricHttpRoute);
- }
- else
- {
- Assert.Equal(testCase.ExpectedHttpRoute, metricHttpRoute);
- }
+ Assert.Equal(expectedActivityDisplayName, activity.DisplayName);
- // Only produce README files based on final semantic conventions
- if (!useLegacyConventions)
+ var testResult = new RoutingTestResult
{
- var testResult = new RoutingTestResult
- {
- IdealHttpRoute = testCase.ExpectedHttpRoute,
- ActivityDisplayName = activity.DisplayName,
- ActivityHttpRoute = activityHttpRoute,
- MetricHttpRoute = metricHttpRoute,
- TestCase = testCase,
- RouteInfo = RouteInfo.Current,
- };
-
- this.fixture.AddTestResult(testResult);
- }
+ IdealHttpRoute = testCase.ExpectedHttpRoute,
+ ActivityDisplayName = activity.DisplayName,
+ ActivityHttpRoute = activityHttpRoute,
+ MetricHttpRoute = metricHttpRoute,
+ TestCase = testCase,
+ RouteInfo = RouteInfo.Current,
+ };
+
+ this.fixture.AddTestResult(testResult);
}
- private static void GetTagsFromActivity(bool useLegacyConventions, Activity activity, out int httpStatusCode, out string httpMethod, out string? httpRoute)
+ private static void GetTagsFromActivity(Activity activity, out int httpStatusCode, out string httpMethod, out string? httpRoute)
{
- var expectedStatusCodeKey = useLegacyConventions ? OldHttpStatusCode : HttpStatusCode;
- var expectedHttpMethodKey = useLegacyConventions ? OldHttpMethod : HttpMethod;
+ var expectedStatusCodeKey = HttpStatusCode;
+ var expectedHttpMethodKey = HttpMethod;
httpStatusCode = Convert.ToInt32(activity.GetTagItem(expectedStatusCodeKey));
httpMethod = (activity.GetTagItem(expectedHttpMethodKey) as string)!;
httpRoute = activity.GetTagItem(HttpRoute) as string ?? string.Empty;
@@ -165,8 +128,8 @@ private static void GetTagsFromActivity(bool useLegacyConventions, Activity acti
private static void GetTagsFromMetricPoint(bool useLegacyConventions, MetricPoint metricPoint, out int httpStatusCode, out string httpMethod, out string? httpRoute)
{
- var expectedStatusCodeKey = useLegacyConventions ? OldHttpStatusCode : HttpStatusCode;
- var expectedHttpMethodKey = useLegacyConventions ? OldHttpMethod : HttpMethod;
+ var expectedStatusCodeKey = HttpStatusCode;
+ var expectedHttpMethodKey = HttpMethod;
httpStatusCode = 0;
httpMethod = string.Empty;
diff --git a/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTestHelpers/ClientTestHelpers.cs b/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTestHelpers/ClientTestHelpers.cs
index f2b34dee76d..66ad69a281c 100644
--- a/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTestHelpers/ClientTestHelpers.cs
+++ b/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTestHelpers/ClientTestHelpers.cs
@@ -64,11 +64,11 @@ public static async Task WriteResponseAsync(Stream ms, TResponse resp
data = response.ToByteArray();
}
- await ResponseUtils.WriteHeaderAsync(ms, data.Length, compress, CancellationToken.None).ConfigureAwait(false);
+ await ResponseUtils.WriteHeaderAsync(ms, data.Length, compress, CancellationToken.None);
#if NET5_0_OR_GREATER
- await ms.WriteAsync(data).ConfigureAwait(false);
+ await ms.WriteAsync(data);
#else
- await ms.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
+ await ms.WriteAsync(data, 0, data.Length);
#endif
}
@@ -78,7 +78,7 @@ private static async Task CreateResponseContentCore(TR
var ms = new MemoryStream();
foreach (var response in responses)
{
- await WriteResponseAsync(ms, response, compressionProvider).ConfigureAwait(false);
+ await WriteResponseAsync(ms, response, compressionProvider);
}
ms.Seek(0, SeekOrigin.Begin);
diff --git a/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTestHelpers/TestHttpMessageHandler.cs b/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTestHelpers/TestHttpMessageHandler.cs
index e104f11acbc..e22fb6d7e17 100644
--- a/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTestHelpers/TestHttpMessageHandler.cs
+++ b/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTestHelpers/TestHttpMessageHandler.cs
@@ -37,8 +37,8 @@ public static TestHttpMessageHandler Create(Func tcs.TrySetCanceled());
- var result = await Task.WhenAny(sendAsync(request), tcs.Task).ConfigureAwait(false);
- return await result.ConfigureAwait(false);
+ var result = await Task.WhenAny(sendAsync(request), tcs.Task);
+ return await result;
});
}
diff --git a/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.client.cs b/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.client.cs
index 407b6d9ab39..a9a6ff901ed 100644
--- a/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.client.cs
+++ b/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.client.cs
@@ -55,7 +55,7 @@ public void GrpcClientCallsAreCollectedSuccessfully(string baseAddress, bool sho
using var httpClient = ClientTestHelpers.CreateTestClient(async request =>
{
- var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).ConfigureAwait(false);
+ var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply());
var response = ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent, grpcStatusCode: global::Grpc.Core.StatusCode.OK);
response.TrailingHeaders().Add("grpc-message", "value");
return response;
@@ -150,7 +150,7 @@ public void GrpcClientCallsAreCollectedSuccessfully_New(string baseAddress, bool
using var httpClient = ClientTestHelpers.CreateTestClient(async request =>
{
- var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).ConfigureAwait(false);
+ var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply());
var response = ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent, grpcStatusCode: global::Grpc.Core.StatusCode.OK);
response.TrailingHeaders().Add("grpc-message", "value");
return response;
@@ -246,7 +246,7 @@ public void GrpcClientCallsAreCollectedSuccessfully_Dupe(string baseAddress, boo
using var httpClient = ClientTestHelpers.CreateTestClient(async request =>
{
- var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).ConfigureAwait(false);
+ var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply());
var response = ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent, grpcStatusCode: global::Grpc.Core.StatusCode.OK);
response.TrailingHeaders().Add("grpc-message", "value");
return response;
diff --git a/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.server.cs b/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.server.cs
index c8011b4bb84..da734da25f9 100644
--- a/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.server.cs
+++ b/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.server.cs
@@ -51,77 +51,6 @@ public GrpcTests()
[InlineData(true)]
[InlineData(false)]
public void GrpcAspNetCoreInstrumentationAddsCorrectAttributes(bool? enableGrpcAspNetCoreSupport)
- {
- var exportedItems = new List();
- var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder();
-
- if (enableGrpcAspNetCoreSupport.HasValue)
- {
- tracerProviderBuilder.AddAspNetCoreInstrumentation(options =>
- {
- options.EnableGrpcAspNetCoreSupport = enableGrpcAspNetCoreSupport.Value;
- });
- }
- else
- {
- tracerProviderBuilder.AddAspNetCoreInstrumentation();
- }
-
- using var tracerProvider = tracerProviderBuilder
- .AddInMemoryExporter(exportedItems)
- .Build();
-
- var clientLoopbackAddresses = new[] { IPAddress.Loopback.ToString(), IPAddress.IPv6Loopback.ToString() };
- var uri = new Uri($"http://localhost:{this.server.Port}");
-
- using var channel = GrpcChannel.ForAddress(uri);
- var client = new Greeter.GreeterClient(channel);
- var returnMsg = client.SayHello(new HelloRequest()).Message;
- Assert.False(string.IsNullOrEmpty(returnMsg));
-
- WaitForExporterToReceiveItems(exportedItems, 1);
- Assert.Single(exportedItems);
- var activity = exportedItems[0];
-
- Assert.Equal(ActivityKind.Server, activity.Kind);
-
- if (!enableGrpcAspNetCoreSupport.HasValue || enableGrpcAspNetCoreSupport.Value)
- {
- Assert.Equal("grpc", activity.GetTagValue(SemanticConventions.AttributeRpcSystem));
- Assert.Equal("greet.Greeter", activity.GetTagValue(SemanticConventions.AttributeRpcService));
- Assert.Equal("SayHello", activity.GetTagValue(SemanticConventions.AttributeRpcMethod));
- Assert.Contains(activity.GetTagValue(SemanticConventions.AttributeNetPeerIp), clientLoopbackAddresses);
- Assert.NotEqual(0, activity.GetTagValue(SemanticConventions.AttributeNetPeerPort));
- Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
- Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
- Assert.Equal(0, activity.GetTagValue(SemanticConventions.AttributeRpcGrpcStatusCode));
- }
- else
- {
- Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
- Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
- }
-
- Assert.Equal(Status.Unset, activity.GetStatus());
-
- // The following are http.* attributes that are also included on the span for the gRPC invocation.
- Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeNetHostName));
- Assert.Equal(this.server.Port, activity.GetTagValue(SemanticConventions.AttributeNetHostPort));
- Assert.Equal("POST", activity.GetTagValue(SemanticConventions.AttributeHttpMethod));
- Assert.Equal("/greet.Greeter/SayHello", activity.GetTagValue(SemanticConventions.AttributeHttpTarget));
- Assert.Equal($"http://localhost:{this.server.Port}/greet.Greeter/SayHello", activity.GetTagValue(SemanticConventions.AttributeHttpUrl));
- Assert.StartsWith("grpc-dotnet", activity.GetTagValue(SemanticConventions.AttributeHttpUserAgent) as string);
- }
-
- // Tests for v1.21.0 Semantic Conventions for database client calls.
- // see the spec https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-spans.md
- // This test emits the new attributes.
- // This test method can replace the other (old) test method when this library is GA.
- [Theory]
- [InlineData(null)]
- [InlineData(true)]
- [InlineData(false)]
- public void GrpcAspNetCoreInstrumentationAddsCorrectAttributes_New(bool? enableGrpcAspNetCoreSupport)
{
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary { [SemanticConventionOptInKeyName] = "http" })
@@ -190,112 +119,6 @@ public void GrpcAspNetCoreInstrumentationAddsCorrectAttributes_New(bool? enableG
Assert.StartsWith("grpc-dotnet", activity.GetTagValue(SemanticConventions.AttributeUserAgentOriginal) as string);
}
- // Tests for v1.21.0 Semantic Conventions for database client calls.
- // see the spec https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-spans.md
- // This test emits both the new and older attributes.
- // This test method can be deleted when this library is GA.
- [Theory]
- [InlineData(null)]
- [InlineData(true)]
- [InlineData(false)]
- public void GrpcAspNetCoreInstrumentationAddsCorrectAttributes_Dupe(bool? enableGrpcAspNetCoreSupport)
- {
- var configuration = new ConfigurationBuilder()
- .AddInMemoryCollection(new Dictionary { [SemanticConventionOptInKeyName] = "http/dup" })
- .Build();
-
- var exportedItems = new List();
- var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder()
- .ConfigureServices(services => services.AddSingleton(configuration));
-
- if (enableGrpcAspNetCoreSupport.HasValue)
- {
- tracerProviderBuilder.AddAspNetCoreInstrumentation(options =>
- {
- options.EnableGrpcAspNetCoreSupport = enableGrpcAspNetCoreSupport.Value;
- });
- }
- else
- {
- tracerProviderBuilder.AddAspNetCoreInstrumentation();
- }
-
- using var tracerProvider = tracerProviderBuilder
- .AddInMemoryExporter(exportedItems)
- .Build();
-
- var clientLoopbackAddresses = new[] { IPAddress.Loopback.ToString(), IPAddress.IPv6Loopback.ToString() };
- var uri = new Uri($"http://localhost:{this.server.Port}");
-
- using var channel = GrpcChannel.ForAddress(uri);
- var client = new Greeter.GreeterClient(channel);
- var returnMsg = client.SayHello(new HelloRequest()).Message;
- Assert.False(string.IsNullOrEmpty(returnMsg));
-
- WaitForExporterToReceiveItems(exportedItems, 1);
- Assert.Single(exportedItems);
- var activity = exportedItems[0];
-
- Assert.Equal(ActivityKind.Server, activity.Kind);
-
- // OLD
- if (!enableGrpcAspNetCoreSupport.HasValue || enableGrpcAspNetCoreSupport.Value)
- {
- Assert.Equal("grpc", activity.GetTagValue(SemanticConventions.AttributeRpcSystem));
- Assert.Equal("greet.Greeter", activity.GetTagValue(SemanticConventions.AttributeRpcService));
- Assert.Equal("SayHello", activity.GetTagValue(SemanticConventions.AttributeRpcMethod));
- Assert.Contains(activity.GetTagValue(SemanticConventions.AttributeNetPeerIp), clientLoopbackAddresses);
- Assert.NotEqual(0, activity.GetTagValue(SemanticConventions.AttributeNetPeerPort));
- Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
- Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
- Assert.Equal(0, activity.GetTagValue(SemanticConventions.AttributeRpcGrpcStatusCode));
- }
- else
- {
- Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
- Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
- }
-
- Assert.Equal(Status.Unset, activity.GetStatus());
-
- // The following are http.* attributes that are also included on the span for the gRPC invocation.
- Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeNetHostName));
- Assert.Equal(this.server.Port, activity.GetTagValue(SemanticConventions.AttributeNetHostPort));
- Assert.Equal("POST", activity.GetTagValue(SemanticConventions.AttributeHttpMethod));
- Assert.Equal("/greet.Greeter/SayHello", activity.GetTagValue(SemanticConventions.AttributeHttpTarget));
- Assert.Equal($"http://localhost:{this.server.Port}/greet.Greeter/SayHello", activity.GetTagValue(SemanticConventions.AttributeHttpUrl));
- Assert.StartsWith("grpc-dotnet", activity.GetTagValue(SemanticConventions.AttributeHttpUserAgent) as string);
-
- // NEW
- if (!enableGrpcAspNetCoreSupport.HasValue || enableGrpcAspNetCoreSupport.Value)
- {
- Assert.Equal("grpc", activity.GetTagValue(SemanticConventions.AttributeRpcSystem));
- Assert.Equal("greet.Greeter", activity.GetTagValue(SemanticConventions.AttributeRpcService));
- Assert.Equal("SayHello", activity.GetTagValue(SemanticConventions.AttributeRpcMethod));
- Assert.Contains(activity.GetTagValue(SemanticConventions.AttributeClientAddress), clientLoopbackAddresses);
- Assert.NotEqual(0, activity.GetTagValue(SemanticConventions.AttributeClientPort));
- Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
- Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
- Assert.Equal(0, activity.GetTagValue(SemanticConventions.AttributeRpcGrpcStatusCode));
- }
- else
- {
- Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
- Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
- }
-
- Assert.Equal(Status.Unset, activity.GetStatus());
-
- // The following are http.* attributes that are also included on the span for the gRPC invocation.
- Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeServerAddress));
- Assert.Equal(this.server.Port, activity.GetTagValue(SemanticConventions.AttributeServerPort));
- Assert.Equal("POST", activity.GetTagValue(SemanticConventions.AttributeHttpRequestMethod));
- Assert.Equal("http", activity.GetTagValue(SemanticConventions.AttributeUrlScheme));
- Assert.Equal("/greet.Greeter/SayHello", activity.GetTagValue(SemanticConventions.AttributeUrlPath));
- Assert.Equal("2", activity.GetTagValue(SemanticConventions.AttributeNetworkProtocolVersion));
- Assert.StartsWith("grpc-dotnet", activity.GetTagValue(SemanticConventions.AttributeUserAgentOriginal) as string);
- }
-
#if NET6_0_OR_GREATER
[Theory(Skip = "Skipping for .NET 6 and higher due to bug #3023")]
#endif
diff --git a/test/OpenTelemetry.Instrumentation.Grpc.Tests/Services/GreeterService.cs b/test/OpenTelemetry.Instrumentation.Grpc.Tests/Services/GreeterService.cs
index cf444b2a2c5..70c6ff5636e 100644
--- a/test/OpenTelemetry.Instrumentation.Grpc.Tests/Services/GreeterService.cs
+++ b/test/OpenTelemetry.Instrumentation.Grpc.Tests/Services/GreeterService.cs
@@ -42,10 +42,10 @@ public override async Task SayHellos(HelloRequest request, IServerStreamWriter
using System.Diagnostics;
-using Microsoft.Extensions.Configuration;
#if NETFRAMEWORK
using System.Net;
using System.Net.Http;
@@ -30,8 +29,6 @@
using Xunit;
using Xunit.Abstractions;
-using static OpenTelemetry.Internal.HttpSemanticConventionHelper;
-
namespace OpenTelemetry.Instrumentation.Http.Tests;
public partial class HttpClientTests : IDisposable
@@ -174,7 +171,7 @@ public async Task InjectsHeadersAsync(bool shouldEnrich)
.Build())
{
using var c = new HttpClient();
- await c.SendAsync(request).ConfigureAwait(false);
+ await c.SendAsync(request);
}
Assert.Equal(5, processor.Invocations.Count); // SetParentProvider/OnStart/OnEnd/OnShutdown/Dispose called.
@@ -248,7 +245,7 @@ public async Task InjectsHeadersAsync_CustomFormat()
.Build())
{
using var c = new HttpClient();
- await c.SendAsync(request).ConfigureAwait(false);
+ await c.SendAsync(request);
}
Assert.Equal(5, processor.Invocations.Count); // SetParentProvider/OnStart/OnEnd/OnShutdown/Dispose called.
@@ -318,7 +315,7 @@ public async Task RespectsSuppress()
using var c = new HttpClient();
using (SuppressInstrumentationScope.Begin())
{
- await c.SendAsync(request).ConfigureAwait(false);
+ await c.SendAsync(request);
}
}
@@ -357,7 +354,7 @@ public async Task ExportsSpansCreatedForRetries()
using var clientHandler = new HttpClientHandler();
using var retryHandler = new RetryHandler(clientHandler, maxRetries);
using var httpClient = new HttpClient(retryHandler);
- await httpClient.SendAsync(request).ConfigureAwait(false);
+ await httpClient.SendAsync(request);
// number of exported spans should be 3(maxRetries)
Assert.Equal(maxRetries, exportedItems.Count());
@@ -393,12 +390,7 @@ public async Task HttpRequestMethodIsSetOnActivityAsPerSpec(string originalMetho
Method = new HttpMethod(originalMethod),
};
- var configuration = new ConfigurationBuilder()
- .AddInMemoryCollection(new Dictionary { [SemanticConventionOptInKeyName] = "http" })
- .Build();
-
using var traceprovider = Sdk.CreateTracerProviderBuilder()
- .ConfigureServices(services => services.AddSingleton(configuration))
.AddHttpClientInstrumentation()
.AddInMemoryExporter(exportedItems)
.Build();
@@ -407,7 +399,7 @@ public async Task HttpRequestMethodIsSetOnActivityAsPerSpec(string originalMetho
try
{
- await httpClient.SendAsync(request).ConfigureAwait(false);
+ await httpClient.SendAsync(request);
}
catch
{
@@ -453,12 +445,7 @@ public async Task HttpRequestMethodIsSetonRequestDurationMetricAsPerSpec(string
Method = new HttpMethod(originalMethod),
};
- var configuration = new ConfigurationBuilder()
- .AddInMemoryCollection(new Dictionary { [SemanticConventionOptInKeyName] = "http" })
- .Build();
-
using var meterprovider = Sdk.CreateMeterProviderBuilder()
- .ConfigureServices(services => services.AddSingleton(configuration))
.AddHttpClientInstrumentation()
.AddInMemoryExporter(metricItems)
.Build();
@@ -467,7 +454,7 @@ public async Task HttpRequestMethodIsSetonRequestDurationMetricAsPerSpec(string
try
{
- await httpClient.SendAsync(request).ConfigureAwait(false);
+ await httpClient.SendAsync(request);
}
catch
{
@@ -511,7 +498,7 @@ public async Task RedirectTest()
.Build())
{
using var c = new HttpClient();
- await c.GetAsync($"{this.url}redirect").ConfigureAwait(false);
+ await c.GetAsync($"{this.url}redirect");
}
#if NETFRAMEWORK
@@ -523,15 +510,15 @@ public async Task RedirectTest()
Assert.Equal(5, processor.Invocations.Count); // SetParentProvider/OnStart/OnEnd/OnShutdown/Dispose called.
var firstActivity = (Activity)processor.Invocations[2].Arguments[0]; // First OnEnd
- Assert.Contains(firstActivity.TagObjects, t => t.Key == "http.status_code" && (int)t.Value == 200);
+ Assert.Contains(firstActivity.TagObjects, t => t.Key == "http.response.status_code" && (int)t.Value == 200);
#else
Assert.Equal(7, processor.Invocations.Count); // SetParentProvider/OnStart/OnEnd/OnStart/OnEnd/OnShutdown/Dispose called.
var firstActivity = (Activity)processor.Invocations[2].Arguments[0]; // First OnEnd
- Assert.Contains(firstActivity.TagObjects, t => t.Key == "http.status_code" && (int)t.Value == 302);
+ Assert.Contains(firstActivity.TagObjects, t => t.Key == "http.response.status_code" && (int)t.Value == 302);
var secondActivity = (Activity)processor.Invocations[4].Arguments[0]; // Second OnEnd
- Assert.Contains(secondActivity.TagObjects, t => t.Key == "http.status_code" && (int)t.Value == 200);
+ Assert.Contains(secondActivity.TagObjects, t => t.Key == "http.response.status_code" && (int)t.Value == 200);
#endif
}
@@ -562,7 +549,7 @@ public async void RequestNotCollectedWhenInstrumentationFilterApplied()
.Build())
{
using var c = new HttpClient();
- await c.GetAsync(this.url).ConfigureAwait(false);
+ await c.GetAsync(this.url);
}
#if NETFRAMEWORK
@@ -593,7 +580,7 @@ public async void RequestNotCollectedWhenInstrumentationFilterThrowsException()
{
using var c = new HttpClient();
using var inMemoryEventListener = new InMemoryEventListener(HttpInstrumentationEventSource.Log);
- await c.GetAsync(this.url).ConfigureAwait(false);
+ await c.GetAsync(this.url);
Assert.Single(inMemoryEventListener.Events.Where((e) => e.EventId == 4));
}
@@ -614,7 +601,7 @@ public async Task ReportsExceptionEventForNetworkFailuresWithGetAsync()
using var c = new HttpClient();
try
{
- await c.GetAsync("https://sdlfaldfjalkdfjlkajdflkajlsdjf.sdlkjafsdjfalfadslkf.com/").ConfigureAwait(false);
+ await c.GetAsync("https://sdlfaldfjalkdfjlkajdflkajlsdjf.sdlkjafsdjfalfadslkf.com/");
}
catch
{
@@ -640,7 +627,7 @@ public async Task DoesNotReportExceptionEventOnErrorResponseWithGetAsync()
using var c = new HttpClient();
try
{
- await c.GetAsync($"{this.url}500").ConfigureAwait(false);
+ await c.GetAsync($"{this.url}500");
}
catch
{
@@ -671,7 +658,7 @@ public async Task DoesNotReportExceptionEventOnErrorResponseWithGetStringAsync()
using var c = new HttpClient();
try
{
- await c.GetStringAsync($"{this.url}500").ConfigureAwait(false);
+ await c.GetStringAsync($"{this.url}500");
}
catch
{
@@ -746,7 +733,7 @@ public async Task CustomPropagatorCalled(bool sample, bool createParentActivity)
};
using var c = new HttpClient();
- await c.SendAsync(request).ConfigureAwait(false);
+ await c.SendAsync(request);
parent?.Stop();
diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.cs
index c957c14840d..b7d381c56ef 100644
--- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.cs
+++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.cs
@@ -22,12 +22,9 @@
using System.Reflection;
using System.Text.Json;
#endif
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using Xunit;
-using static OpenTelemetry.Internal.HttpSemanticConventionHelper;
namespace OpenTelemetry.Instrumentation.Http.Tests;
@@ -35,45 +32,16 @@ public partial class HttpClientTests
{
public static readonly IEnumerable TestData = HttpTestData.ReadTestCases();
-#if !NET8_0_OR_GREATER
- [Theory]
- [MemberData(nameof(TestData))]
- public async Task HttpOutCallsAreCollectedSuccessfullyTracesAndMetricsOldSemanticConventionsAsync(HttpTestData.HttpOutTestCase tc)
- {
- await HttpOutCallsAreCollectedSuccessfullyBodyAsync(
- this.host,
- this.port,
- tc,
- enableTracing: true,
- enableMetrics: true,
- semanticConvention: HttpSemanticConvention.Old).ConfigureAwait(false);
- }
-
[Theory]
[MemberData(nameof(TestData))]
- public async Task HttpOutCallsAreCollectedSuccessfullyTracesAndMetricsDuplicateSemanticConventionsAsync(HttpTestData.HttpOutTestCase tc)
+ public async Task HttpOutCallsAreCollectedSuccessfullyTracesAndMetricsSemanticConventionsAsync(HttpTestData.HttpOutTestCase tc)
{
await HttpOutCallsAreCollectedSuccessfullyBodyAsync(
this.host,
this.port,
tc,
enableTracing: true,
- enableMetrics: true,
- semanticConvention: HttpSemanticConvention.Dupe).ConfigureAwait(false);
- }
-#endif
-
- [Theory]
- [MemberData(nameof(TestData))]
- public async Task HttpOutCallsAreCollectedSuccessfullyTracesAndMetricsNewSemanticConventionsAsync(HttpTestData.HttpOutTestCase tc)
- {
- await HttpOutCallsAreCollectedSuccessfullyBodyAsync(
- this.host,
- this.port,
- tc,
- enableTracing: true,
- enableMetrics: true,
- semanticConvention: HttpSemanticConvention.New).ConfigureAwait(false);
+ enableMetrics: true);
}
[Theory]
@@ -85,7 +53,7 @@ await HttpOutCallsAreCollectedSuccessfullyBodyAsync(
this.port,
tc,
enableTracing: false,
- enableMetrics: true).ConfigureAwait(false);
+ enableMetrics: true);
}
[Theory]
@@ -97,7 +65,7 @@ await HttpOutCallsAreCollectedSuccessfullyBodyAsync(
this.port,
tc,
enableTracing: true,
- enableMetrics: false).ConfigureAwait(false);
+ enableMetrics: false);
}
[Theory]
@@ -109,7 +77,7 @@ await HttpOutCallsAreCollectedSuccessfullyBodyAsync(
this.port,
tc,
enableTracing: false,
- enableMetrics: false).ConfigureAwait(false);
+ enableMetrics: false);
}
#if !NET8_0_OR_GREATER
@@ -129,29 +97,29 @@ public async Task DebugIndividualTestAsync()
""spanStatus"": ""Unset"",
""spanKind"": ""Client"",
""spanAttributes"": {
- ""http.scheme"": ""http"",
- ""http.method"": ""GET"",
- ""net.peer.name"": ""{host}"",
- ""net.peer.port"": ""{port}"",
- ""http.status_code"": ""399"",
- ""http.flavor"": ""{flavor}"",
- ""http.url"": ""http://{host}:{port}/""
+ ""url.scheme"": ""http"",
+ ""http.request.method"": ""GET"",
+ ""server.address"": ""{host}"",
+ ""server.port"": ""{port}"",
+ ""http.response.status_code"": ""399"",
+ ""network.protocol.version"": ""{flavor}"",
+ ""url.full"": ""http://{host}:{port}/""
}
}
]
",
new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
- var t = (Task)this.GetType().InvokeMember(nameof(this.HttpOutCallsAreCollectedSuccessfullyTracesAndMetricsOldSemanticConventionsAsync), BindingFlags.InvokeMethod, null, this, HttpTestData.GetArgumentsFromTestCaseObject(input).First());
- await t.ConfigureAwait(false);
+ var t = (Task)this.GetType().InvokeMember(nameof(this.HttpOutCallsAreCollectedSuccessfullyTracesAndMetricsSemanticConventionsAsync), BindingFlags.InvokeMethod, null, this, HttpTestData.GetArgumentsFromTestCaseObject(input).First());
+ await t;
}
#endif
[Fact]
public async Task CheckEnrichmentWhenSampling()
{
- await CheckEnrichment(new AlwaysOffSampler(), false, this.url).ConfigureAwait(false);
- await CheckEnrichment(new AlwaysOnSampler(), true, this.url).ConfigureAwait(false);
+ await CheckEnrichment(new AlwaysOffSampler(), false, this.url);
+ await CheckEnrichment(new AlwaysOnSampler(), true, this.url);
}
#if NET8_0_OR_GREATER
@@ -178,7 +146,7 @@ public async Task ValidateNet8MetricsAsync(HttpTestData.HttpOutTestCase tc)
request.Headers.Add("contextRequired", "false");
request.Headers.Add("responseCode", (tc.ResponseCode == 0 ? 200 : tc.ResponseCode).ToString());
- await c.SendAsync(request).ConfigureAwait(false);
+ await c.SendAsync(request);
}
catch (Exception)
{
@@ -189,8 +157,6 @@ public async Task ValidateNet8MetricsAsync(HttpTestData.HttpOutTestCase tc)
meterProvider.Dispose();
}
- // dns.lookups.duration is a typo
- // https://github.com/dotnet/runtime/issues/92917
var requestMetrics = metrics
.Where(metric =>
metric.Name == "http.client.request.duration" ||
@@ -198,7 +164,7 @@ public async Task ValidateNet8MetricsAsync(HttpTestData.HttpOutTestCase tc)
metric.Name == "http.client.request.time_in_queue" ||
metric.Name == "http.client.connection.duration" ||
metric.Name == "http.client.open_connections" ||
- metric.Name == "dns.lookups.duration")
+ metric.Name == "dns.lookup.duration")
.ToArray();
if (tc.ResponseExpected)
@@ -218,8 +184,7 @@ private static async Task HttpOutCallsAreCollectedSuccessfullyBodyAsync(
int port,
HttpTestData.HttpOutTestCase tc,
bool enableTracing,
- bool enableMetrics,
- HttpSemanticConvention? semanticConvention = null)
+ bool enableMetrics)
{
bool enrichWithHttpWebRequestCalled = false;
bool enrichWithHttpWebResponseCalled = false;
@@ -234,9 +199,7 @@ private static async Task HttpOutCallsAreCollectedSuccessfullyBodyAsync(
if (enableMetrics)
{
meterProviderBuilder
- .AddHttpClientInstrumentation()
- .ConfigureServices(
- s => s.AddSingleton(BuildConfigurationWithSemanticConventionOptIn(semanticConvention)));
+ .AddHttpClientInstrumentation();
}
var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder();
@@ -252,9 +215,7 @@ private static async Task HttpOutCallsAreCollectedSuccessfullyBodyAsync(
opt.EnrichWithHttpResponseMessage = (activity, httpResponseMessage) => { enrichWithHttpResponseMessageCalled = true; };
opt.EnrichWithException = (activity, exception) => { enrichWithExceptionCalled = true; };
opt.RecordException = tc.RecordException ?? false;
- })
- .ConfigureServices(
- s => s.AddSingleton(BuildConfigurationWithSemanticConventionOptIn(semanticConvention)));
+ });
}
var metrics = new List();
@@ -288,7 +249,7 @@ private static async Task HttpOutCallsAreCollectedSuccessfullyBodyAsync(
request.Headers.Add("contextRequired", "false");
request.Headers.Add("responseCode", (tc.ResponseCode == 0 ? 200 : tc.ResponseCode).ToString());
- await c.SendAsync(request).ConfigureAwait(false);
+ await c.SendAsync(request);
}
catch (Exception)
{
@@ -301,7 +262,7 @@ private static async Task HttpOutCallsAreCollectedSuccessfullyBodyAsync(
}
var requestMetrics = metrics
- .Where(metric => metric.Name == "http.client.duration" || metric.Name == "http.client.request.duration")
+ .Where(metric => metric.Name == "http.client.request.duration")
.ToArray();
var normalizedAttributesTestCase = tc.SpanAttributes.ToDictionary(x => x.Key, x => HttpTestData.NormalizeValues(x.Value, host, port));
@@ -341,65 +302,40 @@ private static async Task HttpOutCallsAreCollectedSuccessfullyBodyAsync(
var normalizedAttributes = activity.TagObjects.Where(kv => !kv.Key.StartsWith("otel.")).ToDictionary(x => x.Key, x => x.Value.ToString());
- int numberOfNewTags = activity.Status == ActivityStatusCode.Error ? 6 : 5;
- int numberOfDupeTags = activity.Status == ActivityStatusCode.Error ? 12 : 11;
+ int numberOfTags = activity.Status == ActivityStatusCode.Error ? 5 : 4;
- var expectedAttributeCount = semanticConvention == HttpSemanticConvention.Dupe
- ? numberOfDupeTags + (tc.ResponseExpected ? 2 : 0)
- : semanticConvention == HttpSemanticConvention.New
- ? numberOfNewTags + (tc.ResponseExpected ? 1 : 0)
- : 6 + (tc.ResponseExpected ? 1 : 0);
+ var expectedAttributeCount = numberOfTags + (tc.ResponseExpected ? 2 : 0);
Assert.Equal(expectedAttributeCount, normalizedAttributes.Count);
- if (semanticConvention == null || semanticConvention.Value.HasFlag(HttpSemanticConvention.Old))
+ Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeHttpRequestMethod && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpRequestMethod]);
+ Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeServerAddress && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeServerAddress]);
+ Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeServerPort && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeServerPort]);
+ Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeUrlFull && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeUrlFull]);
+ if (tc.ResponseExpected)
{
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeHttpMethod && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpMethod]);
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeNetPeerName && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeNetPeerName]);
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeNetPeerPort && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeNetPeerPort]);
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeHttpScheme && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpScheme]);
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeHttpUrl && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpUrl]);
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeHttpFlavor && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpFlavor]);
- if (tc.ResponseExpected)
- {
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeHttpStatusCode && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpStatusCode]);
- }
- else
+ Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeNetworkProtocolVersion && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeNetworkProtocolVersion]);
+ Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeHttpResponseStatusCode && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpResponseStatusCode]);
+
+ if (tc.ResponseCode >= 400)
{
- Assert.DoesNotContain(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeHttpStatusCode);
+ Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpResponseStatusCode]);
}
}
-
- if (semanticConvention != null && semanticConvention.Value.HasFlag(HttpSemanticConvention.New))
+ else
{
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeHttpRequestMethod && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpMethod]);
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeServerAddress && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeNetPeerName]);
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeServerPort && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeNetPeerPort]);
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeUrlFull && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpUrl]);
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeNetworkProtocolVersion && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpFlavor]);
- if (tc.ResponseExpected)
- {
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeHttpResponseStatusCode && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpStatusCode]);
-
- if (tc.ResponseCode >= 400)
- {
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpStatusCode]);
- }
- }
- else
- {
- Assert.DoesNotContain(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeHttpResponseStatusCode);
+ Assert.DoesNotContain(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeHttpResponseStatusCode);
+ Assert.DoesNotContain(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeNetworkProtocolVersion);
#if NET8_0_OR_GREATER
- // we are using fake address so it will be "name_resolution_error"
- // TODO: test other error types.
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == "name_resolution_error");
+ // we are using fake address so it will be "name_resolution_error"
+ // TODO: test other error types.
+ Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == "name_resolution_error");
#elif NETFRAMEWORK
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == "name_resolution_failure");
+ Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == "name_resolution_failure");
#else
- Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == "System.Net.Http.HttpRequestException");
+ Assert.Contains(normalizedAttributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == "System.Net.Http.HttpRequestException");
#endif
- }
}
if (tc.RecordException.HasValue && tc.RecordException.Value)
@@ -415,215 +351,113 @@ private static async Task HttpOutCallsAreCollectedSuccessfullyBodyAsync(
}
else
{
- if (semanticConvention == HttpSemanticConvention.Dupe)
- {
- Assert.Equal(2, requestMetrics.Length);
- }
- else
- {
- Assert.Single(requestMetrics);
- }
-
-#if !NET8_0_OR_GREATER
- if (semanticConvention == null || semanticConvention.Value.HasFlag(HttpSemanticConvention.Old))
- {
- var metric = requestMetrics.FirstOrDefault(m => m.Name == "http.client.duration");
- Assert.NotNull(metric);
- Assert.Equal("ms", metric.Unit);
- Assert.True(metric.MetricType == MetricType.Histogram);
-
- var metricPoints = new List();
- foreach (var p in metric.GetMetricPoints())
- {
- metricPoints.Add(p);
- }
-
- Assert.Single(metricPoints);
- var metricPoint = metricPoints[0];
+ Assert.Single(requestMetrics);
- var count = metricPoint.GetHistogramCount();
- var sum = metricPoint.GetHistogramSum();
+ var metric = requestMetrics.FirstOrDefault(m => m.Name == "http.client.request.duration");
+ Assert.NotNull(metric);
+ Assert.Equal("s", metric.Unit);
+ Assert.True(metric.MetricType == MetricType.Histogram);
- Assert.Equal(1L, count);
-
- if (enableTracing)
- {
- var activity = Assert.Single(activities);
- Assert.Equal(activity.Duration.TotalMilliseconds, sum);
- }
- else
- {
- Assert.True(sum > 0);
- }
-
- // Inspect Metric Attributes
- var attributes = new Dictionary();
- foreach (var tag in metricPoint.Tags)
- {
- attributes[tag.Key] = tag.Value;
- }
-
- var expectedAttributeCount = 5 + (tc.ResponseExpected ? 1 : 0);
-
- Assert.Equal(expectedAttributeCount, attributes.Count);
-
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeHttpMethod && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpMethod]);
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeNetPeerName && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeNetPeerName]);
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeNetPeerPort && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeNetPeerPort]);
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeHttpScheme && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpScheme]);
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeHttpFlavor && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpFlavor]);
- if (tc.ResponseExpected)
- {
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeHttpStatusCode && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpStatusCode]);
- }
- else
- {
- Assert.DoesNotContain(attributes, kvp => kvp.Key == SemanticConventions.AttributeHttpStatusCode);
- }
-
- // Inspect Histogram Bounds
- var histogramBuckets = metricPoint.GetHistogramBuckets();
- var histogramBounds = new List();
- foreach (var t in histogramBuckets)
- {
- histogramBounds.Add(t.ExplicitBound);
- }
-
- Assert.Equal(
- expected: new List { 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000, double.PositiveInfinity },
- actual: histogramBounds);
- }
-#endif
- if (semanticConvention != null && semanticConvention.Value.HasFlag(HttpSemanticConvention.New))
+ var metricPoints = new List();
+ foreach (var p in metric.GetMetricPoints())
{
- var metric = requestMetrics.FirstOrDefault(m => m.Name == "http.client.request.duration");
- Assert.NotNull(metric);
- Assert.Equal("s", metric.Unit);
- Assert.True(metric.MetricType == MetricType.Histogram);
-
- var metricPoints = new List();
- foreach (var p in metric.GetMetricPoints())
- {
- metricPoints.Add(p);
- }
+ metricPoints.Add(p);
+ }
- Assert.Single(metricPoints);
- var metricPoint = metricPoints[0];
+ Assert.Single(metricPoints);
+ var metricPoint = metricPoints[0];
- var count = metricPoint.GetHistogramCount();
- var sum = metricPoint.GetHistogramSum();
+ var count = metricPoint.GetHistogramCount();
+ var sum = metricPoint.GetHistogramSum();
- Assert.Equal(1L, count);
+ Assert.Equal(1L, count);
- if (enableTracing)
- {
- var activity = Assert.Single(activities);
+ if (enableTracing)
+ {
+ var activity = Assert.Single(activities);
#if !NET8_0_OR_GREATER
- Assert.Equal(activity.Duration.TotalSeconds, sum);
+ Assert.Equal(activity.Duration.TotalSeconds, sum);
#endif
- }
- else
- {
- Assert.True(sum > 0);
- }
+ }
+ else
+ {
+ Assert.True(sum > 0);
+ }
- // Inspect Metric Attributes
- var attributes = new Dictionary();
- foreach (var tag in metricPoint.Tags)
- {
- attributes[tag.Key] = tag.Value;
- }
+ // Inspect Metric Attributes
+ var attributes = new Dictionary();
+ foreach (var tag in metricPoint.Tags)
+ {
+ attributes[tag.Key] = tag.Value;
+ }
-#if !NET8_0_OR_GREATER
- var numberOfTags = 6;
-#else
- // network.protocol.version is not emitted when response if not received.
- // https://github.com/open-telemetry/opentelemetry-dotnet/issues/4928
- var numberOfTags = 5;
-#endif
- if (tc.ResponseExpected)
- {
- var expectedStatusCode = int.Parse(normalizedAttributesTestCase[SemanticConventions.AttributeHttpStatusCode]);
- numberOfTags = (expectedStatusCode >= 400) ? 6 : 5;
- }
+ var numberOfTags = 4;
+ if (tc.ResponseExpected)
+ {
+ var expectedStatusCode = int.Parse(normalizedAttributesTestCase[SemanticConventions.AttributeHttpResponseStatusCode]);
+ numberOfTags = (expectedStatusCode >= 400) ? 5 : 4; // error.type extra tag
+ }
+ else
+ {
+ numberOfTags = 5; // error.type would be extra
+ }
- var expectedAttributeCount = numberOfTags + (tc.ResponseExpected ? 1 : 0);
+ var expectedAttributeCount = numberOfTags + (tc.ResponseExpected ? 2 : 0); // responsecode + protocolversion
- Assert.Equal(expectedAttributeCount, attributes.Count);
+ Assert.Equal(expectedAttributeCount, attributes.Count);
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeHttpRequestMethod && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpMethod]);
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeServerAddress && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeNetPeerName]);
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeServerPort && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeNetPeerPort]);
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeUrlScheme && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpScheme]);
-#if !NET8_0_OR_GREATER
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeNetworkProtocolVersion && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpFlavor]);
-#endif
+ Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeHttpRequestMethod && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpRequestMethod]);
+ Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeServerAddress && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeServerAddress]);
+ Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeServerPort && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeServerPort]);
+ Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeUrlScheme && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeUrlScheme]);
- if (tc.ResponseExpected)
- {
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeHttpResponseStatusCode && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpStatusCode]);
+ if (tc.ResponseExpected)
+ {
+ Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeNetworkProtocolVersion && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeNetworkProtocolVersion]);
+ Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeHttpResponseStatusCode && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpResponseStatusCode]);
- if (tc.ResponseCode >= 400)
- {
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpStatusCode]);
- }
- }
- else
+ if (tc.ResponseCode >= 400)
{
- Assert.DoesNotContain(attributes, kvp => kvp.Key == SemanticConventions.AttributeHttpResponseStatusCode);
+ Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == normalizedAttributesTestCase[SemanticConventions.AttributeHttpResponseStatusCode]);
+ }
+ }
+ else
+ {
+ Assert.DoesNotContain(attributes, kvp => kvp.Key == SemanticConventions.AttributeNetworkProtocolVersion);
+ Assert.DoesNotContain(attributes, kvp => kvp.Key == SemanticConventions.AttributeHttpResponseStatusCode);
#if NET8_0_OR_GREATER
- // we are using fake address so it will be "name_resolution_error"
- // TODO: test other error types.
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == "name_resolution_error");
+ // we are using fake address so it will be "name_resolution_error"
+ // TODO: test other error types.
+ Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == "name_resolution_error");
#elif NETFRAMEWORK
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == "name_resolution_failure");
+ Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == "name_resolution_failure");
#else
- Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == "System.Net.Http.HttpRequestException");
+ Assert.Contains(attributes, kvp => kvp.Key == SemanticConventions.AttributeErrorType && kvp.Value.ToString() == "System.Net.Http.HttpRequestException");
#endif
- }
+ }
- // Inspect Histogram Bounds
- var histogramBuckets = metricPoint.GetHistogramBuckets();
- var histogramBounds = new List();
- foreach (var t in histogramBuckets)
- {
- histogramBounds.Add(t.ExplicitBound);
- }
+ // Inspect Histogram Bounds
+ var histogramBuckets = metricPoint.GetHistogramBuckets();
+ var histogramBounds = new List();
+ foreach (var t in histogramBuckets)
+ {
+ histogramBounds.Add(t.ExplicitBound);
+ }
- // TODO: Remove the check for the older bounds once 1.7.0 is released. This is a temporary fix for instrumentation libraries CI workflow.
+ // TODO: Remove the check for the older bounds once 1.7.0 is released. This is a temporary fix for instrumentation libraries CI workflow.
- var expectedHistogramBoundsOld = new List { 0, 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, double.PositiveInfinity };
- var expectedHistogramBoundsNew = new List { 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, double.PositiveInfinity };
+ var expectedHistogramBoundsOld = new List { 0, 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, double.PositiveInfinity };
+ var expectedHistogramBoundsNew = new List { 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, double.PositiveInfinity };
- var histogramBoundsMatchCorrectly = Enumerable.SequenceEqual(expectedHistogramBoundsOld, histogramBounds) ||
- Enumerable.SequenceEqual(expectedHistogramBoundsNew, histogramBounds);
+ var histogramBoundsMatchCorrectly = Enumerable.SequenceEqual(expectedHistogramBoundsOld, histogramBounds) ||
+ Enumerable.SequenceEqual(expectedHistogramBoundsNew, histogramBounds);
- Assert.True(histogramBoundsMatchCorrectly);
- }
+ Assert.True(histogramBoundsMatchCorrectly);
}
}
- private static IConfiguration BuildConfigurationWithSemanticConventionOptIn(
- HttpSemanticConvention? semanticConvention)
- {
- var builder = new ConfigurationBuilder();
-
- if (semanticConvention != null && semanticConvention != HttpSemanticConvention.Old)
- {
- builder.AddInMemoryCollection(
- new Dictionary
- {
- ["OTEL_SEMCONV_STABILITY_OPT_IN"] = semanticConvention == HttpSemanticConvention.Dupe
- ? "http/dup"
- : "http",
- });
- }
-
- return builder.Build();
- }
-
private static async Task CheckEnrichment(Sampler sampler, bool enrichExpected, string url)
{
bool enrichWithHttpWebRequestCalled = false;
@@ -645,7 +479,7 @@ private static async Task CheckEnrichment(Sampler sampler, bool enrichExpected,
.Build())
{
using var c = new HttpClient();
- using var r = await c.GetAsync(url).ConfigureAwait(false);
+ using var r = await c.GetAsync(url);
}
if (enrichExpected)
diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs
index 074ae7df55c..be4f6a2909e 100644
--- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs
+++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs
@@ -155,7 +155,7 @@ public async Task TestReflectInitializationViaSubscription()
// Send a random Http request to generate some events
using (var client = new HttpClient())
{
- (await client.GetAsync(this.BuildRequestUrl()).ConfigureAwait(false)).Dispose();
+ (await client.GetAsync(this.BuildRequestUrl())).Dispose();
}
// Just make sure some events are written, to confirm we successfully subscribed to it.
@@ -180,8 +180,8 @@ public async Task TestBasicReceiveAndResponseEvents(string method, string queryS
using (var client = new HttpClient())
{
(method == "GET"
- ? await client.GetAsync(url).ConfigureAwait(false)
- : await client.PostAsync(url, new StringContent("hello world")).ConfigureAwait(false)).Dispose();
+ ? await client.GetAsync(url)
+ : await client.PostAsync(url, new StringContent("hello world"))).Dispose();
}
// We should have exactly one Start and one Stop event
@@ -211,8 +211,8 @@ public async Task TestBasicReceiveAndResponseEventsWithoutSampling(string method
using (var client = new HttpClient())
{
(method == "GET"
- ? await client.GetAsync(this.BuildRequestUrl()).ConfigureAwait(false)
- : await client.PostAsync(this.BuildRequestUrl(), new StringContent("hello world")).ConfigureAwait(false)).Dispose();
+ ? await client.GetAsync(this.BuildRequestUrl())
+ : await client.PostAsync(this.BuildRequestUrl(), new StringContent("hello world"))).Dispose();
}
// There should be no events because we turned off sampling.
@@ -248,7 +248,7 @@ public async Task TestBasicReceiveAndResponseWebRequestEvents(string method, int
stream = webRequest.GetRequestStream();
break;
case 1:
- stream = await webRequest.GetRequestStreamAsync().ConfigureAwait(false);
+ stream = await webRequest.GetRequestStreamAsync();
break;
case 2:
{
@@ -310,7 +310,7 @@ public async Task TestBasicReceiveAndResponseWebRequestEvents(string method, int
webResponse = webRequest.GetResponse();
break;
case 1:
- webResponse = await webRequest.GetResponseAsync().ConfigureAwait(false);
+ webResponse = await webRequest.GetResponseAsync();
break;
case 2:
{
@@ -397,7 +397,7 @@ public async Task TestTraceStateAndBaggage()
// Send a random Http request to generate some events
using (var client = new HttpClient())
{
- (await client.GetAsync(this.BuildRequestUrl()).ConfigureAwait(false)).Dispose();
+ (await client.GetAsync(this.BuildRequestUrl())).Dispose();
}
parent.Stop();
@@ -438,7 +438,7 @@ public async Task DoNotInjectTraceParentWhenPresent(string method)
request.Content = new StringContent("hello world");
}
- (await client.SendAsync(request).ConfigureAwait(false)).Dispose();
+ (await client.SendAsync(request)).Dispose();
}
// No events are sent.
@@ -466,8 +466,8 @@ public async Task TestResponseWithoutContentEvents(string method)
using (var client = new HttpClient())
{
using HttpResponseMessage response = method == "GET"
- ? await client.GetAsync(url).ConfigureAwait(false)
- : await client.PostAsync(url, new StringContent("hello world")).ConfigureAwait(false);
+ ? await client.GetAsync(url)
+ : await client.PostAsync(url, new StringContent("hello world"));
}
// We should have exactly one Start and one Stop event
@@ -499,8 +499,8 @@ public async Task TestRedirectedRequest(string method)
using (var client = new HttpClient())
{
using HttpResponseMessage response = method == "GET"
- ? await client.GetAsync(this.BuildRequestUrl(queryString: "redirects=10")).ConfigureAwait(false)
- : await client.PostAsync(this.BuildRequestUrl(queryString: "redirects=10"), new StringContent("hello world")).ConfigureAwait(false);
+ ? await client.GetAsync(this.BuildRequestUrl(queryString: "redirects=10"))
+ : await client.PostAsync(this.BuildRequestUrl(queryString: "redirects=10"), new StringContent("hello world"));
}
// We should have exactly one Start and one Stop event
@@ -529,7 +529,7 @@ public async Task TestRequestWithException(string method)
return method == "GET"
? new HttpClient().GetAsync(url)
: new HttpClient().PostAsync(url, new StringContent("hello world"));
- }).ConfigureAwait(false);
+ });
// check that request failed because of the wrong domain name and not because of reflection
var webException = (WebException)ex.InnerException;
@@ -572,7 +572,7 @@ public async Task TestCanceledRequest(string method)
return method == "GET"
? client.GetAsync(url, cts.Token)
: client.PostAsync(url, new StringContent("hello world"), cts.Token);
- }).ConfigureAwait(false);
+ });
Assert.True(ex is TaskCanceledException || ex is WebException);
}
@@ -611,7 +611,7 @@ public async Task TestSecureTransportFailureRequest(string method)
return method == "GET"
? client.GetAsync(url)
: client.PostAsync(url, new StringContent("hello world"));
- }).ConfigureAwait(false);
+ });
Assert.True(ex is HttpRequestException);
}
@@ -653,7 +653,7 @@ public async Task TestSecureTransportRetryFailureRequest(string method)
return method == "GET"
? client.GetAsync(url)
: client.PostAsync(url, new StringContent("hello world"));
- }).ConfigureAwait(false);
+ });
Assert.True(ex is HttpRequestException);
}
@@ -685,7 +685,7 @@ public async Task TestInvalidBaggage()
using (var client = new HttpClient())
{
- (await client.GetAsync(this.BuildRequestUrl()).ConfigureAwait(false)).Dispose();
+ (await client.GetAsync(this.BuildRequestUrl())).Dispose();
}
Assert.Equal(2, eventRecords.Records.Count());
@@ -699,7 +699,7 @@ public async Task TestInvalidBaggage()
/// Test to make sure every event record has the right dynamic properties.
///
[Fact]
- public void TestMultipleConcurrentRequests()
+ public async Task TestMultipleConcurrentRequests()
{
ServicePointManager.DefaultConnectionLimit = int.MaxValue;
using var parentActivity = new Activity("parent").Start();
@@ -724,13 +724,13 @@ public void TestMultipleConcurrentRequests()
}
// wait up to 10 sec for all requests and suppress exceptions
- Task.WhenAll(tasks.Select(t => t.Value).ToArray()).ContinueWith(tt =>
+ await Task.WhenAll(tasks.Select(t => t.Value).ToArray()).ContinueWith(async tt =>
{
foreach (var task in tasks)
{
- task.Value.Result?.Dispose();
+ (await task.Value)?.Dispose();
}
- }).Wait();
+ });
// Examine the result. Make sure we got all successful requests.
@@ -776,20 +776,20 @@ private static void VerifyHeaders(HttpWebRequest startRequest)
private static void VerifyActivityStartTags(string netPeerName, int? netPeerPort, string method, string url, Activity activity)
{
Assert.NotNull(activity.TagObjects);
- Assert.Equal(method, activity.GetTagValue(SemanticConventions.AttributeHttpMethod));
+ Assert.Equal(method, activity.GetTagValue(SemanticConventions.AttributeHttpRequestMethod));
if (netPeerPort != null)
{
- Assert.Equal(netPeerPort, activity.GetTagValue(SemanticConventions.AttributeNetPeerPort));
+ Assert.Equal(netPeerPort, activity.GetTagValue(SemanticConventions.AttributeServerPort));
}
- Assert.Equal(netPeerName, activity.GetTagValue(SemanticConventions.AttributeNetPeerName));
+ Assert.Equal(netPeerName, activity.GetTagValue(SemanticConventions.AttributeServerAddress));
- Assert.Equal(url, activity.GetTagValue(SemanticConventions.AttributeHttpUrl));
+ Assert.Equal(url, activity.GetTagValue(SemanticConventions.AttributeUrlFull));
}
private static void VerifyActivityStopTags(int statusCode, Activity activity)
{
- Assert.Equal(statusCode, activity.GetTagValue(SemanticConventions.AttributeHttpStatusCode));
+ Assert.Equal(statusCode, activity.GetTagValue(SemanticConventions.AttributeHttpResponseStatusCode));
}
private static void ActivityEnrichment(Activity activity, string method, object obj)
diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.cs
index 85ab9988335..40f0cf9a873 100644
--- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.cs
+++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.cs
@@ -87,7 +87,7 @@ public async Task BacksOffIfAlreadyInstrumented()
request.Headers.Add("traceparent", "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01");
- using var response = await request.GetResponseAsync().ConfigureAwait(false);
+ using var response = await request.GetResponseAsync();
#if NETFRAMEWORK
// Note: Back-off is part of the .NET Framework reflection only and
@@ -129,7 +129,7 @@ public async Task RequestNotCollectedWhenInstrumentationFilterApplied()
request.Method = "GET";
- using var response = await request.GetResponseAsync().ConfigureAwait(false);
+ using var response = await request.GetResponseAsync();
#if NETFRAMEWORK
Assert.True(httpWebRequestFilterApplied);
@@ -163,7 +163,7 @@ public async Task RequestNotCollectedWhenInstrumentationFilterThrowsException()
request.Method = "GET";
- using var response = await request.GetResponseAsync().ConfigureAwait(false);
+ using var response = await request.GetResponseAsync();
Assert.Single(inMemoryEventListener.Events.Where((e) => e.EventId == 4));
}
@@ -190,7 +190,7 @@ public async Task InjectsHeadersAsync()
parent.TraceStateString = "k1=v1,k2=v2";
parent.ActivityTraceFlags = ActivityTraceFlags.Recorded;
- using var response = await request.GetResponseAsync().ConfigureAwait(false);
+ using var response = await request.GetResponseAsync();
Assert.Equal(3, activityProcessor.Invocations.Count); // SetParentProvider/Begin/End called
var activity = (Activity)activityProcessor.Invocations[2].Arguments[0];
@@ -272,7 +272,7 @@ public async Task CustomPropagatorCalled(bool sample, bool createParentActivity)
request.Method = "GET";
- using var response = await request.GetResponseAsync().ConfigureAwait(false);
+ using var response = await request.GetResponseAsync();
parent?.Stop();
@@ -345,7 +345,7 @@ public async Task ReportsExceptionEventForNetworkFailures()
request.Method = "GET";
- using var response = await request.GetResponseAsync().ConfigureAwait(false);
+ using var response = await request.GetResponseAsync();
}
catch
{
@@ -374,7 +374,7 @@ public async Task ReportsExceptionEventOnErrorResponse()
request.Method = "GET";
- using var response = await request.GetResponseAsync().ConfigureAwait(false);
+ using var response = await request.GetResponseAsync();
}
catch
{
diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.cs
index 44d79860889..64166560e0a 100644
--- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.cs
+++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.cs
@@ -101,7 +101,7 @@ public void HttpOutCallsAreCollectedSuccessfully(HttpTestData.HttpOutTestCase tc
x => x.Key,
x =>
{
- if (x.Key == "http.flavor")
+ if (x.Key == "network.protocol.version")
{
return "1.1";
}
@@ -127,6 +127,12 @@ public void HttpOutCallsAreCollectedSuccessfully(HttpTestData.HttpOutTestCase tc
continue;
}
+ if (tag.Key == SemanticConventions.AttributeErrorType)
+ {
+ // TODO: Add validation for error.type in test cases.
+ continue;
+ }
+
Assert.Fail($"Tag {tag.Key} was not found in test data.");
}
@@ -173,13 +179,13 @@ public void DebugIndividualTest()
""spanKind"": ""Client"",
""setHttpFlavor"": true,
""spanAttributes"": {
- ""http.scheme"": ""http"",
- ""http.method"": ""GET"",
- ""net.peer.name"": ""{host}"",
- ""net.peer.port"": ""{port}"",
- ""http.flavor"": ""1.1"",
- ""http.status_code"": ""200"",
- ""http.url"": ""http://{host}:{port}/""
+ ""url.scheme"": ""http"",
+ ""http.request.method"": ""GET"",
+ ""server.address"": ""{host}"",
+ ""server.port"": ""{port}"",
+ ""network.protocol.version"": ""1.1"",
+ ""http.response.status_code"": ""200"",
+ ""url.full"": ""http://{host}:{port}/""
}
}
",
diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/RetryHandler.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/RetryHandler.cs
index 1ba12c16768..7d3b28b3b75 100644
--- a/test/OpenTelemetry.Instrumentation.Http.Tests/RetryHandler.cs
+++ b/test/OpenTelemetry.Instrumentation.Http.Tests/RetryHandler.cs
@@ -41,7 +41,7 @@ protected override async Task SendAsync(
try
{
- response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
+ response = await base.SendAsync(request, cancellationToken);
}
catch
{
diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/http-out-test-cases.json b/test/OpenTelemetry.Instrumentation.Http.Tests/http-out-test-cases.json
index d808d5c00f2..056a4d56098 100644
--- a/test/OpenTelemetry.Instrumentation.Http.Tests/http-out-test-cases.json
+++ b/test/OpenTelemetry.Instrumentation.Http.Tests/http-out-test-cases.json
@@ -7,13 +7,13 @@
"spanStatus": "Unset",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "200",
- "http.url": "http://{host}:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "200",
+ "url.full": "http://{host}:{port}/"
}
},
{
@@ -24,13 +24,13 @@
"spanStatus": "Unset",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "POST",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "200",
- "http.url": "http://{host}:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "POST",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "200",
+ "url.full": "http://{host}:{port}/"
}
},
{
@@ -42,13 +42,13 @@
"spanStatus": "Unset",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "200",
- "http.url": "http://{host}:{port}/path/to/resource/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "200",
+ "url.full": "http://{host}:{port}/path/to/resource/"
}
},
{
@@ -60,17 +60,17 @@
"spanStatus": "Unset",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "200",
- "http.url": "http://{host}:{port}/path/to/resource#fragment"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "200",
+ "url.full": "http://{host}:{port}/path/to/resource#fragment"
}
},
{
- "name": "http.url must not contain username nor password",
+ "name": "url.full must not contain username nor password",
"method": "GET",
"url": "http://username:password@{host}:{port}/path/to/resource#fragment",
"responseCode": 200,
@@ -78,13 +78,13 @@
"spanStatus": "Unset",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "200",
- "http.url": "http://{host}:{port}/path/to/resource#fragment"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "200",
+ "url.full": "http://{host}:{port}/path/to/resource#fragment"
}
},
{
@@ -96,12 +96,12 @@
"responseExpected": false,
"recordException": false,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "sdlfaldfjalkdfjlkajdflkajlsdjf",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.url": "http://sdlfaldfjalkdfjlkajdflkajlsdjf:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "sdlfaldfjalkdfjlkajdflkajlsdjf",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "url.full": "http://sdlfaldfjalkdfjlkajdflkajlsdjf:{port}/"
}
},
{
@@ -113,12 +113,12 @@
"responseExpected": false,
"recordException": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "sdlfaldfjalkdfjlkajdflkajlsdjf",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.url": "http://sdlfaldfjalkdfjlkajdflkajlsdjf:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "sdlfaldfjalkdfjlkajdflkajlsdjf",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "url.full": "http://sdlfaldfjalkdfjlkajdflkajlsdjf:{port}/"
}
},
{
@@ -130,13 +130,13 @@
"spanStatus": "Unset",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "200",
- "http.url": "http://{host}:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "200",
+ "url.full": "http://{host}:{port}/"
}
},
{
@@ -148,13 +148,13 @@
"spanStatus": "Unset",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "200",
- "http.url": "http://{host}:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "200",
+ "url.full": "http://{host}:{port}/"
}
},
{
@@ -166,13 +166,13 @@
"spanStatus": "Unset",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "399",
- "http.url": "http://{host}:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "399",
+ "url.full": "http://{host}:{port}/"
}
},
{
@@ -184,13 +184,13 @@
"spanStatus": "Error",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "400",
- "http.url": "http://{host}:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "400",
+ "url.full": "http://{host}:{port}/"
}
},
{
@@ -202,13 +202,13 @@
"spanStatus": "Error",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "401",
- "http.url": "http://{host}:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "401",
+ "url.full": "http://{host}:{port}/"
}
},
{
@@ -220,13 +220,13 @@
"spanStatus": "Error",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "403",
- "http.url": "http://{host}:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "403",
+ "url.full": "http://{host}:{port}/"
}
},
{
@@ -238,13 +238,13 @@
"spanStatus": "Error",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "404",
- "http.url": "http://{host}:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "404",
+ "url.full": "http://{host}:{port}/"
}
},
{
@@ -256,13 +256,13 @@
"spanStatus": "Error",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "429",
- "http.url": "http://{host}:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "429",
+ "url.full": "http://{host}:{port}/"
}
},
{
@@ -274,13 +274,13 @@
"spanStatus": "Error",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "501",
- "http.url": "http://{host}:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "501",
+ "url.full": "http://{host}:{port}/"
}
},
{
@@ -292,13 +292,13 @@
"spanStatus": "Error",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "503",
- "http.url": "http://{host}:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "503",
+ "url.full": "http://{host}:{port}/"
}
},
{
@@ -310,13 +310,13 @@
"spanStatus": "Error",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "504",
- "http.url": "http://{host}:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "504",
+ "url.full": "http://{host}:{port}/"
}
},
{
@@ -328,13 +328,13 @@
"spanStatus": "Unset",
"responseExpected": true,
"spanAttributes": {
- "http.scheme": "http",
- "http.method": "GET",
- "net.peer.name": "{host}",
- "net.peer.port": "{port}",
- "http.flavor": "{flavor}",
- "http.status_code": "200",
- "http.url": "http://{host}:{port}/"
+ "url.scheme": "http",
+ "http.request.method": "GET",
+ "server.address": "{host}",
+ "server.port": "{port}",
+ "network.protocol.version": "{flavor}",
+ "http.response.status_code": "200",
+ "url.full": "http://{host}:{port}/"
}
}
]
diff --git a/test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlEventSourceTests.netfx.cs b/test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlEventSourceTests.netfx.cs
index 131f5622b02..2df68ac8d31 100644
--- a/test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlEventSourceTests.netfx.cs
+++ b/test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlEventSourceTests.netfx.cs
@@ -59,7 +59,7 @@ public async Task SuccessfulCommandTest(CommandType commandType, string commandT
using SqlConnection sqlConnection = new SqlConnection(SqlConnectionString);
- await sqlConnection.OpenAsync().ConfigureAwait(false);
+ await sqlConnection.OpenAsync();
string dataSource = sqlConnection.DataSource;
@@ -72,7 +72,7 @@ public async Task SuccessfulCommandTest(CommandType commandType, string commandT
try
{
- await sqlCommand.ExecuteNonQueryAsync().ConfigureAwait(false);
+ await sqlCommand.ExecuteNonQueryAsync();
}
catch
{
diff --git a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/Dockerfile b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/Dockerfile
index 0508036d3e8..9723867f4f3 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=7.0
-ARG TEST_SDK_VERSION=7.0
+ARG BUILD_SDK_VERSION=8.0
+ARG TEST_SDK_VERSION=8.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=net7.0
+ARG PUBLISH_FRAMEWORK=net8.0
WORKDIR /repo
COPY . ./
WORKDIR "/repo/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests"
diff --git a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/W3CTraceContextTests.cs b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/W3CTraceContextTests.cs
index e7ae8a783d3..05d8bd919d5 100644
--- a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/W3CTraceContextTests.cs
+++ b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/W3CTraceContextTests.cs
@@ -71,7 +71,7 @@ public void W3CTraceContextTestSuiteAsync(string value)
Encoding.UTF8,
"application/json"),
};
- await this.httpClient.SendAsync(request).ConfigureAwait(false);
+ await this.httpClient.SendAsync(request);
}
}
else
diff --git a/test/OpenTelemetry.Tests.Stress.Metrics/README.md b/test/OpenTelemetry.Tests.Stress.Metrics/README.md
index c05f1c6758e..13ce3faf073 100644
--- a/test/OpenTelemetry.Tests.Stress.Metrics/README.md
+++ b/test/OpenTelemetry.Tests.Stress.Metrics/README.md
@@ -15,5 +15,5 @@ for `Counter` and uncomment everything related to `Histogram` in the
Open a console, run the following command from the current folder:
```sh
-dotnet run --framework net7.0 --configuration Release
+dotnet run --framework net8.0 --configuration Release
```
diff --git a/test/OpenTelemetry.Tests/Concurrency/MetricsConcurrencyTests.cs b/test/OpenTelemetry.Tests/Concurrency/MetricsConcurrencyTests.cs
new file mode 100644
index 00000000000..65eb44c8145
--- /dev/null
+++ b/test/OpenTelemetry.Tests/Concurrency/MetricsConcurrencyTests.cs
@@ -0,0 +1,70 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using Microsoft.Coyote;
+using Microsoft.Coyote.SystematicTesting;
+using OpenTelemetry.Metrics.Tests;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace OpenTelemetry.Tests.Concurrency;
+
+public class MetricsConcurrencyTests
+{
+ private readonly ITestOutputHelper output;
+ private readonly AggregatorTests aggregatorTests;
+
+ public MetricsConcurrencyTests(ITestOutputHelper output)
+ {
+ this.output = output;
+ this.aggregatorTests = new AggregatorTests();
+ }
+
+ [SkipUnlessEnvVarFoundFact("OTEL_RUN_COYOTE_TESTS")]
+ [Trait("CategoryName", "CoyoteConcurrencyTests")]
+ public void MultithreadedLongHistogramTestConcurrencyTest()
+ {
+ var config = Configuration.Create()
+ .WithTestingIterations(100)
+ .WithMemoryAccessRaceCheckingEnabled(true);
+
+ var test = TestingEngine.Create(config, this.aggregatorTests.MultiThreadedHistogramUpdateAndSnapShotTest);
+
+ test.Run();
+
+ this.output.WriteLine(test.GetReport());
+ this.output.WriteLine($"Bugs, if any: {string.Join("\n", test.TestReport.BugReports)}");
+
+ var dir = Directory.GetCurrentDirectory();
+ if (test.TryEmitReports(dir, $"{nameof(this.MultithreadedLongHistogramTestConcurrencyTest)}_CoyoteOutput", out var reportPaths))
+ {
+ foreach (var reportPath in reportPaths)
+ {
+ this.output.WriteLine($"Execution Report: {reportPath}");
+ }
+ }
+
+ if (test.TryEmitCoverageReports(dir, $"{nameof(this.MultithreadedLongHistogramTestConcurrencyTest)}_CoyoteOutput", out reportPaths))
+ {
+ foreach (var reportPath in reportPaths)
+ {
+ this.output.WriteLine($"Coverage report: {reportPath}");
+ }
+ }
+
+ Assert.Equal(0, test.TestReport.NumOfFoundBugs);
+ }
+}
diff --git a/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs b/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs
index 2fc427e3ff0..d8307c1dab9 100644
--- a/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs
+++ b/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs
@@ -129,7 +129,7 @@ public async Task CpuPressureTest()
tasks.Add(Task.Run(async () =>
{
- await Task.Delay(2000).ConfigureAwait(false);
+ await Task.Delay(2000);
if (tid == 0)
{
@@ -167,6 +167,6 @@ public async Task CpuPressureTest()
}));
}
- await Task.WhenAll(tasks).ConfigureAwait(false);
+ await Task.WhenAll(tasks);
}
}
diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs b/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs
index fd330dd0694..8d90505ffad 100644
--- a/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs
+++ b/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs
@@ -190,7 +190,7 @@ public async Task ExportTest(bool warmup)
{
Random random = new Random();
- await Task.Delay(random.Next(100, 150)).ConfigureAwait(false);
+ await Task.Delay(random.Next(100, 150));
for (int i = 0; i < 1000; i++)
{
@@ -201,12 +201,12 @@ public async Task ExportTest(bool warmup)
// This should no-op mostly.
pool.Return(logRecord);
- await Task.Delay(random.Next(0, 20)).ConfigureAwait(false);
+ await Task.Delay(random.Next(0, 20));
}
}));
}
- await Task.WhenAll(tasks).ConfigureAwait(false);
+ await Task.WhenAll(tasks);
processor.ForceFlush();
@@ -248,7 +248,7 @@ public async Task DeadlockTest()
{
tasks.Add(Task.Run(async () =>
{
- await Task.Delay(2000).ConfigureAwait(false);
+ await Task.Delay(2000);
for (int i = 0; i < 100_000; i++)
{
@@ -259,7 +259,7 @@ public async Task DeadlockTest()
}));
}
- await Task.WhenAll(tasks).ConfigureAwait(false);
+ await Task.WhenAll(tasks);
Assert.True(pool.Count <= LogRecordSharedPool.DefaultMaxPoolSize);
}
diff --git a/test/OpenTelemetry.Tests/Metrics/AggregatorTestsBase.cs b/test/OpenTelemetry.Tests/Metrics/AggregatorTestsBase.cs
index a93d0156f0f..f3a171df6a9 100644
--- a/test/OpenTelemetry.Tests/Metrics/AggregatorTestsBase.cs
+++ b/test/OpenTelemetry.Tests/Metrics/AggregatorTestsBase.cs
@@ -29,16 +29,15 @@ public abstract class AggregatorTestsBase
private static readonly MetricStreamIdentity MetricStreamIdentity = new(Instrument, HistogramConfiguration);
private readonly bool emitOverflowAttribute;
+ private readonly bool shouldReclaimUnusedMetricPoints;
private readonly AggregatorStore aggregatorStore;
- protected AggregatorTestsBase(bool emitOverflowAttribute)
+ protected AggregatorTestsBase(bool emitOverflowAttribute, bool shouldReclaimUnusedMetricPoints)
{
- if (emitOverflowAttribute)
- {
- this.emitOverflowAttribute = emitOverflowAttribute;
- }
+ this.emitOverflowAttribute = emitOverflowAttribute;
+ this.shouldReclaimUnusedMetricPoints = shouldReclaimUnusedMetricPoints;
- this.aggregatorStore = new(MetricStreamIdentity, AggregationType.HistogramWithBuckets, AggregationTemporality.Cumulative, 1024, emitOverflowAttribute);
+ this.aggregatorStore = new(MetricStreamIdentity, AggregationType.HistogramWithBuckets, AggregationTemporality.Cumulative, 1024, emitOverflowAttribute, this.shouldReclaimUnusedMetricPoints);
}
[Fact]
@@ -248,12 +247,14 @@ public void MultiThreadedHistogramUpdateAndSnapShotTest()
[InlineData("Microsoft.AspNetCore.RateLimiting", "aspnetcore.rate_limiting.request.time_in_queue", "s", KnownHistogramBuckets.DefaultShortSeconds)]
[InlineData("Microsoft.AspNetCore.Server.Kestrel", "kestrel.connection.duration", "s", KnownHistogramBuckets.DefaultLongSeconds)]
[InlineData("Microsoft.AspNetCore.Server.Kestrel", "kestrel.tls_handshake.duration", "s", KnownHistogramBuckets.DefaultShortSeconds)]
+ [InlineData("OpenTelemetry.Instrumentation.AspNet", "http.server.duration", "ms", KnownHistogramBuckets.Default)]
+ [InlineData("OpenTelemetry.Instrumentation.AspNet", "http.server.request.duration", "s", KnownHistogramBuckets.DefaultShortSeconds)]
[InlineData("OpenTelemetry.Instrumentation.AspNetCore", "http.server.duration", "ms", KnownHistogramBuckets.Default)]
[InlineData("OpenTelemetry.Instrumentation.Http", "http.client.duration", "ms", KnownHistogramBuckets.Default)]
[InlineData("System.Net.Http", "http.client.connection.duration", "s", KnownHistogramBuckets.DefaultLongSeconds)]
[InlineData("System.Net.Http", "http.client.request.duration", "s", KnownHistogramBuckets.DefaultShortSeconds)]
[InlineData("System.Net.Http", "http.client.request.time_in_queue", "s", KnownHistogramBuckets.DefaultShortSeconds)]
- [InlineData("System.Net.NameResolution", "dns.lookups.duration", "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)
{
@@ -268,7 +269,8 @@ public void HistogramBucketsDefaultUpdatesForSecondsTest(string meterName, strin
AggregationType.Histogram,
AggregationTemporality.Cumulative,
maxMetricPoints: 1024,
- this.emitOverflowAttribute);
+ this.emitOverflowAttribute,
+ this.shouldReclaimUnusedMetricPoints);
KnownHistogramBuckets actualHistogramBounds = KnownHistogramBuckets.Default;
if (aggregatorStore.HistogramBounds == Metric.DefaultHistogramBoundsShortSeconds)
@@ -345,6 +347,7 @@ internal void ExponentialHistogramTests(AggregationType aggregationType, Aggrega
aggregationTemporality,
maxMetricPoints: 1024,
this.emitOverflowAttribute,
+ this.shouldReclaimUnusedMetricPoints,
exemplarsEnabled ? new AlwaysOnExemplarFilter() : null);
var expectedHistogram = new Base2ExponentialBucketHistogram();
@@ -453,7 +456,8 @@ internal void ExponentialMaxScaleConfigWorks(int? maxScale)
AggregationType.Base2ExponentialHistogram,
AggregationTemporality.Cumulative,
maxMetricPoints: 1024,
- this.emitOverflowAttribute);
+ this.emitOverflowAttribute,
+ this.shouldReclaimUnusedMetricPoints);
aggregatorStore.Update(10, Array.Empty>());
@@ -529,7 +533,7 @@ private class ThreadArguments
public class AggregatorTests : AggregatorTestsBase
{
public AggregatorTests()
- : base(false)
+ : base(emitOverflowAttribute: false, shouldReclaimUnusedMetricPoints: false)
{
}
}
@@ -537,7 +541,23 @@ public AggregatorTests()
public class AggregatorTestsWithOverflowAttribute : AggregatorTestsBase
{
public AggregatorTestsWithOverflowAttribute()
- : base(true)
+ : base(emitOverflowAttribute: true, shouldReclaimUnusedMetricPoints: false)
+ {
+ }
+}
+
+public class AggregatorTestsWithReclaimAttribute : AggregatorTestsBase
+{
+ public AggregatorTestsWithReclaimAttribute()
+ : base(emitOverflowAttribute: false, shouldReclaimUnusedMetricPoints: true)
+ {
+ }
+}
+
+public class AggregatorTestsWithBothReclaimAndOverflowAttributes : AggregatorTestsBase
+{
+ public AggregatorTestsWithBothReclaimAndOverflowAttributes()
+ : base(emitOverflowAttribute: true, shouldReclaimUnusedMetricPoints: true)
{
}
}
diff --git a/test/OpenTelemetry.Tests/Metrics/MeterProviderSdkTest.cs b/test/OpenTelemetry.Tests/Metrics/MeterProviderSdkTest.cs
index 9a980e12ea4..be2e564a3dd 100644
--- a/test/OpenTelemetry.Tests/Metrics/MeterProviderSdkTest.cs
+++ b/test/OpenTelemetry.Tests/Metrics/MeterProviderSdkTest.cs
@@ -14,6 +14,9 @@
// limitations under the License.
//
+using System.Diagnostics.Metrics;
+using OpenTelemetry.Internal;
+using OpenTelemetry.Tests;
using Xunit;
namespace OpenTelemetry.Metrics.Tests;
@@ -45,4 +48,77 @@ public void BuilderTypeDoesNotChangeTest()
Assert.NotNull(provider);
}
+
+ [Theory]
+ [InlineData(false, true)]
+ [InlineData(true, true)]
+ [InlineData(false, false)]
+ [InlineData(true, false)]
+ public void TransientMeterExhaustsMetricStorageTest(bool withView, bool forceFlushAfterEachTest)
+ {
+ using var inMemoryEventListener = new InMemoryEventListener(OpenTelemetrySdkEventSource.Log);
+
+ var meterName = Utils.GetCurrentMethodName();
+ var exportedItems = new List();
+
+ var builder = Sdk.CreateMeterProviderBuilder()
+ .SetMaxMetricStreams(1)
+ .AddMeter(meterName)
+ .AddInMemoryExporter(exportedItems);
+
+ if (withView)
+ {
+ builder.AddView(i => null);
+ }
+
+ using var meterProvider = builder
+ .Build() as MeterProviderSdk;
+
+ Assert.NotNull(meterProvider);
+
+ RunTest();
+
+ if (forceFlushAfterEachTest)
+ {
+ Assert.Single(exportedItems);
+ }
+
+ RunTest();
+
+ if (forceFlushAfterEachTest)
+ {
+ Assert.Empty(exportedItems);
+ }
+ else
+ {
+ meterProvider.ForceFlush();
+
+ Assert.Single(exportedItems);
+ }
+
+#if DEBUG
+ // Note: This is inside a debug block because when running in CI the
+ // event source sees events from other tests running in parallel.
+ var metricInstrumentIgnoredEvents = inMemoryEventListener.Events.Where((e) => e.EventId == 33);
+
+ Assert.Single(metricInstrumentIgnoredEvents);
+#endif
+
+ void RunTest()
+ {
+ exportedItems.Clear();
+
+ var meter = new Meter(meterName);
+
+ var counter = meter.CreateCounter("Counter");
+ counter.Add(1);
+
+ meter.Dispose();
+
+ if (forceFlushAfterEachTest)
+ {
+ meterProvider.ForceFlush();
+ }
+ }
+ }
}
diff --git a/test/OpenTelemetry.Tests/Metrics/MetricApiTestsBase.cs b/test/OpenTelemetry.Tests/Metrics/MetricApiTestsBase.cs
index bfe2229dcf5..8eb3a124dbf 100644
--- a/test/OpenTelemetry.Tests/Metrics/MetricApiTestsBase.cs
+++ b/test/OpenTelemetry.Tests/Metrics/MetricApiTestsBase.cs
@@ -16,6 +16,7 @@
using System.Diagnostics;
using System.Diagnostics.Metrics;
+using Microsoft.Extensions.Configuration;
using OpenTelemetry.Exporter;
using OpenTelemetry.Internal;
using OpenTelemetry.Tests;
@@ -26,7 +27,7 @@ namespace OpenTelemetry.Metrics.Tests;
#pragma warning disable SA1402
-public abstract class MetricApiTestsBase : MetricTestsBase, IDisposable
+public abstract class MetricApiTestsBase : MetricTestsBase
{
private const int MaxTimeToAllowForFlush = 10000;
private static readonly int NumberOfThreads = Environment.ProcessorCount;
@@ -35,14 +36,10 @@ public abstract class MetricApiTestsBase : MetricTestsBase, IDisposable
private static readonly int NumberOfMetricUpdateByEachThread = 100000;
private readonly ITestOutputHelper output;
- protected MetricApiTestsBase(ITestOutputHelper output, bool emitOverflowAttribute)
+ protected MetricApiTestsBase(ITestOutputHelper output, bool emitOverflowAttribute, bool shouldReclaimUnusedMetricPoints)
+ : base(BuildConfiguration(emitOverflowAttribute, shouldReclaimUnusedMetricPoints))
{
this.output = output;
-
- if (emitOverflowAttribute)
- {
- Environment.SetEnvironmentVariable(EmitOverFlowAttributeConfigKey, "true");
- }
}
[Fact]
@@ -50,10 +47,10 @@ public void MeasurementWithNullValuedTag()
{
using var meter = new Meter(Utils.GetCurrentMethodName());
var exportedItems = new List();
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
- .AddInMemoryExporter(exportedItems)
- .Build();
+ .AddInMemoryExporter(exportedItems));
var counter = meter.CreateCounter("myCounter");
counter.Add(100, new KeyValuePair("tagWithNullValue", null));
@@ -83,10 +80,10 @@ public void ObserverCallbackTest()
{
using var meter = new Meter(Utils.GetCurrentMethodName());
var exportedItems = new List();
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
- .AddInMemoryExporter(exportedItems)
- .Build();
+ .AddInMemoryExporter(exportedItems));
var measurement = new Measurement(100, new("name", "apple"), new("color", "red"));
meter.CreateObservableGauge("myGauge", () => measurement);
@@ -112,10 +109,10 @@ public void ObserverCallbackExceptionTest()
{
using var meter = new Meter(Utils.GetCurrentMethodName());
var exportedItems = new List();
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
- .AddInMemoryExporter(exportedItems)
- .Build();
+ .AddInMemoryExporter(exportedItems));
var measurement = new Measurement(100, new("name", "apple"), new("color", "red"));
meter.CreateObservableGauge("myGauge", () => measurement);
@@ -146,11 +143,10 @@ public void MetricUnitIsExportedCorrectly(string unit)
var exportedItems = new List();
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
- var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
- .AddMeter(meter.Name)
- .AddInMemoryExporter(exportedItems);
- using var meterProvider = meterProviderBuilder.Build();
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
+ .AddMeter(meter.Name)
+ .AddInMemoryExporter(exportedItems));
var counter = meter.CreateCounter("name1", unit);
counter.Add(10);
@@ -169,11 +165,10 @@ public void MetricDescriptionIsExportedCorrectly(string description)
var exportedItems = new List();
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
- var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
- .AddMeter(meter.Name)
- .AddInMemoryExporter(exportedItems);
- using var meterProvider = meterProviderBuilder.Build();
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
+ .AddMeter(meter.Name)
+ .AddInMemoryExporter(exportedItems));
var counter = meter.CreateCounter("name1", null, description);
counter.Add(10);
@@ -189,11 +184,10 @@ public void DuplicateInstrumentRegistration_NoViews_IdenticalInstruments()
var exportedItems = new List();
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
- var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
- .AddMeter(meter.Name)
- .AddInMemoryExporter(exportedItems);
- using var meterProvider = meterProviderBuilder.Build();
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
+ .AddMeter(meter.Name)
+ .AddInMemoryExporter(exportedItems));
var instrument = meter.CreateCounter("instrumentName", "instrumentUnit", "instrumentDescription");
var duplicateInstrument = meter.CreateCounter("instrumentName", "instrumentUnit", "instrumentDescription");
@@ -223,11 +217,10 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe
var exportedItems = new List();
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
- var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
- .AddMeter(meter.Name)
- .AddInMemoryExporter(exportedItems);
- using var meterProvider = meterProviderBuilder.Build();
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
+ .AddMeter(meter.Name)
+ .AddInMemoryExporter(exportedItems));
var instrument = meter.CreateCounter("instrumentName", "instrumentUnit", "instrumentDescription1");
var duplicateInstrument = meter.CreateCounter("instrumentName", "instrumentUnit", "instrumentDescription2");
@@ -270,11 +263,10 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe
var exportedItems = new List();
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
- var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
- .AddMeter(meter.Name)
- .AddInMemoryExporter(exportedItems);
- using var meterProvider = meterProviderBuilder.Build();
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
+ .AddMeter(meter.Name)
+ .AddInMemoryExporter(exportedItems));
var instrument = meter.CreateCounter("instrumentName", "instrumentUnit1", "instrumentDescription");
var duplicateInstrument = meter.CreateCounter("instrumentName", "instrumentUnit2", "instrumentDescription");
@@ -317,11 +309,10 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe
var exportedItems = new List();
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
- var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
- .AddMeter(meter.Name)
- .AddInMemoryExporter(exportedItems);
- using var meterProvider = meterProviderBuilder.Build();
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
+ .AddMeter(meter.Name)
+ .AddInMemoryExporter(exportedItems));
var instrument = meter.CreateCounter("instrumentName", "instrumentUnit", "instrumentDescription");
var duplicateInstrument = meter.CreateCounter("instrumentName", "instrumentUnit", "instrumentDescription");
@@ -362,11 +353,10 @@ public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_Differe
var exportedItems = new List();
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
- var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
- .AddMeter(meter.Name)
- .AddInMemoryExporter(exportedItems);
- using var meterProvider = meterProviderBuilder.Build();
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
+ .AddMeter(meter.Name)
+ .AddInMemoryExporter(exportedItems));
var instrument = meter.CreateCounter("instrumentName", "instrumentUnit", "instrumentDescription");
var duplicateInstrument = meter.CreateHistogram("instrumentName", "instrumentUnit", "instrumentDescription");
@@ -409,12 +399,11 @@ public void DuplicateInstrumentNamesFromDifferentMetersWithSameNameDifferentVers
using var meter1 = new Meter($"{Utils.GetCurrentMethodName()}", "1.0");
using var meter2 = new Meter($"{Utils.GetCurrentMethodName()}", "2.0");
- var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter1.Name)
.AddMeter(meter2.Name)
- .AddInMemoryExporter(exportedItems);
-
- using var meterProvider = meterProviderBuilder.Build();
+ .AddInMemoryExporter(exportedItems));
// Expecting one metric stream.
var counterLong = meter1.CreateCounter("name1");
@@ -442,20 +431,22 @@ public void DuplicateInstrumentNamesFromDifferentMetersAreAllowed(MetricReaderTe
using var meter1 = new Meter($"{Utils.GetCurrentMethodName()}.1.{temporality}");
using var meter2 = new Meter($"{Utils.GetCurrentMethodName()}.2.{temporality}");
- var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
- .AddMeter(meter1.Name)
- .AddMeter(meter2.Name)
- .AddInMemoryExporter(exportedItems, metricReaderOptions =>
- {
- metricReaderOptions.TemporalityPreference = temporality;
- });
- if (hasView)
+ using var container = this.BuildMeterProvider(out var meterProvider, builder =>
{
- meterProviderBuilder.AddView("name1", new MetricStreamConfiguration() { Description = "description" });
- }
+ builder
+ .AddMeter(meter1.Name)
+ .AddMeter(meter2.Name)
+ .AddInMemoryExporter(exportedItems, metricReaderOptions =>
+ {
+ metricReaderOptions.TemporalityPreference = temporality;
+ });
- using var meterProvider = meterProviderBuilder.Build();
+ if (hasView)
+ {
+ builder.AddView("name1", new MetricStreamConfiguration() { Description = "description" });
+ }
+ });
// Expecting one metric stream.
var counterLong = meter1.CreateCounter("name1");
@@ -473,6 +464,7 @@ public void DuplicateInstrumentNamesFromDifferentMetersAreAllowed(MetricReaderTe
Assert.Equal(2, exportedItems.Count);
}
+#if !BUILDING_HOSTING_TESTS
[Theory]
[InlineData(true)]
[InlineData(false)]
@@ -486,18 +478,20 @@ public void MeterSourcesWildcardSupportMatchTest(bool hasView)
using var meter6 = new Meter("SomeCompany.SomeProduct.SomeComponent");
var exportedItems = new List();
- var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
- .AddMeter("AbcCompany.XyzProduct.Component?")
- .AddMeter("DefCompany.*.ComponentC")
- .AddMeter("GhiCompany.qweProduct.ComponentN") // Mixing of non-wildcard meter name and wildcard meter name.
- .AddInMemoryExporter(exportedItems);
- if (hasView)
+ using var container = this.BuildMeterProvider(out var meterProvider, builder =>
{
- meterProviderBuilder.AddView("myGauge1", "newName");
- }
+ builder
+ .AddMeter("AbcCompany.XyzProduct.Component?")
+ .AddMeter("DefCompany.*.ComponentC")
+ .AddMeter("GhiCompany.qweProduct.ComponentN") // Mixing of non-wildcard meter name and wildcard meter name.
+ .AddInMemoryExporter(exportedItems);
- using var meterProvider = meterProviderBuilder.Build();
+ if (hasView)
+ {
+ builder.AddView("myGauge1", "newName");
+ }
+ });
var measurement = new Measurement(100, new("name", "apple"), new("color", "red"));
meter1.CreateObservableGauge("myGauge1", () => measurement);
@@ -525,6 +519,7 @@ public void MeterSourcesWildcardSupportMatchTest(bool hasView)
Assert.Equal("myGauge4", exportedItems[3].Name);
Assert.Equal("myGauge5", exportedItems[4].Name);
}
+#endif
[Theory]
[InlineData(true)]
@@ -535,15 +530,18 @@ public void MeterSourcesWildcardSupportNegativeTestNoMeterAdded(bool hasView)
using var meter2 = new Meter($"abcCompany.xYzProduct.componentC.{hasView}");
var exportedItems = new List();
- var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
- .AddInMemoryExporter(exportedItems);
- if (hasView)
+ using var container = this.BuildMeterProvider(out var meterProvider, builder =>
{
- meterProviderBuilder.AddView("gauge1", "renamed");
- }
+ builder
+ .AddInMemoryExporter(exportedItems);
+
+ if (hasView)
+ {
+ builder.AddView("gauge1", "renamed");
+ }
+ });
- using var meterProvider = meterProviderBuilder.Build();
var measurement = new Measurement(100, new("name", "apple"), new("color", "red"));
meter1.CreateObservableGauge("myGauge1", () => measurement);
@@ -564,13 +562,13 @@ public void CounterAggregationTest(bool exportDelta)
using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{exportDelta}");
var counterLong = meter.CreateCounter("mycounter");
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems, metricReaderOptions =>
{
metricReaderOptions.TemporalityPreference = exportDelta ? MetricReaderTemporalityPreference.Delta : MetricReaderTemporalityPreference.Cumulative;
- })
- .Build();
+ }));
counterLong.Add(10);
counterLong.Add(10);
@@ -666,13 +664,12 @@ public void ObservableCounterAggregationTest(bool exportDelta)
};
});
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems, metricReaderOptions =>
{
metricReaderOptions.TemporalityPreference = exportDelta ? MetricReaderTemporalityPreference.Delta : MetricReaderTemporalityPreference.Cumulative;
- })
- .Build();
+ }));
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
long sumReceived = GetLongSum(exportedItems);
@@ -740,13 +737,12 @@ public void ObservableCounterWithTagsAggregationTest(bool exportDelta)
};
});
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems, metricReaderOptions =>
{
metricReaderOptions.TemporalityPreference = exportDelta ? MetricReaderTemporalityPreference.Delta : MetricReaderTemporalityPreference.Cumulative;
- })
- .Build();
+ }));
// Export 1
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
@@ -837,14 +833,13 @@ public void ObservableCounterSpatialAggregationTest(bool exportDelta)
};
});
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems, metricReaderOptions =>
{
metricReaderOptions.TemporalityPreference = exportDelta ? MetricReaderTemporalityPreference.Delta : MetricReaderTemporalityPreference.Cumulative;
})
- .AddView("requestCount", new MetricStreamConfiguration() { TagKeys = Array.Empty() })
- .Build();
+ .AddView("requestCount", new MetricStreamConfiguration() { TagKeys = Array.Empty() }));
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
Assert.Single(exportedItems);
@@ -878,13 +873,13 @@ public void UpDownCounterAggregationTest(bool exportDelta)
using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{exportDelta}");
var counterLong = meter.CreateUpDownCounter("mycounter");
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems, metricReaderOptions =>
{
metricReaderOptions.TemporalityPreference = exportDelta ? MetricReaderTemporalityPreference.Delta : MetricReaderTemporalityPreference.Cumulative;
- })
- .Build();
+ }));
counterLong.Add(10);
counterLong.Add(-5);
@@ -960,13 +955,12 @@ public void ObservableUpDownCounterAggregationTest(bool exportDelta)
};
});
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems, metricReaderOptions =>
{
metricReaderOptions.TemporalityPreference = exportDelta ? MetricReaderTemporalityPreference.Delta : MetricReaderTemporalityPreference.Cumulative;
- })
- .Build();
+ }));
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
long sumReceived = GetLongSum(exportedItems);
@@ -1024,13 +1018,12 @@ public void ObservableUpDownCounterWithTagsAggregationTest(bool exportDelta)
};
});
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems, metricReaderOptions =>
{
metricReaderOptions.TemporalityPreference = exportDelta ? MetricReaderTemporalityPreference.Delta : MetricReaderTemporalityPreference.Cumulative;
- })
- .Build();
+ }));
// Export 1
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
@@ -1094,13 +1087,13 @@ public void DimensionsAreOrderInsensitiveWithSortedKeysFirst(bool exportDelta)
using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{exportDelta}");
var counterLong = meter.CreateCounter("Counter");
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems, metricReaderOptions =>
{
metricReaderOptions.TemporalityPreference = exportDelta ? MetricReaderTemporalityPreference.Delta : MetricReaderTemporalityPreference.Cumulative;
- })
- .Build();
+ }));
// Emit the first metric with the sorted order of tag keys
counterLong.Add(5, new("Key1", "Value1"), new("Key2", "Value2"), new("Key3", "Value3"));
@@ -1185,13 +1178,13 @@ public void DimensionsAreOrderInsensitiveWithUnsortedKeysFirst(bool exportDelta)
using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{exportDelta}");
var counterLong = meter.CreateCounter("Counter");
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems, metricReaderOptions =>
{
metricReaderOptions.TemporalityPreference = exportDelta ? MetricReaderTemporalityPreference.Delta : MetricReaderTemporalityPreference.Cumulative;
- })
- .Build();
+ }));
// Emit the first metric with the unsorted order of tag keys
counterLong.Add(5, new("Key1", "Value1"), new("Key3", "Value3"), new("Key2", "Value2"));
@@ -1278,14 +1271,14 @@ public void TestInstrumentDisposal(MetricReaderTemporalityPreference temporality
var meter2 = new Meter($"{Utils.GetCurrentMethodName()}.{temporality}.2");
var counter1 = meter1.CreateCounter("counterFromMeter1");
var counter2 = meter2.CreateCounter("counterFromMeter2");
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter1.Name)
.AddMeter(meter2.Name)
.AddInMemoryExporter(exportedItems, metricReaderOptions =>
{
metricReaderOptions.TemporalityPreference = temporality;
- })
- .Build();
+ }));
counter1.Add(10, new KeyValuePair("key", "value"));
counter2.Add(10, new KeyValuePair("key", "value"));
@@ -1346,13 +1339,13 @@ int MetricPointCount()
using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{temporality}");
var counterLong = meter.CreateCounter("mycounterCapTest");
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems, metricReaderOptions =>
{
metricReaderOptions.TemporalityPreference = temporality;
- })
- .Build();
+ }));
// Make one Add with no tags.
// as currently we reserve 0th index
@@ -1442,10 +1435,9 @@ public void InstrumentWithInvalidNameIsIgnoredTest(string instrumentName)
using var meter = new Meter("InstrumentWithInvalidNameIsIgnoredTest");
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
- .AddInMemoryExporter(exportedItems)
- .Build();
+ .AddInMemoryExporter(exportedItems));
var counterLong = meter.CreateCounter(instrumentName);
counterLong.Add(10);
@@ -1464,10 +1456,9 @@ public void InstrumentWithValidNameIsExportedTest(string name)
using var meter = new Meter("InstrumentValidNameIsExportedTest");
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
- .AddInMemoryExporter(exportedItems)
- .Build();
+ .AddInMemoryExporter(exportedItems));
var counterLong = meter.CreateCounter(name);
counterLong.Add(10);
@@ -1486,15 +1477,17 @@ public void SetupSdkProviderWithNoReader(bool hasViews)
{
// This test ensures that MeterProviderSdk can be set up without any reader
using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{hasViews}");
- var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
- .AddMeter(meter.Name);
- if (hasViews)
+ using var container = this.BuildMeterProvider(out var meterProvider, builder =>
{
- meterProviderBuilder.AddView("counter", "renamedCounter");
- }
+ builder
+ .AddMeter(meter.Name);
- using var meterProvider = meterProviderBuilder.Build();
+ if (hasViews)
+ {
+ builder.AddView("counter", "renamedCounter");
+ }
+ });
var counter = meter.CreateCounter("counter");
@@ -1506,10 +1499,10 @@ public void UnsupportedMetricInstrument()
{
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
var exportedItems = new List();
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
- .AddMeter(meter.Name)
- .AddInMemoryExporter(exportedItems)
- .Build();
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
+ .AddMeter(meter.Name)
+ .AddInMemoryExporter(exportedItems));
using (var inMemoryEventListener = new InMemoryEventListener(OpenTelemetrySdkEventSource.Log))
{
@@ -1518,16 +1511,39 @@ public void UnsupportedMetricInstrument()
// This validates that we log InstrumentIgnored event
// and not something else.
- Assert.Single(inMemoryEventListener.Events.Where((e) => e.EventId == 33));
+ var instrumentIgnoredEvents = inMemoryEventListener.Events.Where((e) => e.EventId == 33);
+#if BUILDING_HOSTING_TESTS
+ // Note: When using IMetricsListener this event is fired twice. Once
+ // for the SDK listener ignoring it because it isn't listening to
+ // the meter and then once for IMetricsListener ignoring it because
+ // decimal is not supported.
+ Assert.Equal(2, instrumentIgnoredEvents.Count());
+#else
+ Assert.Single(instrumentIgnoredEvents);
+#endif
}
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
Assert.Empty(exportedItems);
}
- public void Dispose()
+ internal static IConfiguration BuildConfiguration(bool emitOverflowAttribute, bool shouldReclaimUnusedMetricPoints)
{
- Environment.SetEnvironmentVariable(EmitOverFlowAttributeConfigKey, null);
+ var configurationData = new Dictionary();
+
+ if (emitOverflowAttribute)
+ {
+ configurationData[EmitOverFlowAttributeConfigKey] = "true";
+ }
+
+ if (shouldReclaimUnusedMetricPoints)
+ {
+ configurationData[ReclaimUnusedMetricPointsConfigKey] = "true";
+ }
+
+ return new ConfigurationBuilder()
+ .AddInMemoryCollection(configurationData)
+ .Build();
}
private static void CounterUpdateThread(object obj)
@@ -1591,10 +1607,10 @@ private void MultithreadedCounterTest(T deltaValueUpdatedByEachCall)
var metricItems = new List();
using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{typeof(T).Name}.{deltaValueUpdatedByEachCall}");
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
- .AddInMemoryExporter(metricItems)
- .Build();
+ .AddInMemoryExporter(metricItems));
var argToThread = new UpdateThreadArguments
{
@@ -1647,10 +1663,10 @@ private void MultithreadedHistogramTest(long[] expected, T[] values)
var metricReader = new BaseExportingMetricReader(new InMemoryExporter(metrics));
using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{typeof(T).Name}");
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
- .AddReader(metricReader)
- .Build();
+ .AddReader(metricReader));
var argsToThread = new UpdateThreadArguments
{
@@ -1705,7 +1721,7 @@ private class UpdateThreadArguments
public class MetricApiTest : MetricApiTestsBase
{
public MetricApiTest(ITestOutputHelper output)
- : base(output, false)
+ : base(output, emitOverflowAttribute: false, shouldReclaimUnusedMetricPoints: false)
{
}
}
@@ -1713,7 +1729,23 @@ public MetricApiTest(ITestOutputHelper output)
public class MetricApiTestWithOverflowAttribute : MetricApiTestsBase
{
public MetricApiTestWithOverflowAttribute(ITestOutputHelper output)
- : base(output, true)
+ : base(output, emitOverflowAttribute: true, shouldReclaimUnusedMetricPoints: false)
+ {
+ }
+}
+
+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)
{
}
}
diff --git a/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs
index d306e618de3..6529b4ec2c6 100644
--- a/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs
+++ b/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs
@@ -40,14 +40,14 @@ public void TestExemplarsCounter()
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
var counter = meter.CreateCounter("testCounter");
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.SetExemplarFilter(new AlwaysOnExemplarFilter())
.AddInMemoryExporter(exportedItems, metricReaderOptions =>
{
metricReaderOptions.TemporalityPreference = MetricReaderTemporalityPreference.Delta;
- })
- .Build();
+ }));
var measurementValues = GenerateRandomValues(10);
foreach (var value in measurementValues)
@@ -94,14 +94,14 @@ public void TestExemplarsHistogram()
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
var histogram = meter.CreateHistogram("testHistogram");
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.SetExemplarFilter(new AlwaysOnExemplarFilter())
.AddInMemoryExporter(exportedItems, metricReaderOptions =>
{
metricReaderOptions.TemporalityPreference = MetricReaderTemporalityPreference.Delta;
- })
- .Build();
+ }));
var measurementValues = GenerateRandomValues(10);
foreach (var value in measurementValues)
@@ -147,15 +147,15 @@ public void TestExemplarsFilterTags()
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
var histogram = meter.CreateHistogram("testHistogram");
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
+
+ using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.SetExemplarFilter(new AlwaysOnExemplarFilter())
.AddView(histogram.Name, new MetricStreamConfiguration() { TagKeys = new string[] { "key1" } })
.AddInMemoryExporter(exportedItems, metricReaderOptions =>
{
metricReaderOptions.TemporalityPreference = MetricReaderTemporalityPreference.Delta;
- })
- .Build();
+ }));
var measurementValues = GenerateRandomValues(10);
foreach (var value in measurementValues)
diff --git a/test/OpenTelemetry.Tests/Metrics/MetricOverflowAttributeTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricOverflowAttributeTests.cs
deleted file mode 100644
index e6b1dde373f..00000000000
--- a/test/OpenTelemetry.Tests/Metrics/MetricOverflowAttributeTests.cs
+++ /dev/null
@@ -1,447 +0,0 @@
-//
-// Copyright The OpenTelemetry Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-using System.Diagnostics.Metrics;
-using System.Reflection;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using OpenTelemetry.Tests;
-using Xunit;
-
-namespace OpenTelemetry.Metrics.Tests;
-
-public class MetricOverflowAttributeTests
-{
- [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)
- {
- try
- {
- 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);
- var metric = exportedItems[0];
-
- var aggregatorStore = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metric) as AggregatorStore;
- var emitOverflowAttribute = (bool)typeof(AggregatorStore).GetField("emitOverflowAttribute", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStore);
-
- Assert.Equal(isEmitOverflowAttributeKeySet, emitOverflowAttribute);
- }
- finally
- {
- Environment.SetEnvironmentVariable(MetricTestsBase.EmitOverFlowAttributeConfigKey, null);
- }
- }
-
- [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)
- {
- try
- {
- 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);
- var metric = exportedItems[0];
-
- var aggregatorStore = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metric) as AggregatorStore;
- var emitOverflowAttribute = (bool)typeof(AggregatorStore).GetField("emitOverflowAttribute", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStore);
-
- Assert.Equal(isEmitOverflowAttributeKeySet, emitOverflowAttribute);
- }
- finally
- {
- Environment.SetEnvironmentVariable(MetricTestsBase.EmitOverFlowAttributeConfigKey, null);
- }
- }
-
- [Theory]
- [InlineData(1, false)]
- [InlineData(2, true)]
- [InlineData(10, true)]
- public void EmitOverflowAttributeIsOnlySetWhenMaxMetricPointsIsGreaterThanOne(int maxMetricPoints, bool isEmitOverflowAttributeKeySet)
- {
- try
- {
- Environment.SetEnvironmentVariable(MetricTestsBase.EmitOverFlowAttributeConfigKey, "true");
-
- var exportedItems = new List();
-
- var meter = new Meter(Utils.GetCurrentMethodName());
- var counter = meter.CreateCounter("TestCounter");
-
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
- .SetMaxMetricPointsPerMetricStream(maxMetricPoints)
- .AddMeter(meter.Name)
- .AddInMemoryExporter(exportedItems)
- .Build();
-
- counter.Add(10);
-
- meterProvider.ForceFlush();
-
- Assert.Single(exportedItems);
- var metric = exportedItems[0];
-
- var aggregatorStore = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metric) as AggregatorStore;
- var emitOverflowAttribute = (bool)typeof(AggregatorStore).GetField("emitOverflowAttribute", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStore);
-
- Assert.Equal(isEmitOverflowAttributeKeySet, emitOverflowAttribute);
- }
- finally
- {
- Environment.SetEnvironmentVariable(MetricTestsBase.EmitOverFlowAttributeConfigKey, null);
- }
- }
-
- [Theory]
- [InlineData(MetricReaderTemporalityPreference.Delta)]
- [InlineData(MetricReaderTemporalityPreference.Cumulative)]
- public void MetricOverflowAttributeIsRecordedCorrectlyForCounter(MetricReaderTemporalityPreference temporalityPreference)
- {
- try
- {
- Environment.SetEnvironmentVariable(MetricTestsBase.EmitOverFlowAttributeConfigKey, "true");
-
- 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, metricReaderOptions => metricReaderOptions.TemporalityPreference = temporalityPreference)
- .Build();
-
- // There are two reserved MetricPoints
- // 1. For zero tags
- // 2. For metric overflow attribute when user opts-in for this feature
-
- counter.Add(10); // Record measurement for zero tags
-
- // Max number for MetricPoints available for use when emitted with tags
- int maxMetricPointsForUse = MeterProviderBuilderSdk.MaxMetricPointsPerMetricDefault - 2;
-
- for (int i = 0; i < maxMetricPointsForUse; i++)
- {
- // 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));
- }
-
- meterProvider.ForceFlush();
-
- Assert.Single(exportedItems);
- var metric = exportedItems[0];
-
- var metricPoints = new List();
- foreach (ref readonly var mp in metric.GetMetricPoints())
- {
- metricPoints.Add(mp);
- }
-
- MetricPoint overflowMetricPoint;
-
- // We still have not exceeded the max MetricPoint limit
- Assert.DoesNotContain(metricPoints, mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
-
- exportedItems.Clear();
- metricPoints.Clear();
-
- counter.Add(5, new KeyValuePair("Key", 1998)); // Emit a metric to exceed the max MetricPoint limit
-
- meterProvider.ForceFlush();
- metric = exportedItems[0];
- foreach (ref readonly var mp in metric.GetMetricPoints())
- {
- metricPoints.Add(mp);
- }
-
- MetricPoint zeroTagsMetricPoint;
- if (temporalityPreference == MetricReaderTemporalityPreference.Cumulative)
- {
- // Check metric point for zero tags
- zeroTagsMetricPoint = metricPoints.Single(mp => mp.Tags.Count == 0);
- Assert.Equal(10, zeroTagsMetricPoint.GetSumLong());
- }
-
- // Check metric point for overflow
- overflowMetricPoint = metricPoints.Single(mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
- Assert.Equal(true, overflowMetricPoint.Tags.KeyAndValues[0].Value);
- Assert.Equal(1, overflowMetricPoint.Tags.Count);
- Assert.Equal(5, overflowMetricPoint.GetSumLong());
-
- exportedItems.Clear();
- metricPoints.Clear();
-
- counter.Add(15); // Record another measurement for zero tags
-
- // Emit 2500 more newer MetricPoints with distinct dimension combinations
- for (int i = 2000; i < 4500; i++)
- {
- counter.Add(5, new KeyValuePair("Key", i));
- }
-
- meterProvider.ForceFlush();
- metric = exportedItems[0];
- foreach (ref readonly var mp in metric.GetMetricPoints())
- {
- metricPoints.Add(mp);
- }
-
- zeroTagsMetricPoint = metricPoints.Single(mp => mp.Tags.Count == 0);
- overflowMetricPoint = metricPoints.Single(mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
-
- if (temporalityPreference == MetricReaderTemporalityPreference.Delta)
- {
- Assert.Equal(15, zeroTagsMetricPoint.GetSumLong());
-
- // Number of metric points that were available before the 2500 measurements were made = 2000 (max MetricPoints) - 2 (reserved for zero tags and overflow) = 1998
- // Number of metric points dropped = 2500 - 1998 = 502
- Assert.Equal(2510, overflowMetricPoint.GetSumLong()); // 502 * 5
- }
- else
- {
- Assert.Equal(25, zeroTagsMetricPoint.GetSumLong());
- Assert.Equal(12505, overflowMetricPoint.GetSumLong()); // 5 + (2500 * 5)
- }
-
- exportedItems.Clear();
- metricPoints.Clear();
-
- // Test that the SDK continues to correctly aggregate the previously registered measurements even after overflow has occurred
- counter.Add(25);
-
- meterProvider.ForceFlush();
- metric = exportedItems[0];
- foreach (ref readonly var mp in metric.GetMetricPoints())
- {
- metricPoints.Add(mp);
- }
-
- zeroTagsMetricPoint = metricPoints.Single(mp => mp.Tags.Count == 0);
-
- if (temporalityPreference == MetricReaderTemporalityPreference.Delta)
- {
- Assert.Equal(25, zeroTagsMetricPoint.GetSumLong());
- }
- else
- {
- overflowMetricPoint = metricPoints.Single(mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
-
- Assert.Equal(50, zeroTagsMetricPoint.GetSumLong());
- Assert.Equal(12505, overflowMetricPoint.GetSumLong());
- }
- }
- finally
- {
- Environment.SetEnvironmentVariable(MetricTestsBase.EmitOverFlowAttributeConfigKey, null);
- }
- }
-
- [Theory]
- [InlineData(MetricReaderTemporalityPreference.Delta)]
- [InlineData(MetricReaderTemporalityPreference.Cumulative)]
- public void MetricOverflowAttributeIsRecordedCorrectlyForHistogram(MetricReaderTemporalityPreference temporalityPreference)
- {
- try
- {
- Environment.SetEnvironmentVariable(MetricTestsBase.EmitOverFlowAttributeConfigKey, "true");
-
- var exportedItems = new List();
-
- var meter = new Meter(Utils.GetCurrentMethodName());
- var histogram = meter.CreateHistogram("TestHistogram");
-
- using var meterProvider = Sdk.CreateMeterProviderBuilder()
- .AddMeter(meter.Name)
- .AddInMemoryExporter(exportedItems, metricReaderOptions => metricReaderOptions.TemporalityPreference = temporalityPreference)
- .Build();
-
- // There are two reserved MetricPoints
- // 1. For zero tags
- // 2. For metric overflow attribute when user opts-in for this feature
-
- histogram.Record(10); // Record measurement for zero tags
-
- // Max number for MetricPoints available for use when emitted with tags
- int maxMetricPointsForUse = MeterProviderBuilderSdk.MaxMetricPointsPerMetricDefault - 2;
-
- for (int i = 0; i < maxMetricPointsForUse; i++)
- {
- // 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));
- }
-
- meterProvider.ForceFlush();
-
- Assert.Single(exportedItems);
- var metric = exportedItems[0];
-
- var metricPoints = new List();
- foreach (ref readonly var mp in metric.GetMetricPoints())
- {
- metricPoints.Add(mp);
- }
-
- MetricPoint overflowMetricPoint;
-
- // We still have not exceeded the max MetricPoint limit
- Assert.DoesNotContain(metricPoints, mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
-
- exportedItems.Clear();
- metricPoints.Clear();
-
- histogram.Record(5, new KeyValuePair("Key", 1998)); // Emit a metric to exceed the max MetricPoint limit
-
- meterProvider.ForceFlush();
- metric = exportedItems[0];
- foreach (ref readonly var mp in metric.GetMetricPoints())
- {
- metricPoints.Add(mp);
- }
-
- MetricPoint zeroTagsMetricPoint;
- if (temporalityPreference == MetricReaderTemporalityPreference.Cumulative)
- {
- // Check metric point for zero tags
- zeroTagsMetricPoint = metricPoints.Single(mp => mp.Tags.Count == 0);
- Assert.Equal(10, zeroTagsMetricPoint.GetHistogramSum());
- }
-
- // Check metric point for overflow
- overflowMetricPoint = metricPoints.Single(mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
- Assert.Equal(true, overflowMetricPoint.Tags.KeyAndValues[0].Value);
- Assert.Equal(1, overflowMetricPoint.Tags.Count);
- Assert.Equal(5, overflowMetricPoint.GetHistogramSum());
-
- exportedItems.Clear();
- metricPoints.Clear();
-
- histogram.Record(15); // Record another measurement for zero tags
-
- // Emit 2500 more newer MetricPoints with distinct dimension combinations
- for (int i = 2000; i < 4500; i++)
- {
- histogram.Record(5, new KeyValuePair("Key", i));
- }
-
- meterProvider.ForceFlush();
- metric = exportedItems[0];
- foreach (ref readonly var mp in metric.GetMetricPoints())
- {
- metricPoints.Add(mp);
- }
-
- zeroTagsMetricPoint = metricPoints.Single(mp => mp.Tags.Count == 0);
- overflowMetricPoint = metricPoints.Single(mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
-
- if (temporalityPreference == MetricReaderTemporalityPreference.Delta)
- {
- Assert.Equal(15, zeroTagsMetricPoint.GetHistogramSum());
-
- // Number of metric points that were available before the 2500 measurements were made = 2000 (max MetricPoints) - 2 (reserved for zero tags and overflow) = 1998
- // Number of metric points dropped = 2500 - 1998 = 502
- Assert.Equal(502, overflowMetricPoint.GetHistogramCount());
- Assert.Equal(2510, overflowMetricPoint.GetHistogramSum()); // 502 * 5
- }
- else
- {
- Assert.Equal(25, zeroTagsMetricPoint.GetHistogramSum());
-
- Assert.Equal(2501, overflowMetricPoint.GetHistogramCount());
- Assert.Equal(12505, overflowMetricPoint.GetHistogramSum()); // 5 + (2500 * 5)
- }
-
- exportedItems.Clear();
- metricPoints.Clear();
-
- // Test that the SDK continues to correctly aggregate the previously registered measurements even after overflow has occurred
- histogram.Record(25);
-
- meterProvider.ForceFlush();
- metric = exportedItems[0];
- foreach (ref readonly var mp in metric.GetMetricPoints())
- {
- metricPoints.Add(mp);
- }
-
- zeroTagsMetricPoint = metricPoints.Single(mp => mp.Tags.Count == 0);
-
- if (temporalityPreference == MetricReaderTemporalityPreference.Delta)
- {
- Assert.Equal(25, zeroTagsMetricPoint.GetHistogramSum());
- }
- else
- {
- overflowMetricPoint = metricPoints.Single(mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
-
- Assert.Equal(50, zeroTagsMetricPoint.GetHistogramSum());
- Assert.Equal(12505, overflowMetricPoint.GetHistogramSum());
- }
- }
- finally
- {
- Environment.SetEnvironmentVariable(MetricTestsBase.EmitOverFlowAttributeConfigKey, null);
- }
- }
-}
diff --git a/test/OpenTelemetry.Tests/Metrics/MetricOverflowAttributeTestsBase.cs b/test/OpenTelemetry.Tests/Metrics/MetricOverflowAttributeTestsBase.cs
new file mode 100644
index 00000000000..d275e8c69c2
--- /dev/null
+++ b/test/OpenTelemetry.Tests/Metrics/MetricOverflowAttributeTestsBase.cs
@@ -0,0 +1,489 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System.Diagnostics.Metrics;
+using System.Reflection;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using OpenTelemetry.Tests;
+using Xunit;
+
+namespace OpenTelemetry.Metrics.Tests;
+
+#pragma warning disable SA1402
+
+public abstract class MetricOverflowAttributeTestsBase
+{
+ public const string ReclaimUnusedMetricPointsConfigKey = "OTEL_DOTNET_EXPERIMENTAL_METRICS_RECLAIM_UNUSED_METRIC_POINTS";
+
+ private readonly bool shouldReclaimUnusedMetricPoints;
+ private readonly Dictionary configurationData = new()
+ {
+ [MetricTestsBase.EmitOverFlowAttributeConfigKey] = "true",
+ };
+
+ private readonly IConfiguration configuration;
+
+ public MetricOverflowAttributeTestsBase(bool shouldReclaimUnusedMetricPoints)
+ {
+ this.shouldReclaimUnusedMetricPoints = shouldReclaimUnusedMetricPoints;
+
+ if (shouldReclaimUnusedMetricPoints)
+ {
+ this.configurationData[ReclaimUnusedMetricPointsConfigKey] = "true";
+ }
+
+ this.configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(this.configurationData)
+ .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);
+ var metric = exportedItems[0];
+
+ var aggregatorStore = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metric) as AggregatorStore;
+ var emitOverflowAttribute = (bool)typeof(AggregatorStore).GetField("emitOverflowAttribute", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStore);
+
+ Assert.Equal(isEmitOverflowAttributeKeySet, 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);
+ var metric = exportedItems[0];
+
+ var aggregatorStore = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metric) as AggregatorStore;
+ var emitOverflowAttribute = (bool)typeof(AggregatorStore).GetField("emitOverflowAttribute", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStore);
+
+ Assert.Equal(isEmitOverflowAttributeKeySet, emitOverflowAttribute);
+ }
+
+ [Theory]
+ [InlineData(1, false)]
+ [InlineData(2, true)]
+ [InlineData(10, true)]
+ public void EmitOverflowAttributeIsOnlySetWhenMaxMetricPointsIsGreaterThanOne(int maxMetricPoints, bool isEmitOverflowAttributeKeySet)
+ {
+ 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);
+ var metric = exportedItems[0];
+
+ var aggregatorStore = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metric) as AggregatorStore;
+ var emitOverflowAttribute = (bool)typeof(AggregatorStore).GetField("emitOverflowAttribute", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStore);
+
+ Assert.Equal(isEmitOverflowAttributeKeySet, emitOverflowAttribute);
+ }
+
+ [Theory]
+ [InlineData(MetricReaderTemporalityPreference.Delta)]
+ [InlineData(MetricReaderTemporalityPreference.Cumulative)]
+ public void MetricOverflowAttributeIsRecordedCorrectlyForCounter(MetricReaderTemporalityPreference temporalityPreference)
+ {
+ 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);
+ })
+ .AddMeter(meter.Name)
+ .AddInMemoryExporter(exportedItems, metricReaderOptions => metricReaderOptions.TemporalityPreference = temporalityPreference)
+ .Build();
+
+ // There are two reserved MetricPoints
+ // 1. For zero tags
+ // 2. For metric overflow attribute when user opts-in for this feature
+
+ counter.Add(10); // Record measurement for zero tags
+
+ // Max number for MetricPoints available for use when emitted with tags
+ int maxMetricPointsForUse = MeterProviderBuilderSdk.MaxMetricPointsPerMetricDefault - 2;
+
+ for (int i = 0; i < maxMetricPointsForUse; i++)
+ {
+ // 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));
+ }
+
+ meterProvider.ForceFlush();
+
+ Assert.Single(exportedItems);
+ var metric = exportedItems[0];
+
+ var metricPoints = new List();
+ foreach (ref readonly var mp in metric.GetMetricPoints())
+ {
+ metricPoints.Add(mp);
+ }
+
+ MetricPoint overflowMetricPoint;
+
+ // We still have not exceeded the max MetricPoint limit
+ Assert.DoesNotContain(metricPoints, mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
+
+ exportedItems.Clear();
+ metricPoints.Clear();
+
+ counter.Add(5, new KeyValuePair("Key", 1998)); // Emit a metric to exceed the max MetricPoint limit
+
+ meterProvider.ForceFlush();
+ metric = exportedItems[0];
+ foreach (ref readonly var mp in metric.GetMetricPoints())
+ {
+ metricPoints.Add(mp);
+ }
+
+ MetricPoint zeroTagsMetricPoint;
+ if (temporalityPreference == MetricReaderTemporalityPreference.Cumulative)
+ {
+ // Check metric point for zero tags
+ zeroTagsMetricPoint = metricPoints.Single(mp => mp.Tags.Count == 0);
+ Assert.Equal(10, zeroTagsMetricPoint.GetSumLong());
+ }
+
+ // Check metric point for overflow
+ overflowMetricPoint = metricPoints.Single(mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
+ Assert.Equal(true, overflowMetricPoint.Tags.KeyAndValues[0].Value);
+ Assert.Equal(1, overflowMetricPoint.Tags.Count);
+ Assert.Equal(5, overflowMetricPoint.GetSumLong());
+
+ exportedItems.Clear();
+ metricPoints.Clear();
+
+ counter.Add(15); // Record another measurement for zero tags
+
+ // Emit 2500 more newer MetricPoints with distinct dimension combinations
+ for (int i = 2000; i < 4500; i++)
+ {
+ counter.Add(5, new KeyValuePair("Key", i));
+ }
+
+ meterProvider.ForceFlush();
+ metric = exportedItems[0];
+ foreach (ref readonly var mp in metric.GetMetricPoints())
+ {
+ metricPoints.Add(mp);
+ }
+
+ zeroTagsMetricPoint = metricPoints.Single(mp => mp.Tags.Count == 0);
+ overflowMetricPoint = metricPoints.Single(mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
+
+ if (temporalityPreference == MetricReaderTemporalityPreference.Delta)
+ {
+ Assert.Equal(15, zeroTagsMetricPoint.GetSumLong());
+
+ int expectedSum;
+
+ // Number of metric points that were available before the 2500 measurements were made = 2000 (max MetricPoints) - 2 (reserved for zero tags and overflow) = 1998
+ if (this.shouldReclaimUnusedMetricPoints)
+ {
+ // If unused metric points are reclaimed, then number of metric points dropped = 2500 - 1998 = 502
+ expectedSum = 2510; // 502 * 5
+ }
+ else
+ {
+ expectedSum = 12500; // 2500 * 5
+ }
+
+ Assert.Equal(expectedSum, overflowMetricPoint.GetSumLong());
+ }
+ else
+ {
+ Assert.Equal(25, zeroTagsMetricPoint.GetSumLong());
+ Assert.Equal(12505, overflowMetricPoint.GetSumLong()); // 5 + (2500 * 5)
+ }
+
+ exportedItems.Clear();
+ metricPoints.Clear();
+
+ // Test that the SDK continues to correctly aggregate the previously registered measurements even after overflow has occurred
+ counter.Add(25);
+
+ meterProvider.ForceFlush();
+ metric = exportedItems[0];
+ foreach (ref readonly var mp in metric.GetMetricPoints())
+ {
+ metricPoints.Add(mp);
+ }
+
+ zeroTagsMetricPoint = metricPoints.Single(mp => mp.Tags.Count == 0);
+
+ if (temporalityPreference == MetricReaderTemporalityPreference.Delta)
+ {
+ Assert.Equal(25, zeroTagsMetricPoint.GetSumLong());
+ }
+ else
+ {
+ overflowMetricPoint = metricPoints.Single(mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
+
+ Assert.Equal(50, zeroTagsMetricPoint.GetSumLong());
+ Assert.Equal(12505, overflowMetricPoint.GetSumLong());
+ }
+ }
+
+ [Theory]
+ [InlineData(MetricReaderTemporalityPreference.Delta)]
+ [InlineData(MetricReaderTemporalityPreference.Cumulative)]
+ public void MetricOverflowAttributeIsRecordedCorrectlyForHistogram(MetricReaderTemporalityPreference temporalityPreference)
+ {
+ var exportedItems = new List();
+
+ var meter = new Meter(Utils.GetCurrentMethodName());
+ var histogram = meter.CreateHistogram("TestHistogram");
+
+ using var meterProvider = Sdk.CreateMeterProviderBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddSingleton(this.configuration);
+ })
+ .AddMeter(meter.Name)
+ .AddInMemoryExporter(exportedItems, metricReaderOptions => metricReaderOptions.TemporalityPreference = temporalityPreference)
+ .Build();
+
+ // There are two reserved MetricPoints
+ // 1. For zero tags
+ // 2. For metric overflow attribute when user opts-in for this feature
+
+ histogram.Record(10); // Record measurement for zero tags
+
+ // Max number for MetricPoints available for use when emitted with tags
+ int maxMetricPointsForUse = MeterProviderBuilderSdk.MaxMetricPointsPerMetricDefault - 2;
+
+ for (int i = 0; i < maxMetricPointsForUse; i++)
+ {
+ // 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));
+ }
+
+ meterProvider.ForceFlush();
+
+ Assert.Single(exportedItems);
+ var metric = exportedItems[0];
+
+ var metricPoints = new List();
+ foreach (ref readonly var mp in metric.GetMetricPoints())
+ {
+ metricPoints.Add(mp);
+ }
+
+ MetricPoint overflowMetricPoint;
+
+ // We still have not exceeded the max MetricPoint limit
+ Assert.DoesNotContain(metricPoints, mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
+
+ exportedItems.Clear();
+ metricPoints.Clear();
+
+ histogram.Record(5, new KeyValuePair("Key", 1998)); // Emit a metric to exceed the max MetricPoint limit
+
+ meterProvider.ForceFlush();
+ metric = exportedItems[0];
+ foreach (ref readonly var mp in metric.GetMetricPoints())
+ {
+ metricPoints.Add(mp);
+ }
+
+ MetricPoint zeroTagsMetricPoint;
+ if (temporalityPreference == MetricReaderTemporalityPreference.Cumulative)
+ {
+ // Check metric point for zero tags
+ zeroTagsMetricPoint = metricPoints.Single(mp => mp.Tags.Count == 0);
+ Assert.Equal(10, zeroTagsMetricPoint.GetHistogramSum());
+ }
+
+ // Check metric point for overflow
+ overflowMetricPoint = metricPoints.Single(mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
+ Assert.Equal(true, overflowMetricPoint.Tags.KeyAndValues[0].Value);
+ Assert.Equal(1, overflowMetricPoint.Tags.Count);
+ Assert.Equal(5, overflowMetricPoint.GetHistogramSum());
+
+ exportedItems.Clear();
+ metricPoints.Clear();
+
+ histogram.Record(15); // Record another measurement for zero tags
+
+ // Emit 2500 more newer MetricPoints with distinct dimension combinations
+ for (int i = 2000; i < 4500; i++)
+ {
+ histogram.Record(5, new KeyValuePair("Key", i));
+ }
+
+ meterProvider.ForceFlush();
+ metric = exportedItems[0];
+ foreach (ref readonly var mp in metric.GetMetricPoints())
+ {
+ metricPoints.Add(mp);
+ }
+
+ zeroTagsMetricPoint = metricPoints.Single(mp => mp.Tags.Count == 0);
+ overflowMetricPoint = metricPoints.Single(mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
+
+ if (temporalityPreference == MetricReaderTemporalityPreference.Delta)
+ {
+ Assert.Equal(15, zeroTagsMetricPoint.GetHistogramSum());
+
+ int expectedCount;
+ int expectedSum;
+
+ // Number of metric points that were available before the 2500 measurements were made = 2000 (max MetricPoints) - 2 (reserved for zero tags and overflow) = 1998
+ if (this.shouldReclaimUnusedMetricPoints)
+ {
+ // If unused metric points are reclaimed, then number of metric points dropped = 2500 - 1998 = 502
+ expectedCount = 502;
+ expectedSum = 2510; // 502 * 5
+ }
+ else
+ {
+ expectedCount = 2500;
+ expectedSum = 12500; // 2500 * 5
+ }
+
+ Assert.Equal(expectedCount, overflowMetricPoint.GetHistogramCount());
+ Assert.Equal(expectedSum, overflowMetricPoint.GetHistogramSum());
+ }
+ else
+ {
+ Assert.Equal(25, zeroTagsMetricPoint.GetHistogramSum());
+
+ Assert.Equal(2501, overflowMetricPoint.GetHistogramCount());
+ Assert.Equal(12505, overflowMetricPoint.GetHistogramSum()); // 5 + (2500 * 5)
+ }
+
+ exportedItems.Clear();
+ metricPoints.Clear();
+
+ // Test that the SDK continues to correctly aggregate the previously registered measurements even after overflow has occurred
+ histogram.Record(25);
+
+ meterProvider.ForceFlush();
+ metric = exportedItems[0];
+ foreach (ref readonly var mp in metric.GetMetricPoints())
+ {
+ metricPoints.Add(mp);
+ }
+
+ zeroTagsMetricPoint = metricPoints.Single(mp => mp.Tags.Count == 0);
+
+ if (temporalityPreference == MetricReaderTemporalityPreference.Delta)
+ {
+ Assert.Equal(25, zeroTagsMetricPoint.GetHistogramSum());
+ }
+ else
+ {
+ overflowMetricPoint = metricPoints.Single(mp => mp.Tags.Count != 0 && mp.Tags.KeyAndValues[0].Key == "otel.metric.overflow");
+
+ Assert.Equal(50, zeroTagsMetricPoint.GetHistogramSum());
+ Assert.Equal(12505, overflowMetricPoint.GetHistogramSum());
+ }
+ }
+}
+
+public class MetricOverflowAttributeTests : MetricOverflowAttributeTestsBase
+{
+ public MetricOverflowAttributeTests()
+ : base(false)
+ {
+ }
+}
+
+public class MetricOverflowAttributeTestsWithReclaimAttribute : MetricOverflowAttributeTestsBase
+{
+ public MetricOverflowAttributeTestsWithReclaimAttribute()
+ : base(true)
+ {
+ }
+}
diff --git a/test/OpenTelemetry.Tests/Metrics/MetricPointReclaimTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricPointReclaimTests.cs
index b5ef0b65d02..129b1bfbb09 100644
--- a/test/OpenTelemetry.Tests/Metrics/MetricPointReclaimTests.cs
+++ b/test/OpenTelemetry.Tests/Metrics/MetricPointReclaimTests.cs
@@ -17,6 +17,8 @@
using System.Collections.Concurrent;
using System.Diagnostics.Metrics;
using System.Reflection;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Tests;
using Xunit;
@@ -24,6 +26,80 @@ namespace OpenTelemetry.Metrics.Tests;
public class MetricPointReclaimTests
{
+ public const string ReclaimUnusedMetricPointsConfigKey = "OTEL_DOTNET_EXPERIMENTAL_METRICS_RECLAIM_UNUSED_METRIC_POINTS";
+
+ private readonly Dictionary configurationData = new()
+ {
+ [ReclaimUnusedMetricPointsConfigKey] = "true",
+ };
+
+ private readonly IConfiguration configuration;
+
+ public MetricPointReclaimTests()
+ {
+ this.configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(this.configurationData)
+ .Build();
+ }
+
+ [Theory]
+ [InlineData("false", false)]
+ [InlineData("False", false)]
+ [InlineData("FALSE", false)]
+ [InlineData("true", true)]
+ [InlineData("True", true)]
+ [InlineData("TRUE", true)]
+ public void TestReclaimAttributeConfigWithEnvVar(string value, bool isReclaimAttributeKeySet)
+ {
+ // Clear the environment variable value first
+ Environment.SetEnvironmentVariable(ReclaimUnusedMetricPointsConfigKey, null);
+
+ // Set the environment variable to the value provided in the test input
+ Environment.SetEnvironmentVariable(ReclaimUnusedMetricPointsConfigKey, value);
+
+ var exportedItems = new List();
+
+ var meter = new Meter(Utils.GetCurrentMethodName());
+
+ using var meterProvider = Sdk.CreateMeterProviderBuilder()
+ .AddMeter(meter.Name)
+ .AddInMemoryExporter(exportedItems)
+ .Build();
+
+ var meterProviderSdk = meterProvider as MeterProviderSdk;
+ Assert.Equal(isReclaimAttributeKeySet, meterProviderSdk.ShouldReclaimUnusedMetricPoints);
+ }
+
+ [Theory]
+ [InlineData("false", false)]
+ [InlineData("False", false)]
+ [InlineData("FALSE", false)]
+ [InlineData("true", true)]
+ [InlineData("True", true)]
+ [InlineData("TRUE", true)]
+ public void TestReclaimAttributeConfigWithOtherConfigProvider(string value, bool isReclaimAttributeKeySet)
+ {
+ var exportedItems = new List();
+
+ var meter = new Meter(Utils.GetCurrentMethodName());
+
+ using var meterProvider = Sdk.CreateMeterProviderBuilder()
+ .ConfigureServices(services =>
+ {
+ var configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary { [ReclaimUnusedMetricPointsConfigKey] = value })
+ .Build();
+
+ services.AddSingleton(configuration);
+ })
+ .AddMeter(meter.Name)
+ .AddInMemoryExporter(exportedItems)
+ .Build();
+
+ var meterProviderSdk = meterProvider as MeterProviderSdk;
+ Assert.Equal(isReclaimAttributeKeySet, meterProviderSdk.ShouldReclaimUnusedMetricPoints);
+ }
+
[Theory]
[InlineData(false)]
[InlineData(true)]
@@ -42,6 +118,10 @@ public void MeasurementsAreNotDropped(bool emitMetricWithNoDimensions)
};
using var meterProvider = Sdk.CreateMeterProviderBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddSingleton(this.configuration);
+ })
.AddMeter(Utils.GetCurrentMethodName())
.AddReader(metricReader)
.Build();
@@ -131,6 +211,10 @@ public void MeasurementsAreAggregatedAfterMetricPointReclaim(bool emitMetricWith
};
using var meterProvider = Sdk.CreateMeterProviderBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddSingleton(this.configuration);
+ })
.AddMeter(Utils.GetCurrentMethodName())
.SetMaxMetricPointsPerMetricStream(10) // Set max MetricPoints limit to 5
.AddReader(metricReader)
@@ -152,12 +236,12 @@ void EmitMetric()
{
int numberOfMeasurements = 0;
var random = new Random();
- while (emitMetricWithNoDimension)
+ while (true)
{
if (numberOfMeasurements < numberOfMeasurementsPerThread)
{
// Check for cases where a metric with no dimension is also emitted
- if (true)
+ if (emitMetricWithNoDimension)
{
counter.Add(25);
Interlocked.Add(ref sum, 25);
@@ -196,12 +280,12 @@ void EmitMetric()
Assert.Equal(sum, exporter.Sum);
}
- private class ThreadArguments
+ private sealed class ThreadArguments
{
public int Counter;
}
- private class CustomExporter : BaseExporter
+ private sealed class CustomExporter : BaseExporter
{
public long Sum = 0;
diff --git a/test/OpenTelemetry.Tests/Metrics/MetricSnapshotTestsBase.cs b/test/OpenTelemetry.Tests/Metrics/MetricSnapshotTestsBase.cs
index dc0810fd338..9a22f7b6795 100644
--- a/test/OpenTelemetry.Tests/Metrics/MetricSnapshotTestsBase.cs
+++ b/test/OpenTelemetry.Tests/Metrics/MetricSnapshotTestsBase.cs
@@ -15,7 +15,8 @@
//
using System.Diagnostics.Metrics;
-
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Tests;
using Xunit;
@@ -24,14 +25,15 @@ namespace OpenTelemetry.Metrics.Tests;
#pragma warning disable SA1402
-public abstract class MetricSnapshotTestsBase : IDisposable
+public abstract class MetricSnapshotTestsBase
{
- protected MetricSnapshotTestsBase(bool emitOverflowAttribute)
+ private readonly IConfiguration configuration;
+
+ protected MetricSnapshotTestsBase(bool emitOverflowAttribute, bool shouldReclaimUnusedMetricPoints)
{
- if (emitOverflowAttribute)
- {
- Environment.SetEnvironmentVariable(MetricTestsBase.EmitOverFlowAttributeConfigKey, "true");
- }
+ this.configuration = MetricApiTestsBase.BuildConfiguration(
+ emitOverflowAttribute,
+ shouldReclaimUnusedMetricPoints);
}
[Fact]
@@ -43,6 +45,10 @@ public void VerifySnapshot_Counter()
using var meter = new Meter(Utils.GetCurrentMethodName());
var counter = meter.CreateCounter